]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(freqai): preserve restart state correctly
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 23 Sep 2026 13:10:53 +0000 (15:10 +0200)
committerGitHub <noreply@github.com>
Wed, 23 Sep 2026 13:10:53 +0000 (15:10 +0200)
Squash merge PR #274 after all quality and CodeQL checks passed.

README.md
ReforceXY/.basedpyright/diagnostics.json
ReforceXY/tests/test_training_observations.py
ReforceXY/user_data/freqaimodels/ReforceXY.py
quickadapter/.basedpyright/diagnostics.json
quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py
quickadapter/user_data/strategies/LabelTransformer.py
quickadapter/user_data/strategies/QuickAdapterV3.py

index ab08b9f6ba517eb86a8e4eef845dfad966ddbe35..35d248fb7f69cbb4a0876e4fd693e1df450525ca 100644 (file)
--- a/README.md
+++ b/README.md
@@ -170,11 +170,25 @@ specific; equally specific patterns follow declaration order, so the later one
 wins.
 
 In live and dry-run modes, each pair requires
-`freqai.fit_live_predictions_candles` model predictions after session startup
-before adaptive thresholds become available. Restarting requires a new warmup.
-Downtime and expired-model rows are excluded; genuine zero and outlier-rejected
-predictions count. Predictions align to candle dates; candles without
-predictions have `do_predict=0` and downtime zeros display but never calibrate.
+`freqai.fit_live_predictions_candles` real model predictions before adaptive
+thresholds become available. The Nth observation first affects the next
+prediction update, not the candle that produced it. FreqAI bootstrap
+predictions made from the initial training frame do not count. Warmup progress
+from persisted real predictions is restored after a restart. FreqAI saves
+prediction history after training attempts and on clean shutdown. After an
+abrupt stop or hard reboot, predictions since the last history save may be
+lost, so those observations must accumulate again. Legacy rows missing
+provenance, including rows in partly marked histories, count only when their
+nonzero, nonexpired prediction status distinguishes them from bootstrap;
+ambiguous rejected rows and explicit false markers do not count.
+
+A pair starts a new warmup when the time since its last observation, or a gap
+within its observations, is greater than
+`fit_live_predictions_candles × timeframe`. An observation exactly one horizon
+old remains eligible. Downtime and expired-model rows are excluded; genuine
+zero and outlier-rejected predictions count. Predictions align to candle dates;
+candles without predictions have `do_predict=0` and downtime zeros display but
+never calibrate.
 
 ### Backtest evaluation protocol
 
@@ -348,7 +362,8 @@ The documented list of model tunables is at the top of the
 
 Continual learning trains an independent copy of the deployed policy with its
 fitted feature pipeline. DQN/QRDQN deployments each persist their replay buffer;
-missing or incompatible replay data prevents continuation. Reset trained models
+it is loaded only when continual training starts. Missing or incompatible replay
+data prevents continuation but does not prevent inference. Reset trained models
 or use a new `freqai.identifier` to migrate incompatible artifacts, including
 deployments without the chronological training marker. Training disables
 `shuffle_after_split`. HPO studies and saved best parameters are reused only
@@ -356,9 +371,20 @@ when their objective identity matches.
 
 ### Live inference
 
-Optional `fit_live_predictions_candles` statistics count produced observations
-per pair after session startup; restarts reset the warmup. See the model
-docstrings for continuation, HPO and statistics details.
+Optional `fit_live_predictions_candles` statistics use the latest persisted real
+predictions per pair, excluding FreqAI bootstrap rows. Available observations
+are used before a full window accumulates and survive restarts. FreqAI returns
+the initial strategy frame before calculating live statistics; restored
+statistics appear on the next prediction update. Legacy rows missing provenance,
+including rows in partly marked histories, can count when their nonzero,
+nonexpired prediction status distinguishes them from bootstrap; zero-status rows
+remain excluded because bootstrap and rejected predictions cannot be
+distinguished. Explicit false markers remain excluded.
+On duplicate candle dates, a provable prediction takes precedence over an
+ambiguous close-bearing legacy row during history restoration.
+Rows with an invalid `date_pred` are discarded with a per-pair warning and the
+discarded-row count; valid duplicates retain the same precedence.
+See the model docstrings for continuation, HPO and statistics details.
 
 With `hold_potential_enabled=true`, ReforceXY enables `add_state_info` before
 constructing environments so training and inference use the same observations.
@@ -382,7 +408,8 @@ numeric value. Environment prices remain raw regardless of
 current evaluation run when available. DQN/QRDQN HPO rejects warmup budgets that
 leave no gradient update and trials that finish without learning. A zero-sized
 holdout remains supported when HPO is disabled, including with raw OHLC feature
-removal.
+removal. An interrupted ReforceXY fit is logged and still selects the best
+usable checkpoint when available, falling back to the final model otherwise.
 
 ### Reward and portfolio accounting
 
index 85cab2b5fd002dcfcc00e763ee1ab8a756256f7d..a305e4bd45e05e65efc8bc3c76206da72082a28f 100644 (file)
     },
     {
       "endCharacter": 22,
-      "endLine": 914,
+      "endLine": 1010,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Cannot assign to attribute \"train_env\" for class \"ReforceXY*\"\n  Expression of type \"VecEnv\" cannot be assigned to attribute \"train_env\" of class \"ReforceXY\"\n    Type \"VecEnv\" is not assignable to type \"VecMonitor | SubprocVecEnv | Env[Unknown, Unknown]\"\n      \"VecEnv\" is not assignable to \"VecMonitor\"\n      \"VecEnv\" is not assignable to \"SubprocVecEnv\"\n      \"VecEnv\" is not assignable to \"Env[Unknown, Unknown]\"",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 13,
-      "startLine": 914
+      "startLine": 1010
     },
     {
       "endCharacter": 37,
-      "endLine": 914,
+      "endLine": 1010,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Cannot assign to attribute \"eval_env\" for class \"ReforceXY*\"\n  Expression of type \"VecEnv | None\" cannot be assigned to attribute \"eval_env\" of class \"ReforceXY\"\n    Type \"VecEnv | None\" is not assignable to type \"VecMonitor | SubprocVecEnv | Env[Unknown, Unknown]\"\n      Type \"VecEnv\" is not assignable to type \"VecMonitor | SubprocVecEnv | Env[Unknown, Unknown]\"\n        \"VecEnv\" is not assignable to \"VecMonitor\"\n        \"VecEnv\" is not assignable to \"SubprocVecEnv\"\n        \"VecEnv\" is not assignable to \"Env[Unknown, Unknown]\"",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 29,
-      "startLine": 914
+      "startLine": 1010
     },
     {
       "endCharacter": 24,
-      "endLine": 1173,
+      "endLine": 1269,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"VecEnv\" cannot be assigned to parameter \"eval_env\" of type \"BaseEnvironment\" in function \"__init__\"\n  \"VecEnv\" is not assignable to \"BaseEnvironment\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 16,
-      "startLine": 1173
+      "startLine": 1269
     },
     {
       "endCharacter": 38,
-      "endLine": 1378,
+      "endLine": 1477,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"obj\" of type \"Sized\" in function \"len\"\n  Type \"Any | None\" is not assignable to type \"Sized\"\n    \"None\" is incompatible with protocol \"Sized\"\n      \"__len__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 30,
-      "startLine": 1378
+      "startLine": 1477
     },
     {
       "endCharacter": 36,
-      "endLine": 1382,
+      "endLine": 1481,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"obj\" of type \"Sized\" in function \"len\"\n  Type \"Any | None\" is not assignable to type \"Sized\"\n    \"None\" is incompatible with protocol \"Sized\"\n      \"__len__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 29,
-      "startLine": 1382
+      "startLine": 1481
     },
     {
       "endCharacter": 80,
-      "endLine": 1385,
+      "endLine": 1484,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"timeframe\" of type \"str\" in function \"steps_to_days\"\n  Type \"Any | None\" is not assignable to type \"str\"\n    \"None\" is not assignable to \"str\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 52,
-      "startLine": 1385
+      "startLine": 1484
     },
     {
       "endCharacter": 78,
-      "endLine": 1386,
+      "endLine": 1485,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"timeframe\" of type \"str\" in function \"steps_to_days\"\n  Type \"Any | None\" is not assignable to type \"str\"\n    \"None\" is not assignable to \"str\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 50,
-      "startLine": 1386
+      "startLine": 1485
     },
     {
       "endCharacter": 80,
-      "endLine": 1387,
+      "endLine": 1486,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"timeframe\" of type \"str\" in function \"steps_to_days\"\n  Type \"Any | None\" is not assignable to type \"str\"\n    \"None\" is not assignable to \"str\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 52,
-      "startLine": 1387
+      "startLine": 1486
     },
     {
       "endCharacter": 56,
-      "endLine": 1472,
+      "endLine": 1571,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"VecMonitor | SubprocVecEnv | Env[Unknown, Unknown]\" cannot be assigned to parameter \"eval_env\" of type \"VecEnv | None\" in function \"get_callbacks\"\n  Type \"VecMonitor | SubprocVecEnv | Env[Unknown, Unknown]\" is not assignable to type \"VecEnv | None\"\n    Type \"Env[Unknown, Unknown]\" is not assignable to type \"VecEnv | None\"\n      \"Env[Unknown, Unknown]\" is not assignable to \"VecEnv\"\n      \"Env[Unknown, Unknown]\" is not assignable to \"None\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 43,
-      "startLine": 1472
+      "startLine": 1571
     },
     {
       "endCharacter": 84,
-      "endLine": 1492,
+      "endLine": 1591,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | list[float]\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | list[float]\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"list[float]\" is not assignable to type \"ConvertibleToFloat\"\n      \"list[float]\" is not assignable to \"str\"\n      \"list[float]\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"list[float]\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"list[float]\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 67,
-      "startLine": 1492
+      "startLine": 1591
     },
     {
       "endCharacter": 84,
-      "endLine": 1492,
+      "endLine": 1591,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | list[float]\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | list[float]\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"list[float]\" is not assignable to type \"ConvertibleToFloat\"\n      \"list[float]\" is not assignable to \"str\"\n      \"list[float]\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"list[float]\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"list[float]\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 67,
-      "startLine": 1492
+      "startLine": 1591
     },
     {
       "endCharacter": 44,
-      "endLine": 2586,
+      "endLine": 2665,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"best_trial_params\" is possibly unbound",
       "rule": "reportPossiblyUnboundVariable",
       "severity": "error",
       "startCharacter": 27,
-      "startLine": 2586
+      "startLine": 2665
     },
     {
       "endCharacter": 12,
-      "endLine": 2606,
+      "endLine": 2685,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Parameter declaration \"seed\" is obscured by a declaration of the same name",
       "rule": "reportRedeclaration",
       "severity": "error",
       "startCharacter": 8,
-      "startLine": 2606
+      "startLine": 2685
     },
     {
       "endCharacter": 16,
-      "endLine": 2607,
+      "endLine": 2686,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Parameter declaration \"env_info\" is obscured by a declaration of the same name",
       "rule": "reportRedeclaration",
       "severity": "error",
       "startCharacter": 8,
-      "startLine": 2607
+      "startLine": 2686
     },
     {
       "endCharacter": 51,
-      "endLine": 2650,
+      "endLine": 2729,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"list[() -> BaseEnvironment]\" cannot be assigned to parameter \"env_fns\" of type \"list[() -> Env[Unknown, Unknown]]\" in function \"__init__\"\n  \"list[() -> BaseEnvironment]\" is not assignable to \"list[() -> Env[Unknown, Unknown]]\"\n    Type parameter \"_T@list\" is invariant, but \"() -> BaseEnvironment\" is not the same as \"() -> Env[Unknown, Unknown]\"\n    Consider switching from \"list\" to \"Sequence\" which is covariant",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 42,
-      "startLine": 2650
+      "startLine": 2729
     },
     {
       "endCharacter": 53,
-      "endLine": 2656,
+      "endLine": 2735,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"list[() -> BaseEnvironment]\" cannot be assigned to parameter \"env_fns\" of type \"list[() -> Env[Unknown, Unknown]]\" in function \"__init__\"\n  \"list[() -> BaseEnvironment]\" is not assignable to \"list[() -> Env[Unknown, Unknown]]\"\n    Type parameter \"_T@list\" is invariant, but \"() -> BaseEnvironment\" is not the same as \"() -> Env[Unknown, Unknown]\"\n    Consider switching from \"list\" to \"Sequence\" which is covariant",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 45,
-      "startLine": 2656
+      "startLine": 2735
     },
     {
       "endCharacter": 22,
-      "endLine": 2719,
+      "endLine": 2798,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 15,
-      "startLine": 2719
+      "startLine": 2798
     },
     {
       "endCharacter": 96,
-      "endLine": 2721,
+      "endLine": 2800,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 89,
-      "startLine": 2721
+      "startLine": 2800
     },
     {
       "endCharacter": 23,
-      "endLine": 2724,
+      "endLine": 2803,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 16,
-      "startLine": 2724
+      "startLine": 2803
     },
     {
       "endCharacter": 98,
-      "endLine": 2726,
+      "endLine": 2805,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 91,
-      "startLine": 2726
+      "startLine": 2805
     },
     {
       "endCharacter": 44,
-      "endLine": 2738,
+      "endLine": 2817,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for types \"Any | None\" and \"Any | int | None\"\n  Operator \"*\" not supported for types \"None\" and \"None\"\n  Operator \"*\" not supported for types \"None\" and \"int\"",
       "rule": "reportOperatorIssue",
       "severity": "error",
       "startCharacter": 15,
-      "startLine": 2738
+      "startLine": 2817
     },
     {
       "endCharacter": 133,
-      "endLine": 2740,
+      "endLine": 2819,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for types \"Any | None\" and \"Any | int | None\"\n  Operator \"*\" not supported for types \"None\" and \"None\"\n  Operator \"*\" not supported for types \"None\" and \"int\"",
       "rule": "reportOperatorIssue",
       "severity": "error",
       "startCharacter": 106,
-      "startLine": 2740
+      "startLine": 2819
     },
     {
       "endCharacter": 30,
-      "endLine": 2743,
+      "endLine": 2822,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \">\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 15,
-      "startLine": 2743
+      "startLine": 2822
     },
     {
       "endCharacter": 54,
-      "endLine": 2808,
+      "endLine": 2887,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"is_pruned\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 45,
-      "startLine": 2808
+      "startLine": 2887
     },
     {
       "endCharacter": 67,
-      "endLine": 2811,
+      "endLine": 2890,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"use_masking\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 56,
-      "startLine": 2811
+      "startLine": 2890
     },
     {
       "endCharacter": 60,
-      "endLine": 2819,
+      "endLine": 2898,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"update_best_reward\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 42,
-      "startLine": 2819
+      "startLine": 2898
     },
     {
       "endCharacter": 84,
-      "endLine": 2819,
+      "endLine": 2898,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | list[float]\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | list[float]\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"list[float]\" is not assignable to type \"ConvertibleToFloat\"\n      \"list[float]\" is not assignable to \"str\"\n      \"list[float]\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"list[float]\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"list[float]\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 67,
-      "startLine": 2819
+      "startLine": 2898
     },
     {
       "endCharacter": 84,
-      "endLine": 2819,
+      "endLine": 2898,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | list[float]\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | list[float]\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"list[float]\" is not assignable to type \"ConvertibleToFloat\"\n      \"list[float]\" is not assignable to \"str\"\n      \"list[float]\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"list[float]\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"list[float]\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 67,
-      "startLine": 2819
+      "startLine": 2898
     },
     {
       "endCharacter": 46,
-      "endLine": 2873,
+      "endLine": 2952,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"is_pruned\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 37,
-      "startLine": 2873
+      "startLine": 2952
     },
     {
       "endCharacter": 57,
-      "endLine": 2876,
+      "endLine": 2955,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"best_mean_reward\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 41,
-      "startLine": 2876
+      "startLine": 2955
     },
     {
       "endCharacter": 37,
-      "endLine": 2886,
+      "endLine": 2965,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Cannot assign to attribute \"train_env\" for class \"ReforceXY*\"\n  Type \"None\" is not assignable to type \"VecMonitor | SubprocVecEnv | Env[Unknown, Unknown]\"\n    \"None\" is not assignable to \"VecMonitor\"\n    \"None\" is not assignable to \"SubprocVecEnv\"\n    \"None\" is not assignable to \"Env[Unknown, Unknown]\"",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 33,
-      "startLine": 2886
+      "startLine": 2965
     },
     {
       "endCharacter": 36,
-      "endLine": 2891,
+      "endLine": 2970,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Cannot assign to attribute \"eval_env\" for class \"ReforceXY*\"\n  Type \"None\" is not assignable to type \"VecMonitor | SubprocVecEnv | Env[Unknown, Unknown]\"\n    \"None\" is not assignable to \"VecMonitor\"\n    \"None\" is not assignable to \"SubprocVecEnv\"\n    \"None\" is not assignable to \"Env[Unknown, Unknown]\"",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 32,
-      "startLine": 2891
+      "startLine": 2970
     },
     {
       "endCharacter": 7,
-      "endLine": 2936,
+      "endLine": 3015,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Declaration \"MyRLEnv\" is obscured by a declaration of the same name",
       "rule": "reportRedeclaration",
       "severity": "error",
       "startCharacter": 0,
-      "startLine": 2936
+      "startLine": 3015
     },
     {
       "endCharacter": 36,
-      "endLine": 2948,
+      "endLine": 3027,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Declaration \"_last_closed_trade_tick\" is obscured by a declaration of the same name",
       "rule": "reportRedeclaration",
       "severity": "error",
       "startCharacter": 13,
-      "startLine": 2948
+      "startLine": 3027
     },
     {
       "endCharacter": 13,
-      "endLine": 3540,
+      "endLine": 3619,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Method \"reset\" overrides class \"BaseEnvironment\" in an incompatible manner\n  Return type mismatch: base method returns type \"tuple[DataFrame, dict[Unknown, Unknown]]\", override returns type \"tuple[NDArray[float32], dict[str, Any]]\"\n    \"tuple[NDArray[float32], dict[str, Any]]\" is not assignable to \"tuple[DataFrame, dict[Unknown, Unknown]]\"\n      Tuple entry 1 is incorrect type\n        \"ndarray[_AnyShape, dtype[float32]]\" is not assignable to \"DataFrame\"",
       "rule": "reportIncompatibleMethodOverride",
       "severity": "error",
       "startCharacter": 8,
-      "startLine": 3540
+      "startLine": 3619
     },
     {
       "endCharacter": 26,
-      "endLine": 3561,
+      "endLine": 3640,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Type \"tuple[DataFrame, dict[Unknown, Unknown]]\" is not assignable to return type \"tuple[NDArray[float32], dict[str, Any]]\"\n  \"DataFrame\" is not assignable to \"ndarray[_AnyShape, dtype[float32]]\"",
       "rule": "reportReturnType",
       "severity": "error",
       "startCharacter": 15,
-      "startLine": 3561
+      "startLine": 3640
     },
     {
       "endCharacter": 55,
-      "endLine": 3869,
+      "endLine": 3948,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 44,
-      "startLine": 3869
+      "startLine": 3948
     },
     {
       "endCharacter": 55,
-      "endLine": 3869,
+      "endLine": 3948,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 44,
-      "startLine": 3869
+      "startLine": 3948
     },
     {
       "endCharacter": 59,
-      "endLine": 3893,
+      "endLine": 3972,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 48,
-      "startLine": 3893
+      "startLine": 3972
     },
     {
       "endCharacter": 59,
-      "endLine": 3893,
+      "endLine": 3972,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 48,
-      "startLine": 3893
+      "startLine": 3972
     },
     {
       "endCharacter": 24,
-      "endLine": 3922,
+      "endLine": 4001,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Method \"_get_observation\" overrides class \"BaseEnvironment\" in an incompatible manner\n  Return type mismatch: base method returns type \"DataFrame\", override returns type \"NDArray[float32]\"\n    \"ndarray[_AnyShape, dtype[float32]]\" is not assignable to \"DataFrame\"",
       "rule": "reportIncompatibleMethodOverride",
       "severity": "error",
       "startCharacter": 8,
-      "startLine": 3922
+      "startLine": 4001
     },
     {
       "endCharacter": 12,
-      "endLine": 4024,
+      "endLine": 4103,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Method \"step\" overrides class \"Base5ActionRLEnv\" in an incompatible manner\n  Return type mismatch: base method returns type \"tuple[DataFrame, float, bool, Literal[False], dict[str, Unknown]]\", override returns type \"tuple[NDArray[float32], float, bool, bool, dict[str, Any]]\"\n    \"tuple[NDArray[float32], float, bool, bool, dict[str, Any]]\" is not assignable to \"tuple[DataFrame, float, bool, Literal[False], dict[str, Unknown]]\"\n      Tuple entry 1 is incorrect type\n        \"ndarray[_AnyShape, dtype[float32]]\" is not assignable to \"DataFrame\"",
       "rule": "reportIncompatibleMethodOverride",
       "severity": "error",
       "startCharacter": 8,
-      "startLine": 4024
+      "startLine": 4103
     },
     {
       "endCharacter": 20,
-      "endLine": 4207,
+      "endLine": 4290,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Method \"action_masks\" overrides class \"BaseEnvironment\" in an incompatible manner\n  Return type mismatch: base method returns type \"list[bool]\", override returns type \"NDArray[bool_]\"\n    \"ndarray[_AnyShape, dtype[bool_]]\" is not assignable to \"list[bool]\"",
       "rule": "reportIncompatibleMethodOverride",
       "severity": "error",
       "startCharacter": 8,
-      "startLine": 4207
+      "startLine": 4290
     },
     {
       "endCharacter": 40,
-      "endLine": 4356,
+      "endLine": 4439,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"Figure\" is not exported from module \"matplotlib.pyplot\"",
       "rule": "reportPrivateImportUsage",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 4356
+      "startLine": 4439
     },
     {
       "endCharacter": 54,
-      "endLine": 4859,
+      "endLine": 4942,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 45,
-      "startLine": 4859
+      "startLine": 4942
     },
     {
       "endCharacter": 54,
-      "endLine": 4859,
+      "endLine": 4942,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 45,
-      "startLine": 4859
+      "startLine": 4942
     },
     {
       "endCharacter": 50,
-      "endLine": 4871,
+      "endLine": 4954,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"object\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"object\" is not assignable to type \"ConvertibleToFloat\"\n    \"object\" is not assignable to \"str\"\n    \"object\" is incompatible with protocol \"Buffer\"\n      \"__buffer__\" is not present\n    \"object\" is incompatible with protocol \"SupportsFloat\"\n      \"__float__\" is not present\n    \"object\" is incompatible with protocol \"SupportsIndex\"\n      \"__index__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 37,
-      "startLine": 4871
+      "startLine": 4954
     },
     {
       "endCharacter": 72,
-      "endLine": 4882,
+      "endLine": 4965,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 70,
-      "startLine": 4882
+      "startLine": 4965
     },
     {
       "endCharacter": 72,
-      "endLine": 4882,
+      "endLine": 4965,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 70,
-      "startLine": 4882
+      "startLine": 4965
     },
     {
       "endCharacter": 73,
-      "endLine": 4891,
+      "endLine": 4974,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 71,
-      "startLine": 4891
+      "startLine": 4974
     },
     {
       "endCharacter": 73,
-      "endLine": 4891,
+      "endLine": 4974,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"float | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"float | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 71,
-      "startLine": 4891
+      "startLine": 4974
     },
     {
       "endCharacter": 58,
-      "endLine": 4900,
+      "endLine": 4983,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 56,
-      "startLine": 4900
+      "startLine": 4983
     },
     {
       "endCharacter": 58,
-      "endLine": 4900,
+      "endLine": 4983,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 56,
-      "startLine": 4900
+      "startLine": 4983
     },
     {
       "endCharacter": 50,
-      "endLine": 5131,
+      "endLine": 5214,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Mapping[Unknown, Unknown]\" cannot be assigned to parameter \"src\" of type \"dict[str, Any]\" in function \"deepmerge\"\n  \"Mapping[Unknown, Unknown]\" is not assignable to \"dict[str, Any]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 49,
-      "startLine": 5131
+      "startLine": 5214
     },
     {
       "endCharacter": 20,
-      "endLine": 5308,
+      "endLine": 5391,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 18,
-      "startLine": 5308
+      "startLine": 5391
     },
     {
       "endCharacter": 20,
-      "endLine": 5308,
+      "endLine": 5391,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 18,
-      "startLine": 5308
+      "startLine": 5391
     },
     {
       "endCharacter": 59,
-      "endLine": 5313,
+      "endLine": 5396,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 31,
-      "startLine": 5313
+      "startLine": 5396
     },
     {
       "endCharacter": 59,
-      "endLine": 5313,
+      "endLine": 5396,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 31,
-      "startLine": 5313
+      "startLine": 5396
     },
     {
       "endCharacter": 65,
-      "endLine": 5314,
+      "endLine": 5397,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 5314
+      "startLine": 5397
     },
     {
       "endCharacter": 65,
-      "endLine": 5314,
+      "endLine": 5397,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 5314
+      "startLine": 5397
     },
     {
       "endCharacter": 57,
-      "endLine": 5315,
+      "endLine": 5398,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 31,
-      "startLine": 5315
+      "startLine": 5398
     },
     {
       "endCharacter": 57,
-      "endLine": 5315,
+      "endLine": 5398,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 31,
-      "startLine": 5315
+      "startLine": 5398
     },
     {
       "endCharacter": 63,
-      "endLine": 5317,
+      "endLine": 5400,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 5317
+      "startLine": 5400
     },
     {
       "endCharacter": 63,
-      "endLine": 5317,
+      "endLine": 5400,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 5317
+      "startLine": 5400
     },
     {
       "endCharacter": 61,
-      "endLine": 5319,
+      "endLine": 5402,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 32,
-      "startLine": 5319
+      "startLine": 5402
     },
     {
       "endCharacter": 61,
-      "endLine": 5319,
+      "endLine": 5402,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 32,
-      "startLine": 5319
+      "startLine": 5402
     },
     {
       "endCharacter": 67,
-      "endLine": 5320,
+      "endLine": 5403,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 36,
-      "startLine": 5320
+      "startLine": 5403
     },
     {
       "endCharacter": 67,
-      "endLine": 5320,
+      "endLine": 5403,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 36,
-      "startLine": 5320
+      "startLine": 5403
     },
     {
       "endCharacter": 73,
-      "endLine": 5321,
+      "endLine": 5404,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 39,
-      "startLine": 5321
+      "startLine": 5404
     },
     {
       "endCharacter": 73,
-      "endLine": 5321,
+      "endLine": 5404,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 39,
-      "startLine": 5321
+      "startLine": 5404
     },
     {
       "endCharacter": 61,
-      "endLine": 5322,
+      "endLine": 5405,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 33,
-      "startLine": 5322
+      "startLine": 5405
     },
     {
       "endCharacter": 61,
-      "endLine": 5322,
+      "endLine": 5405,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 33,
-      "startLine": 5322
+      "startLine": 5405
     },
     {
       "endCharacter": 89,
-      "endLine": 5329,
+      "endLine": 5412,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 52,
-      "startLine": 5329
+      "startLine": 5412
     },
     {
       "endCharacter": 89,
-      "endLine": 5329,
+      "endLine": 5412,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 52,
-      "startLine": 5329
+      "startLine": 5412
     },
     {
       "endCharacter": 83,
-      "endLine": 5330,
+      "endLine": 5413,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 49,
-      "startLine": 5330
+      "startLine": 5413
     },
     {
       "endCharacter": 83,
-      "endLine": 5330,
+      "endLine": 5413,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 49,
-      "startLine": 5330
+      "startLine": 5413
     },
     {
       "endCharacter": 57,
-      "endLine": 5355,
+      "endLine": 5438,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 31,
-      "startLine": 5355
+      "startLine": 5438
     },
     {
       "endCharacter": 57,
-      "endLine": 5355,
+      "endLine": 5438,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 31,
-      "startLine": 5355
+      "startLine": 5438
     },
     {
       "endCharacter": 65,
-      "endLine": 5356,
+      "endLine": 5439,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 5356
+      "startLine": 5439
     },
     {
       "endCharacter": 65,
-      "endLine": 5356,
+      "endLine": 5439,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 5356
+      "startLine": 5439
     },
     {
       "endCharacter": 67,
-      "endLine": 5358,
+      "endLine": 5441,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 35,
-      "startLine": 5358
+      "startLine": 5441
     },
     {
       "endCharacter": 67,
-      "endLine": 5358,
+      "endLine": 5441,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 35,
-      "startLine": 5358
+      "startLine": 5441
     },
     {
       "endCharacter": 87,
-      "endLine": 5361,
+      "endLine": 5444,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 46,
-      "startLine": 5361
+      "startLine": 5444
     },
     {
       "endCharacter": 87,
-      "endLine": 5361,
+      "endLine": 5444,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 46,
-      "startLine": 5361
+      "startLine": 5444
     },
     {
       "endCharacter": 93,
-      "endLine": 5362,
+      "endLine": 5445,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 49,
-      "startLine": 5362
+      "startLine": 5445
     },
     {
       "endCharacter": 93,
-      "endLine": 5362,
+      "endLine": 5445,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 49,
-      "startLine": 5362
+      "startLine": 5445
     },
     {
       "endCharacter": 89,
-      "endLine": 5363,
+      "endLine": 5446,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 47,
-      "startLine": 5363
+      "startLine": 5446
     },
     {
       "endCharacter": 89,
-      "endLine": 5363,
+      "endLine": 5446,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToFloat\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToFloat\"\n    Type \"None\" is not assignable to type \"ConvertibleToFloat\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsFloat\"\n        \"__float__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 47,
-      "startLine": 5363
+      "startLine": 5446
     },
     {
       "endCharacter": 89,
-      "endLine": 5364,
+      "endLine": 5447,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 46,
-      "startLine": 5364
+      "startLine": 5447
     },
     {
       "endCharacter": 89,
-      "endLine": 5364,
+      "endLine": 5447,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 46,
-      "startLine": 5364
+      "startLine": 5447
     },
     {
       "endCharacter": 75,
-      "endLine": 5365,
+      "endLine": 5448,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 39,
-      "startLine": 5365
+      "startLine": 5448
     },
     {
       "endCharacter": 75,
-      "endLine": 5365,
+      "endLine": 5448,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"Any | None\" is not assignable to type \"ConvertibleToInt\"\n    Type \"None\" is not assignable to type \"ConvertibleToInt\"\n      \"None\" is not assignable to \"str\"\n      \"None\" is incompatible with protocol \"Buffer\"\n        \"__buffer__\" is not present\n      \"None\" is incompatible with protocol \"SupportsInt\"\n        \"__int__\" is not present\n      \"None\" is incompatible with protocol \"SupportsIndex\"\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 39,
-      "startLine": 5365
+      "startLine": 5448
     },
     {
       "endCharacter": 33,
index 9bbd4362d59402b93103f6ba08c0fdd3a1391497..6fa931a56ecc3a467866f2bff520fbec2b116e86 100644 (file)
@@ -8,6 +8,8 @@ from unittest import mock
 
 import numpy as np
 import pandas as pd
+from freqtrade.exceptions import DependencyException
+from freqtrade.freqai.data_drawer import FreqaiDataDrawer
 from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
 from optuna import TrialPruned, create_study
 
@@ -60,27 +62,32 @@ class TrainingObservationsTest(unittest.TestCase):
                 replay = trained.replay_buffer.observations.copy()
                 size = trained.replay_buffer.size()
                 self.assertGreater(size, 0)
-                model.dd.model_dictionary.clear()
+                model.dd.model_dictionary[dk.pair] = trained
                 model.dd.meta_data_dictionary.clear()
+                direct_clone, _ = model._resolve_deployment_state(dk, dk.pair)
+                self.assertEqual(direct_clone.replay_buffer.size(), size)
+                np.testing.assert_array_equal(direct_clone.replay_buffer.observations, replay)
+                direct_clone.replay_buffer.observations.flat[0] += 42
+                np.testing.assert_array_equal(trained.replay_buffer.observations, replay)
+                model.dd.model_dictionary.clear()
                 restored = model.dd.load_data(dk.pair, dk)
-                self.assertEqual(restored.replay_buffer.size(), size)
-                np.testing.assert_array_equal(restored.replay_buffer.observations, replay)
+                self.assertEqual(restored.replay_buffer.size(), 0)
                 clone, _ = model._resolve_deployment_state(dk, dk.pair)
                 self.assertEqual(clone.replay_buffer.size(), size)
-                self.assertIsNot(clone.replay_buffer, restored.replay_buffer)
-                clone.replay_buffer.observations.flat[0] += 123.0
-                self.assertNotEqual(
-                    clone.replay_buffer.observations.flat[0],
-                    restored.replay_buffer.observations.flat[0],
-                )
-                np.testing.assert_array_equal(restored.replay_buffer.observations, replay)
+                np.testing.assert_array_equal(clone.replay_buffer.observations, replay)
+                clone.replay_buffer.observations.flat[0] += 42
+                np.testing.assert_array_equal(trained.replay_buffer.observations, replay)
+                self.assertEqual(restored.replay_buffer.size(), 0)
                 self.assertIs(model.dd.load_data(dk.pair, dk), restored)
                 model.dd.model_dictionary.clear()
                 (dk.data_path / dk.data["reforcexy_replay"]).unlink()
-                for _ in range(2):
-                    with self.assertRaises(FileNotFoundError):
-                        model.dd.load_data(dk.pair, dk)
-                    self.assertNotIn(dk.pair, model.dd.model_dictionary)
+                inference_model = model.dd.load_data(dk.pair, dk)
+                self.assertIsNotNone(inference_model)
+                model.continual_learning = False
+                self.assertIsNone(model._resolve_deployment_state(dk, dk.pair))
+                model.continual_learning = True
+                with self.assertRaises(DependencyException):
+                    model._resolve_deployment_state(dk, dk.pair)
                 params = model.get_model_params()
                 trial = create_study(direction="maximize").ask()
                 for starts in (64, 50000):
@@ -113,6 +120,257 @@ class TrainingObservationsTest(unittest.TestCase):
                     )
                 self.assertTrue(np.isfinite(score))
 
+    def test_provenance_does_not_change_other_drawers(self):
+        with tempfile.TemporaryDirectory() as temp:
+            config = model_config(temp)
+            model = ReforceXY(config=config)
+            self.addCleanup(model.close_envs)
+            drawer = FreqaiDataDrawer(Path(temp), config)
+            pair = "BTC/USDT"
+            date = pd.Timestamp("2026-01-01", tz="UTC")
+            drawer.historic_predictions[pair] = pd.DataFrame(
+                {
+                    "date_pred": [date],
+                    "&-action": [1.0],
+                    "do_predict": [1],
+                    "close_price": [100.0],
+                }
+            )
+            candles = pd.DataFrame({"date": [date]})
+            drawer.set_initial_return_values(pair, pd.DataFrame({"&-action": [99.0]}), candles)
+            self.assertNotIn("_freqai_strategies_produced", drawer.historic_predictions[pair])
+            self.assertNotIn(
+                "_freqai_strategies_produced",
+                drawer.attach_return_values_to_return_dataframe(pair, candles),
+            )
+
+    def test_native_bootstrap_append_and_restart_provenance(self):
+        pair = "BTC/USDT"
+        marker = "_freqai_strategies_produced"
+        with tempfile.TemporaryDirectory() as temp:
+            config = model_config(temp)
+            config["freqai"]["fit_live_predictions_candles"] = 3
+            model = ReforceXY(config=config)
+            model.live = True
+            self.addCleanup(model.close_envs)
+            dates = pd.date_range("2026-01-01", periods=4, freq="5min", tz="UTC")
+            strat_df = pd.DataFrame(
+                {"date": dates, "high": [101.0] * 4, "low": [99.0] * 4, "close": [100.0] * 4}
+            )
+            dk = SimpleNamespace(
+                data={"extra_returns_per_train": {}}, label_list=["&-action"], unique_class_list=[]
+            )
+            bootstrap = pd.DataFrame({"&-action": [21.0, 22.0, 23.0, 24.0]})
+            model.set_initial_historic_predictions(bootstrap, dk, pair, strat_df)
+            model.dd.set_initial_return_values(pair, bootstrap, strat_df)
+            self.assertEqual(model.dd.historic_predictions[pair][marker].tolist(), [False] * 4)
+            model.fit_live_predictions(dk, pair)
+            self.assertEqual(dk.data["labels_mean"]["&-action"], 0.0)
+            returned = model.dd.attach_return_values_to_return_dataframe(pair, strat_df)
+            self.assertNotIn(marker, returned)
+
+            # Native same-candle append overwrites a bootstrap candle. A rejected
+            # prediction is nevertheless produced and must enter the statistics.
+            model.dd.append_model_predictions(
+                pair, pd.DataFrame({"&-action": [3.0]}), np.array([0]), dk, strat_df
+            )
+            model.fit_live_predictions(dk, pair)
+            self.assertEqual(dk.data["labels_mean"]["&-action"], 3.0)
+            self.assertNotIn(
+                marker, model.dd.attach_return_values_to_return_dataframe(pair, strat_df)
+            )
+
+            future = pd.date_range(dates[-1] + pd.Timedelta(minutes=5), periods=3, freq="5min")
+            resumed = pd.concat(
+                [
+                    strat_df,
+                    pd.DataFrame(
+                        {
+                            "date": future,
+                            "high": [101.0] * 3,
+                            "low": [99.0] * 3,
+                            "close": [100.0] * 3,
+                        }
+                    ),
+                ],
+                ignore_index=True,
+            )
+            model.dd.append_model_predictions(
+                pair, pd.DataFrame({"&-action": [9.0]}), np.array([1]), dk, resumed
+            )
+            history = model.dd.historic_predictions[pair]
+            self.assertEqual(history[marker].tolist(), [False] * 3 + [True, False, False, True])
+            model.fit_live_predictions(dk, pair)
+            self.assertEqual(dk.data["labels_mean"]["&-action"], 6.0)
+            self.assertNotIn(
+                marker, model.dd.attach_return_values_to_return_dataframe(pair, resumed)
+            )
+            model.dd.append_model_predictions(
+                pair, pd.DataFrame({"&-action": [99.0]}), np.array([2]), dk, resumed
+            )
+            model.fit_live_predictions(dk, pair)
+            self.assertEqual(dk.data["labels_mean"]["&-action"], 3.0)
+            model.dd.append_model_predictions(
+                pair, pd.DataFrame({"&-action": [9.0]}), np.array([1]), dk, resumed
+            )
+            self.assertEqual(len(model.dd.historic_predictions[pair]), len(resumed))
+            model.fit_live_predictions(dk, pair)
+            self.assertEqual(dk.data["labels_mean"]["&-action"], 6.0)
+
+            model.dd.save_historic_predictions_to_disk()
+            restored = ReforceXY(config=config)
+            restored.live = True
+            self.addCleanup(restored.close_envs)
+            self.assertTrue(restored.dd.load_historic_predictions_from_disk())
+            self.assertEqual(
+                restored.dd.historic_predictions[pair][marker].tolist(), history[marker].tolist()
+            )
+            restored.fit_live_predictions(dk, pair)
+            self.assertEqual(dk.data["labels_mean"]["&-action"], 6.0)
+            self.assertNotIn(
+                marker, restored.dd.attach_return_values_to_return_dataframe(pair, resumed)
+            )
+
+    def test_partially_marked_history_restores_provable_observations(self):
+        pair = "BTC/USDT"
+        marker = "_freqai_strategies_produced"
+        with tempfile.TemporaryDirectory() as temp:
+            config = model_config(temp)
+            config["freqai"]["fit_live_predictions_candles"] = 4
+            source = ReforceXY(config=config)
+            self.addCleanup(source.close_envs)
+            dates = pd.date_range("2026-01-01", periods=4, freq="5min", tz="UTC")
+            source.dd.historic_predictions[pair] = pd.DataFrame(
+                {
+                    "date_pred": dates,
+                    "&-action": [7.0, 9.0, 99.0, 1000.0],
+                    "&-action_mean": [0.0] * 4,
+                    "&-action_std": [0.0] * 4,
+                    "close_price": [100.0] * 4,
+                    "high_price": [101.0] * 4,
+                    "low_price": [99.0] * 4,
+                    "do_predict": [1, 0, 2, 1],
+                    marker: [np.nan, 1.0, np.nan, 0.0],
+                }
+            )
+            source.dd.save_historic_predictions_to_disk()
+
+            restarted = ReforceXY(config=config)
+            restarted.live = True
+            self.addCleanup(restarted.close_envs)
+            dk = SimpleNamespace(
+                data={"extra_returns_per_train": {}},
+                label_list=["&-action"],
+                unique_class_list=[],
+                return_dataframe=pd.DataFrame(),
+            )
+            restarted.predict = lambda frame, kitchen, **kwargs: (
+                pd.DataFrame({"&-action": np.ones(len(frame))}),
+                np.ones(len(frame), dtype=int),
+            )
+            restarted.fit_live_predictions(dk, pair)
+            self.assertEqual(dk.data["labels_mean"]["&-action"], 8.0)
+
+            candles = pd.DataFrame(
+                {"date": dates, "high": [101.0] * 4, "low": [99.0] * 4, "close": [100.0] * 4}
+            )
+            restarted.build_strategy_return_arrays(candles, dk, pair, 0)
+            self.assertEqual(
+                restarted.dd.historic_predictions[pair][marker].tolist(), [True, True, False, False]
+            )
+
+            next_candle = pd.DataFrame(
+                {
+                    "date": [dates[-1] + pd.Timedelta(minutes=5)],
+                    "high": [101.0],
+                    "low": [99.0],
+                    "close": [100.0],
+                }
+            )
+            restarted.dk = SimpleNamespace(check_if_model_expired=lambda timestamp: False)
+            restarted.build_strategy_return_arrays(
+                pd.concat([candles, next_candle], ignore_index=True), dk, pair, 0
+            )
+            self.assertEqual(dk.return_dataframe["&-action_mean"].iloc[-1], 8.0)
+            self.assertEqual(dk.return_dataframe["&-action_std"].iloc[-1], 1.0)
+            self.assertNotIn(marker, dk.return_dataframe)
+
+    def test_duplicate_date_keeps_provable_observation_after_restart(self):
+        pair = "BTC/USDT"
+        marker = "_freqai_strategies_produced"
+        date = pd.Timestamp("2026-01-01", tz="UTC")
+        for statuses, markers in (([1, 0], None), ([0, 0], [True, False])):
+            with self.subTest(markers=markers), tempfile.TemporaryDirectory() as temp:
+                config = model_config(temp)
+                config["freqai"]["fit_live_predictions_candles"] = 3
+                source = ReforceXY(config=config)
+                self.addCleanup(source.close_envs)
+                history = pd.DataFrame(
+                    {
+                        "date_pred": [date, date],
+                        "&-action": [7.0, 99.0],
+                        "close_price": [100.0, 100.0],
+                        "do_predict": statuses,
+                    }
+                )
+                if markers is not None:
+                    history[marker] = markers
+                source.dd.historic_predictions[pair] = history
+                source.dd.save_historic_predictions_to_disk()
+
+                restarted = ReforceXY(config=config)
+                restarted.live = True
+                self.addCleanup(restarted.close_envs)
+                restored = restarted.dd.historic_predictions[pair]
+                self.assertEqual(restored["&-action"].tolist(), [7.0])
+                self.assertEqual(restored[marker].tolist(), [True])
+                dk = SimpleNamespace(data={}, label_list=["&-action"], unique_class_list=[])
+                restarted.fit_live_predictions(dk, pair)
+                self.assertEqual(dk.data["labels_mean"]["&-action"], 7.0)
+
+    def test_legacy_zero_status_is_ambiguous_but_expired_status_is_excluded(self):
+        pair = "BTC/USDT"
+        history = pd.DataFrame(
+            {
+                "date_pred": pd.date_range("2026-01-01", periods=4, freq="5min", tz="UTC"),
+                "&-action": [99.0, 7.0, 88.0, 77.0],
+                "close_price": [100.0] * 4,
+                "do_predict": [0, 1, 2, np.nan],
+            }
+        )
+        model = ReforceXY.__new__(ReforceXY)
+        model.live = True
+        model.freqai_info = {"fit_live_predictions_candles": 4}
+        model.dd = SimpleNamespace(historic_predictions={pair: history})
+        dk = SimpleNamespace(data={}, label_list=["&-action"], unique_class_list=[])
+        model.fit_live_predictions(dk, pair)
+        self.assertEqual(dk.data["labels_mean"]["&-action"], 7.0)
+
+    def test_live_action_statistics_resume_persisted_observations(self):
+        pair = "BTC/USDT"
+        dates = pd.date_range("2026-01-01", periods=4, freq="5min", tz="UTC")
+        history = pd.DataFrame(
+            {
+                "date_pred": dates,
+                "&-action": [1.0, 2.0, 3.0, 99.0],
+                "do_predict": [1, 1, 1, 2],
+                "close_price": [100.0, 101.0, 102.0, 103.0],
+            }
+        )
+        model = ReforceXY.__new__(ReforceXY)
+        model.live = True
+        model.freqai_info = {"fit_live_predictions_candles": 4}
+        model.dd = SimpleNamespace(
+            historic_predictions={pair: history},
+            model_return_values={pair: history.tail(1)},
+        )
+        dk = SimpleNamespace(data={}, label_list=["&-action"], unique_class_list=[])
+
+        model.fit_live_predictions(dk, pair)
+
+        self.assertEqual(dk.data["labels_mean"]["&-action"], 2.0)
+        self.assertAlmostEqual(dk.data["labels_std"]["&-action"], np.std([1.0, 2.0, 3.0]))
+
     def test_frame_validity_and_gap_reset(self):
         with tempfile.TemporaryDirectory() as temp:
             model = ReforceXY(config=model_config(temp))
index c2e573a26b2abf1f47ec0e40ec5ff04c99a89ce9..f12d205a1b4a374ad94c186ede66e93628d0d716 100644 (file)
@@ -7,7 +7,6 @@ import math
 import os
 import stat
 import time
-import warnings
 from collections import defaultdict, deque
 from collections.abc import Callable, Iterator, Mapping
 from contextlib import contextmanager, suppress
@@ -57,7 +56,6 @@ from joblib.externals import cloudpickle
 from matplotlib.lines import Line2D
 from numpy.typing import NDArray
 from optuna import Trial, TrialPruned, create_study, delete_study
-from optuna.exceptions import ExperimentalWarning
 from optuna.pruners import BasePruner, HyperbandPruner
 from optuna.samplers import BaseSampler, TPESampler
 from optuna.storages import (
@@ -103,11 +101,21 @@ def _update_eval_best_reward(callback: Any, mean_reward: float, model: Any) -> N
 
 
 _DATE_PRED_DEDUP_SENTINEL = "_freqai_strategies_date_pred_repair_patched"
+_PRODUCED_COLUMN = "_freqai_strategies_produced"
 
 
-def _recorded_prediction_mask(frame: pd.DataFrame) -> NDArray[np.bool_]:
-    """Identify recorded rows from metadata, never from prediction magnitudes."""
+def _legacy_produced_prediction_mask(frame: pd.DataFrame) -> NDArray[np.bool_]:
+    """Bootstrap has status 0; only nonzero, nonexpired statuses prove legacy production."""
+    if "do_predict" not in frame:
+        return np.zeros(len(frame), dtype=bool)
+    status = pd.to_numeric(frame["do_predict"], errors="coerce")
+    return (status.notna() & np.isfinite(status) & status.ne(0) & status.ne(2)).to_numpy(dtype=bool)
+
+
+def _recorded_prediction_rank(frame: pd.DataFrame) -> NDArray[np.int8]:
+    """Prefer provable predictions to ambiguous legacy rows and placeholders."""
     recorded = np.zeros(len(frame), dtype=bool)
+    status = None
     if "close_price" in frame:
         close = pd.to_numeric(frame["close_price"], errors="coerce")
         recorded |= (close.gt(0) & close.lt(np.inf)).fillna(False).to_numpy(dtype=bool)
@@ -118,44 +126,79 @@ def _recorded_prediction_mask(frame: pd.DataFrame) -> NDArray[np.bool_]:
             .fillna(False)
             .to_numpy(dtype=bool)
         )
-    return recorded
+        recorded &= status.ne(2).fillna(True).to_numpy(dtype=bool)
+    rank = recorded.astype(np.int8)
+    rank[_legacy_produced_prediction_mask(frame)] = 2
+    if _PRODUCED_COLUMN in frame:
+        marker = frame[_PRODUCED_COLUMN]
+        proven = marker.eq(True).fillna(False).to_numpy(dtype=bool)
+        if status is not None:
+            proven = proven & status.ne(2).fillna(True).to_numpy(dtype=bool)
+        rank[marker.notna().to_numpy(dtype=bool)] = 0
+        rank[proven] = 2
+    return rank
 
 
 def _produced_prediction_mask(frame: pd.DataFrame) -> NDArray[np.bool_]:
-    """Exclude expired-model placeholders from recorded model observations."""
-    produced = _recorded_prediction_mask(frame)
+    """Select real model outputs, excluding bootstrap and expired-model placeholders."""
+    produced = _legacy_produced_prediction_mask(frame)
+    if _PRODUCED_COLUMN in frame:
+        marker = frame[_PRODUCED_COLUMN]
+        produced = np.where(
+            marker.notna(), marker.eq(True).fillna(False).to_numpy(dtype=bool), produced
+        )
     if "do_predict" in frame:
         status = pd.to_numeric(frame["do_predict"], errors="coerce")
-        produced &= status.ne(2).fillna(True).to_numpy(dtype=bool)
+        produced = produced & status.ne(2).fillna(True).to_numpy(dtype=bool)
     return produced
 
 
-def _dedupe_historic_predictions_on_date_pred(frame: pd.DataFrame) -> pd.DataFrame:
-    """Normalize dates and retain the latest recorded row per candle, in date order.
+def _ensure_prediction_provenance(frame: pd.DataFrame) -> pd.DataFrame:
+    """Preserve explicit markers and infer only distinguishable legacy predictions."""
+    if _PRODUCED_COLUMN not in frame:
+        result = frame.copy()
+        result[_PRODUCED_COLUMN] = _legacy_produced_prediction_mask(frame)
+        return result
+    missing = frame[_PRODUCED_COLUMN].isna()
+    if not missing.any():
+        return frame
+    result = frame.copy()
+    result[_PRODUCED_COLUMN] = frame[_PRODUCED_COLUMN].where(
+        ~missing, _legacy_produced_prediction_mask(frame)
+    )
+    return result
+
 
-    Freqtrade fills downtime rows with zeros/NaNs, without a candle close or a
-    prediction status. Those rows must not replace recorded predictions, including
-    zero predictions and rejected predictions (do_predict == 0 with a candle close).
-    Indistinguishable rows use last-write-wins; label magnitudes never rank rows.
-    Invalid dates cannot match a candle and are discarded.
+def _dedupe_historic_predictions_on_date_pred(frame: pd.DataFrame, pair: str) -> pd.DataFrame:
+    """Retain the most provable prediction per candle, in date order.
+
+    Proven outputs (legacy nonzero statuses or explicit markers) outrank
+    ambiguous close-bearing rows, which outrank downtime and expired placeholders.
+    Equally ranked rows use last-write-wins; invalid dates are discarded.
     """
     date_pred = pd.to_datetime(frame["date_pred"], utc=True, errors="coerce", format="mixed")
     valid = date_pred.notna()
-    if valid.all() and date_pred.is_monotonic_increasing and date_pred.is_unique:
+    if not valid.all():
+        logger.warning(
+            "FreqAI prediction history [%s]: discarded invalid date_pred entries (count=%d)",
+            pair,
+            len(frame) - int(valid.sum()),
+        )
+    elif date_pred.is_monotonic_increasing and date_pred.is_unique:
         if date_pred.dtype == frame["date_pred"].dtype:
             return frame
         result = frame.copy()
         result["date_pred"] = date_pred
         return result
 
-    recorded = _recorded_prediction_mask(frame)
+    rank = _recorded_prediction_rank(frame)
     # Rank only metadata, without copying or coercing all prediction columns.
     order = pd.DataFrame(
-        {"date_pred": date_pred.array, "recorded": recorded, "position": np.arange(len(frame))}
+        {"date_pred": date_pred.array, "rank": rank, "position": np.arange(len(frame))}
     )
     kept = (
         order.loc[valid.to_numpy()]
-        .sort_values(["date_pred", "recorded", "position"])
+        .sort_values(["date_pred", "rank", "position"])
         .drop_duplicates("date_pred", keep="last")
     )
     result = frame.iloc[kept.index].copy()
@@ -179,11 +222,11 @@ def _align_historic_predictions(history: pd.DataFrame, dataframe: pd.DataFrame)
 
 
 def _install_date_pred_dedup_patch() -> None:
-    """Normalize persisted history and duplicate predictions before upstream writes.
+    """Repair persisted prediction dates before Freqtrade's positional writes.
 
-    Normalize before upstream positional writes and before disk repair can discard
-    a recorded duplicate. Already-clean upstream results are preserved. Recheck
-    these synchronous method contracts on Freqtrade upgrades.
+    Normalize before upstream disk repair discards a provable duplicate, and
+    align the returned candles after writes. Both model copies of this global
+    patch must have identical behavior regardless of import order.
     """
     names = (
         "set_initial_return_values",
@@ -202,7 +245,7 @@ def _install_date_pred_dedup_patch() -> None:
         )
         pending.append(not getattr(current, _DATE_PRED_DEDUP_SENTINEL, False))
         if iscoroutinefunction(original) or iscoroutinefunction(current):
-            raise RuntimeError("Repair [global]: requires synchronous drawer methods")
+            raise RuntimeError("FreqAI prediction repair requires synchronous drawer methods")
     if not any(pending):
         return
     original_set_initial, original_append, original_attach = originals[:3]
@@ -212,12 +255,12 @@ def _install_date_pred_dedup_patch() -> None:
         self, pair: str, pred_df: pd.DataFrame, dataframe: pd.DataFrame
     ) -> None:
         self.historic_predictions[pair] = _dedupe_historic_predictions_on_date_pred(
-            self.historic_predictions[pair]
+            self.historic_predictions[pair], pair
         )
         original_set_initial(
             self, pair, pred_df.reset_index(drop=True), dataframe.reset_index(drop=True)
         )
-        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair])
+        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair], pair)
         self.historic_predictions[pair] = repaired
         self.model_return_values[pair] = _align_historic_predictions(repaired, dataframe)
 
@@ -231,7 +274,7 @@ def _install_date_pred_dedup_patch() -> None:
         strat_df: pd.DataFrame,
     ) -> None:
         self.historic_predictions[pair] = _dedupe_historic_predictions_on_date_pred(
-            self.historic_predictions[pair]
+            self.historic_predictions[pair], pair
         )
         if self.historic_predictions[pair].empty and not strat_df.empty:
             # Append requires an initialized row; let upstream construct it.
@@ -242,7 +285,7 @@ def _install_date_pred_dedup_patch() -> None:
                 strat_df.tail(1).reset_index(drop=True),
             )
         original_append(self, pair, predictions, do_preds, dk, strat_df)
-        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair])
+        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair], pair)
         self.historic_predictions[pair] = repaired
         self.model_return_values[pair] = _align_historic_predictions(repaired, strat_df)
 
@@ -250,7 +293,7 @@ def _install_date_pred_dedup_patch() -> None:
     def attach_return_values_to_return_dataframe(
         self, pair: str, dataframe: pd.DataFrame
     ) -> pd.DataFrame:
-        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair])
+        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair], pair)
         self.historic_predictions[pair] = repaired
         self.model_return_values[pair] = _align_historic_predictions(repaired, dataframe)
         return original_attach(self, pair, dataframe)
@@ -265,7 +308,7 @@ def _install_date_pred_dedup_patch() -> None:
         @wraps(original_repair)
         def repair_historic_predictions(self, pair: str, pair_df: pd.DataFrame) -> pd.DataFrame:
             if "date_pred" in pair_df:
-                pair_df = _dedupe_historic_predictions_on_date_pred(pair_df)
+                pair_df = _dedupe_historic_predictions_on_date_pred(pair_df, pair)
             return original_repair(self, pair, pair_df)
 
         replacements += (repair_historic_predictions,)
@@ -304,9 +347,6 @@ class _Samplers(NamedTuple):
 
 
 matplotlib.use("Agg")
-warnings.filterwarnings("ignore", category=UserWarning)
-warnings.filterwarnings("ignore", category=FutureWarning)
-warnings.filterwarnings("ignore", category=ExperimentalWarning)
 logger = logging.getLogger(__name__)
 
 
@@ -321,10 +361,13 @@ class ReforceXY(BaseReinforcementLearningModel):
         "freqai": {
             ...
             "fit_live_predictions_candles": 0,      // Optional non-negative integer; omitted or 0 disables action statistics
-            // Live/dry-run: latest N produced observations per pair after session startup.
-            // Restart resets warmup; numeric mean/std are zero until N observations exist.
-            // Downtime and expired status 2 do not count; neutral/exit actions and recorded
-            // rejected predictions do. Backtests use the previous N rows, not the current row.
+            // Live/dry-run: latest N persisted produced observations per pair.
+            // Initial full-frame predictions are bootstrap samples, not observations.
+            // Available observations are used after the next actual prediction append
+            // and survive restarts. Downtime and expired status 2 do not count;
+            // neutral/exit actions and recorded rejected predictions do. Legacy history
+            // without provenance cannot distinguish bootstrap from rejected status 0:
+            // those ambiguous rows are excluded. Backtests use the previous N rows.
             // Numeric objects are coerced; non-finite samples are ignored. Empty finite
             // samples yield zeros; constants have zero spread; nonnumeric objects are skipped.
             // Statistics do not gate RL actions. Population standard deviation is used.
@@ -527,7 +570,7 @@ class ReforceXY(BaseReinforcementLearningModel):
             raise ValueError(
                 f"Config [global]: fit_live_predictions_candles="
                 f"{fit_live_predictions_candles!r} invalid; "
-                "must be a non-negative integer (0 disables label statistics)"
+                "must be a non-negative integer (0 disables action statistics)"
             )
         self.freqai_info["fit_live_predictions_candles"] = fit_live_predictions_candles
 
@@ -581,10 +624,77 @@ class ReforceXY(BaseReinforcementLearningModel):
         self.unset_unsupported()
         self._configure_gpu_memory()
         self._install_replay_persistence()
+        self._install_prediction_provenance()
+
+    def _install_prediction_provenance(self) -> None:
+        """Keep live observation metadata local to this model's drawer instance."""
+        drawer = self.dd
+        set_initial = drawer.set_initial_return_values
+        append = drawer.append_model_predictions
+        attach = drawer.attach_return_values_to_return_dataframe
+        load = drawer.load_historic_predictions_from_disk
+
+        def migrate() -> None:
+            for pair, history in drawer.historic_predictions.items():
+                if "date_pred" in history:
+                    drawer.historic_predictions[pair] = _ensure_prediction_provenance(history)
+
+        @wraps(load)
+        def load_with_provenance() -> bool:
+            loaded = load()
+            migrate()
+            return loaded
+
+        @wraps(set_initial)
+        def set_initial_with_provenance(
+            pair: str, pred_df: pd.DataFrame, dataframe: pd.DataFrame
+        ) -> None:
+            drawer.historic_predictions[pair] = _ensure_prediction_provenance(
+                drawer.historic_predictions[pair]
+            )
+            set_initial(pair, pred_df, dataframe)
+            drawer.historic_predictions[pair] = _ensure_prediction_provenance(
+                drawer.historic_predictions[pair]
+            )
+            drawer.model_return_values[pair] = _align_historic_predictions(
+                drawer.historic_predictions[pair], dataframe
+            )
+
+        @wraps(append)
+        def append_with_provenance(
+            pair: str,
+            predictions: pd.DataFrame,
+            do_preds: NDArray[np.int_],
+            dk: FreqaiDataKitchen,
+            strat_df: pd.DataFrame,
+        ) -> None:
+            drawer.historic_predictions[pair] = _ensure_prediction_provenance(
+                drawer.historic_predictions[pair]
+            )
+            append(pair, predictions, do_preds, dk, strat_df)
+            history = drawer.historic_predictions[pair]
+            # The native writer may return without writing an older candle.
+            if (
+                not history.empty
+                and not strat_df.empty
+                and pd.to_datetime(history["date_pred"].iloc[-1], utc=True)
+                == pd.to_datetime(strat_df["date"].iloc[-1], utc=True)
+            ):
+                history.loc[history.index[-1], _PRODUCED_COLUMN] = True
+            drawer.model_return_values[pair] = _align_historic_predictions(history, strat_df)
+
+        @wraps(attach)
+        def attach_without_provenance(pair: str, dataframe: pd.DataFrame) -> pd.DataFrame:
+            return attach(pair, dataframe).drop(columns=_PRODUCED_COLUMN, errors="ignore")
+
+        migrate()
+        drawer.load_historic_predictions_from_disk = load_with_provenance
+        drawer.set_initial_return_values = set_initial_with_provenance
+        drawer.append_model_predictions = append_with_provenance
+        drawer.attach_return_values_to_return_dataframe = attach_without_provenance
 
     def _install_replay_persistence(self) -> None:
         save_data = self.dd.save_data
-        load_data = self.dd.load_data
 
         @wraps(save_data)
         def save_with_replay(model, coin, dk):
@@ -603,21 +713,7 @@ class ReforceXY(BaseReinforcementLearningModel):
                 dk.data["reforcexy_replay"] = filename
             return save_data(model, coin, dk)
 
-        @wraps(load_data)
-        def load_with_replay(coin, dk):
-            cached = self.dd.model_dictionary.get(coin) if dk.live else None
-            model = load_data(coin, dk)
-            if model is not None and model is not cached:
-                try:
-                    self._restore_replay(model, dk.data, dk.data_path, coin)
-                except Exception:
-                    if self.dd.model_dictionary.get(coin) is model:
-                        self.dd.model_dictionary.pop(coin, None)
-                    raise
-            return model
-
         self.dd.save_data = save_with_replay
-        self.dd.load_data = load_with_replay
 
     @staticmethod
     def _restore_replay(model: Any, metadata: dict[str, Any], directory: Path, pair: str) -> None:
@@ -1241,8 +1337,11 @@ class ReforceXY(BaseReinforcementLearningModel):
                     archive.seek(0)
                     model = self.MODELCLASS.load(archive, device=cached_model.device)
                 # Off-policy experience is excluded from SB3 model archives.
-                if getattr(cached_model, "replay_buffer", None) is not None:
-                    model.replay_buffer = copy.deepcopy(cached_model.replay_buffer)
+                replay_buffer = getattr(cached_model, "replay_buffer", None)
+                if replay_buffer is not None and replay_buffer.size() > 0:
+                    model.replay_buffer = copy.deepcopy(replay_buffer)
+                elif hasattr(model, "load_replay_buffer"):
+                    self._restore_replay(model, metadata, Path(previous["data_path"]), pair)
             state = model, copy.deepcopy(feature_pipeline)
         except Exception as exc:
             raise DependencyException(
@@ -1354,7 +1453,7 @@ class ReforceXY(BaseReinforcementLearningModel):
         )
         if model is not None:
             dk.data[self._DEPLOYMENT_COORDINATE_MARKER_KEY] = self._DEPLOYMENT_COORDINATE_GENERATION
-        logger.info("Training [%s]: completed", pair)
+        logger.info("Training [%s]: model selection finished", pair)
         return model
 
     def fit(
@@ -1492,7 +1591,7 @@ class ReforceXY(BaseReinforcementLearningModel):
                 )
                 _update_eval_best_reward(self.eval_callback, float(final_mean_reward), model)
         except KeyboardInterrupt:
-            pass
+            logger.warning("Training [%s]: model fitting interrupted by user", dk.pair)
         finally:
             if self.progressbar_callback:
                 self.progressbar_callback.on_training_end()
@@ -1532,40 +1631,23 @@ class ReforceXY(BaseReinforcementLearningModel):
 
         return model
 
+    def set_initial_historic_predictions(
+        self, pred_df: pd.DataFrame, dk: FreqaiDataKitchen, pair: str, strat_df: pd.DataFrame
+    ) -> None:
+        """Keep Freqtrade's full-frame bootstrap out of live prediction statistics."""
+        super().set_initial_historic_predictions(pred_df, dk, pair, strat_df)
+        self.dd.historic_predictions[pair][_PRODUCED_COLUMN] = False
+
     def fit_live_predictions(self, dk: FreqaiDataKitchen, pair: str) -> None:
         """Compute optional action statistics from prior prediction observations."""
         fit_live_predictions_candles = self.freqai_info.get("fit_live_predictions_candles", 0)
         if not fit_live_predictions_candles:
             return
 
-        warmed_up = True
         history = self.dd.historic_predictions[pair]
         if self.live:
-            history = _dedupe_historic_predictions_on_date_pred(history)
-            if not hasattr(self, "_prediction_session_cutoffs"):
-                self._prediction_session_cutoffs: dict[str, pd.Timestamp] = {}
-            if pair not in self._prediction_session_cutoffs:
-                initial_dates = pd.to_datetime(
-                    self.dd.model_return_values[pair]["date_pred"],
-                    utc=True,
-                    errors="coerce",
-                    format="mixed",
-                )
-                self._prediction_session_cutoffs[pair] = initial_dates.max()
-            cutoff = self._prediction_session_cutoffs[pair]
-            eligible = (
-                _produced_prediction_mask(history) & history["date_pred"].gt(cutoff).to_numpy()
-            )
-            history = history.loc[eligible]
-            remaining = fit_live_predictions_candles - len(history)
-            warmed_up = remaining <= 0
-            if not warmed_up:
-                logger.warning(
-                    "Predict [%s]: fit live predictions not warmed up; "
-                    "%d more produced observations required for warmup completion",
-                    pair,
-                    remaining,
-                )
+            history = _dedupe_historic_predictions_on_date_pred(history, pair)
+            history = history.loc[_produced_prediction_mask(history)]
         pred_df = history.tail(fit_live_predictions_candles).reset_index(drop=True)
 
         dk.data["labels_mean"], dk.data["labels_std"] = {}, {}
@@ -1577,20 +1659,17 @@ class ReforceXY(BaseReinforcementLearningModel):
             pred_label = pd.to_numeric(raw_label, errors="coerce")
             if raw_label.dtype == object and pred_label.isna().all():
                 continue
-            if not warmed_up:
-                f = [0.0, 0.0]
+            values = pred_label.to_numpy(dtype=float, na_value=np.nan)
+            values = values[np.isfinite(values)]
+            if values.size == 0:
+                f = (0.0, 0.0)
             else:
-                values = pred_label.to_numpy(dtype=float, na_value=np.nan)
-                values = values[np.isfinite(values)]
-                if values.size == 0:
-                    f = (0.0, 0.0)
-                else:
-                    sample_mean = float(np.mean(values))
-                    sample_std = float(np.std(values, ddof=0))
-                    f = (
-                        sample_mean if np.isfinite(sample_mean) else 0.0,
-                        sample_std if np.isfinite(sample_std) else 0.0,
-                    )
+                sample_mean = float(np.mean(values))
+                sample_std = float(np.std(values, ddof=0))
+                f = (
+                    sample_mean if np.isfinite(sample_mean) else 0.0,
+                    sample_std if np.isfinite(sample_std) else 0.0,
+                )
             dk.data["labels_mean"][label_col], dk.data["labels_std"][label_col] = (
                 f[0],
                 f[1],
@@ -4042,10 +4121,14 @@ class MyRLEnv(Base5ActionRLEnv):
             if self._position == Positions.Neutral:
                 exit_pnl = pre_pnl
         elif action != Actions.Neutral.value:
+            try:
+                action_name = Actions(action).name
+            except ValueError:
+                action_name = "unknown"
             logger.warning(
                 "Env [%s]: invalid action=%s (%d) in position=%s at tick=%d",
                 self.id,
-                Actions(action).name,
+                action_name,
                 action,
                 self._position.name,
                 self._current_tick,
index 5a5b444bae22e99dd46c00357bb0fe840eb74632..0684d8aca17222c16e68b0865d3e9b873af434b3 100644 (file)
   "diagnostics": [
     {
       "endCharacter": 46,
-      "endLine": 1357,
+      "endLine": 1400,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"str | None\" cannot be assigned to parameter \"default\" of type \"str\" in function \"_validate_label_selection_metric\"\n  Type \"str | None\" is not assignable to type \"str\"\n    \"None\" is not assignable to \"str\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 24,
-      "startLine": 1357
+      "startLine": 1400
     },
     {
       "endCharacter": 79,
-      "endLine": 2139,
+      "endLine": 2183,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"index\" for class \"NDArray[Unknown]\"\n  Attribute \"index\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 74,
-      "startLine": 2139
+      "startLine": 2183
     },
     {
       "endCharacter": 72,
-      "endLine": 2141,
+      "endLine": 2185,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"index\" for class \"NDArray[Unknown]\"\n  Attribute \"index\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 67,
-      "startLine": 2141
+      "startLine": 2185
     },
     {
       "endCharacter": 90,
-      "endLine": 2150,
+      "endLine": 2194,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"index\" for class \"NDArray[Unknown]\"\n  Attribute \"index\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 85,
-      "startLine": 2150
+      "startLine": 2194
     },
     {
       "endCharacter": 34,
-      "endLine": 2163,
+      "endLine": 2207,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"train_features\" of type \"DataFrame\" in function \"_filter_train_by_mask\"\n  Type \"Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 2163
+      "startLine": 2207
     },
     {
       "endCharacter": 32,
-      "endLine": 2164,
+      "endLine": 2208,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"train_labels\" of type \"DataFrame\" in function \"_filter_train_by_mask\"\n  Type \"Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 2164
+      "startLine": 2208
     },
     {
       "endCharacter": 38,
-      "endLine": 2165,
+      "endLine": 2209,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"train_weights\" of type \"NDArray[floating[Any]]\" in function \"_filter_train_by_mask\"\n  Type \"Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 2165
+      "startLine": 2209
     },
     {
       "endCharacter": 59,
-      "endLine": 2168,
+      "endLine": 2212,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | None\" cannot be assigned to parameter \"train_label_weights\" of type \"NDArray[floating[Any]] | None\" in function \"_filter_train_by_mask\"\n  Type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | None\" is not assignable to type \"NDArray[floating[Any]] | None\"\n    Type \"list[Unknown]\" is not assignable to type \"NDArray[floating[Any]] | None\"\n      \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"\n      \"list[Unknown]\" is not assignable to \"None\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 40,
-      "startLine": 2168
+      "startLine": 2212
     },
     {
       "endCharacter": 34,
-      "endLine": 2202,
+      "endLine": 2246,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"features\" of type \"DataFrame\" in function \"_shuffle_split_rows\"\n  Type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 2202
+      "startLine": 2246
     },
     {
       "endCharacter": 32,
-      "endLine": 2203,
+      "endLine": 2247,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"labels\" of type \"DataFrame\" in function \"_shuffle_split_rows\"\n  Type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 2203
+      "startLine": 2247
     },
     {
       "endCharacter": 38,
-      "endLine": 2204,
+      "endLine": 2248,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"base_weights\" of type \"NDArray[floating[Any]]\" in function \"_shuffle_split_rows\"\n  Type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 2204
+      "startLine": 2248
     },
     {
       "endCharacter": 39,
-      "endLine": 2205,
+      "endLine": 2249,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown] | None\" cannot be assigned to parameter \"label_weights\" of type \"NDArray[floating[Any]] | None\" in function \"_shuffle_split_rows\"\n  Type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown] | None\" is not assignable to type \"NDArray[floating[Any]] | None\"\n    Type \"list[Unknown]\" is not assignable to type \"NDArray[floating[Any]] | None\"\n      \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"\n      \"list[Unknown]\" is not assignable to \"None\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 2205
+      "startLine": 2249
     },
     {
       "endCharacter": 37,
-      "endLine": 2212,
+      "endLine": 2256,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | DataFrame\" cannot be assigned to parameter \"features\" of type \"DataFrame\" in function \"_shuffle_split_rows\"\n  Type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | DataFrame\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 24,
-      "startLine": 2212
+      "startLine": 2256
     },
     {
       "endCharacter": 35,
-      "endLine": 2213,
+      "endLine": 2257,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | DataFrame\" cannot be assigned to parameter \"labels\" of type \"DataFrame\" in function \"_shuffle_split_rows\"\n  Type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | DataFrame\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 24,
-      "startLine": 2213
+      "startLine": 2257
     },
     {
       "endCharacter": 41,
-      "endLine": 2214,
+      "endLine": 2258,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | ndarray[_AnyShape, dtype[floating[Any]]]\" cannot be assigned to parameter \"base_weights\" of type \"NDArray[floating[Any]]\" in function \"_shuffle_split_rows\"\n  Type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | ndarray[_AnyShape, dtype[floating[Any]]]\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 24,
-      "startLine": 2214
+      "startLine": 2258
     },
     {
       "endCharacter": 42,
-      "endLine": 2215,
+      "endLine": 2259,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | ndarray[_AnyShape, dtype[floating[Any]]] | None\" cannot be assigned to parameter \"label_weights\" of type \"NDArray[floating[Any]] | None\" in function \"_shuffle_split_rows\"\n  Type \"Unknown | NDArray[Unknown] | Any | list[Unknown] | ndarray[_AnyShape, dtype[floating[Any]]] | None\" is not assignable to type \"NDArray[floating[Any]] | None\"\n    Type \"list[Unknown]\" is not assignable to type \"NDArray[floating[Any]] | None\"\n      \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"\n      \"list[Unknown]\" is not assignable to \"None\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 24,
-      "startLine": 2215
+      "startLine": 2259
     },
     {
       "endCharacter": 30,
-      "endLine": 2233,
+      "endLine": 2277,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"base_weights\" of type \"NDArray[floating[Any]]\" in function \"_compose_train_weights_with_support\"\n  Type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 2233
+      "startLine": 2277
     },
     {
       "endCharacter": 31,
-      "endLine": 2234,
+      "endLine": 2278,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown] | None\" cannot be assigned to parameter \"label_weights\" of type \"NDArray[floating[Any]] | None\" in function \"_compose_train_weights_with_support\"\n  Type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown] | None\" is not assignable to type \"NDArray[floating[Any]] | None\"\n    Type \"list[Unknown]\" is not assignable to type \"NDArray[floating[Any]] | None\"\n      \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"\n      \"list[Unknown]\" is not assignable to \"None\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 2234
+      "startLine": 2278
     },
     {
       "endCharacter": 33,
-      "endLine": 2240,
+      "endLine": 2284,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"base_weights\" of type \"NDArray[floating[Any]]\" in function \"_compose_eval_weights\"\n  Type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 16,
-      "startLine": 2240
+      "startLine": 2284
     },
     {
       "endCharacter": 34,
-      "endLine": 2241,
+      "endLine": 2285,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown] | None\" cannot be assigned to parameter \"label_weights\" of type \"NDArray[floating[Any]] | None\" in function \"_compose_eval_weights\"\n  Type \"NDArray[floating[Any]] | Unknown | NDArray[Unknown] | Any | list[Unknown] | None\" is not assignable to type \"NDArray[floating[Any]] | None\"\n    Type \"list[Unknown]\" is not assignable to type \"NDArray[floating[Any]] | None\"\n      \"list[Unknown]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"\n      \"list[Unknown]\" is not assignable to \"None\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 16,
-      "startLine": 2241
+      "startLine": 2285
     },
     {
       "endCharacter": 26,
-      "endLine": 2248,
+      "endLine": 2292,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"train_df\" of type \"DataFrame\" in function \"build_data_dictionary\"\n  Type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 2248
+      "startLine": 2292
     },
     {
       "endCharacter": 25,
-      "endLine": 2249,
+      "endLine": 2293,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"test_df\" of type \"DataFrame\" in function \"build_data_dictionary\"\n  Type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 2249
+      "startLine": 2293
     },
     {
       "endCharacter": 24,
-      "endLine": 2250,
+      "endLine": 2294,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"train_labels\" of type \"DataFrame\" in function \"build_data_dictionary\"\n  Type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 2250
+      "startLine": 2294
     },
     {
       "endCharacter": 23,
-      "endLine": 2251,
+      "endLine": 2295,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" cannot be assigned to parameter \"test_labels\" of type \"DataFrame\" in function \"build_data_dictionary\"\n  Type \"DataFrame | Unknown | NDArray[Unknown] | Any | list[Unknown]\" is not assignable to type \"DataFrame\"\n    \"list[Unknown]\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 2251
+      "startLine": 2295
     },
     {
       "endCharacter": 87,
-      "endLine": 2558,
+      "endLine": 2602,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"index\" for class \"NDArray[Unknown]\"\n  Attribute \"index\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 82,
-      "startLine": 2558
+      "startLine": 2602
     },
     {
       "endCharacter": 68,
-      "endLine": 2559,
+      "endLine": 2603,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"index\" for class \"NDArray[Unknown]\"\n  Attribute \"index\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 63,
-      "startLine": 2559
+      "startLine": 2603
     },
     {
       "endCharacter": 63,
-      "endLine": 2570,
+      "endLine": 2614,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"index\" for class \"NDArray[Unknown]\"\n  Attribute \"index\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 58,
-      "startLine": 2570
+      "startLine": 2614
     },
     {
       "endCharacter": 47,
-      "endLine": 2572,
+      "endLine": 2616,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"loc\" for class \"NDArray[Unknown]\"\n  Attribute \"loc\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 44,
-      "startLine": 2572
+      "startLine": 2616
     },
     {
       "endCharacter": 47,
-      "endLine": 2572,
+      "endLine": 2616,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"loc\" for class \"list[Unknown]\"\n  Attribute \"loc\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 44,
-      "startLine": 2572
+      "startLine": 2616
     },
     {
       "endCharacter": 43,
-      "endLine": 2573,
+      "endLine": 2617,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"loc\" for class \"NDArray[Unknown]\"\n  Attribute \"loc\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 40,
-      "startLine": 2573
+      "startLine": 2617
     },
     {
       "endCharacter": 43,
-      "endLine": 2573,
+      "endLine": 2617,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"loc\" for class \"list[Unknown]\"\n  Attribute \"loc\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 40,
-      "startLine": 2573
+      "startLine": 2617
     },
     {
       "endCharacter": 31,
-      "endLine": 2594,
+      "endLine": 2638,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"NDArray[Unknown]\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 26,
-      "startLine": 2594
+      "startLine": 2638
     },
     {
       "endCharacter": 31,
-      "endLine": 2594,
+      "endLine": 2638,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"list[Unknown]\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 26,
-      "startLine": 2594
+      "startLine": 2638
     },
     {
       "endCharacter": 60,
-      "endLine": 2594,
+      "endLine": 2638,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"NDArray[Unknown]\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 55,
-      "startLine": 2594
+      "startLine": 2638
     },
     {
       "endCharacter": 60,
-      "endLine": 2594,
+      "endLine": 2638,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"list[Unknown]\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 55,
-      "startLine": 2594
+      "startLine": 2638
     },
     {
       "endCharacter": 73,
-      "endLine": 2606,
+      "endLine": 2650,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | Any | ((value: Unknown, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int)\" cannot be assigned to parameter \"target\" of type \"Index[Any]\" in function \"get_indexer\"\n  Type \"Unknown | Any | ((value: Unknown, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int)\" is not assignable to type \"Index[Any]\"\n    \"MethodType\" is not assignable to \"Index[Any]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 53,
-      "startLine": 2606
+      "startLine": 2650
     },
     {
       "endCharacter": 73,
-      "endLine": 2606,
+      "endLine": 2650,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"index\" for class \"NDArray[Unknown]\"\n  Attribute \"index\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 68,
-      "startLine": 2606
+      "startLine": 2650
     },
     {
       "endCharacter": 83,
-      "endLine": 2607,
+      "endLine": 2651,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Unknown | Any | ((value: Unknown, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int)\" cannot be assigned to parameter \"target\" of type \"Index[Any]\" in function \"get_indexer\"\n  Type \"Unknown | Any | ((value: Unknown, start: SupportsIndex = 0, stop: SupportsIndex = sys.maxsize, /) -> int)\" is not assignable to type \"Index[Any]\"\n    \"MethodType\" is not assignable to \"Index[Any]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 58,
-      "startLine": 2607
+      "startLine": 2651
     },
     {
       "endCharacter": 83,
-      "endLine": 2607,
+      "endLine": 2651,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"index\" for class \"NDArray[Unknown]\"\n  Attribute \"index\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 78,
-      "startLine": 2607
+      "startLine": 2651
     },
     {
       "endCharacter": 46,
-      "endLine": 2842,
+      "endLine": 2886,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"Buffer\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 41,
-      "startLine": 2842
+      "startLine": 2886
     },
     {
       "endCharacter": 46,
-      "endLine": 2842,
+      "endLine": 2886,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"_NestedSequence[_SupportsArray[dtype[Any]]]\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 41,
-      "startLine": 2842
+      "startLine": 2886
     },
     {
       "endCharacter": 46,
-      "endLine": 2842,
+      "endLine": 2886,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"_NestedSequence[complex | bytes | str]\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 41,
-      "startLine": 2842
+      "startLine": 2886
     },
     {
       "endCharacter": 46,
-      "endLine": 2842,
+      "endLine": 2886,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"_SupportsArray[dtype[Any]]\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 41,
-      "startLine": 2842
+      "startLine": 2886
     },
     {
       "endCharacter": 46,
-      "endLine": 2842,
+      "endLine": 2886,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"bytes\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 41,
-      "startLine": 2842
+      "startLine": 2886
     },
     {
       "endCharacter": 46,
-      "endLine": 2842,
+      "endLine": 2886,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"complex\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 41,
-      "startLine": 2842
+      "startLine": 2886
     },
     {
       "endCharacter": 46,
-      "endLine": 2842,
+      "endLine": 2886,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Cannot access attribute \"empty\" for class \"str\"\n  Attribute \"empty\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 41,
-      "startLine": 2842
+      "startLine": 2886
     },
     {
       "endCharacter": 41,
-      "endLine": 2849,
+      "endLine": 2893,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"ArrayLike\" cannot be assigned to parameter \"features\" of type \"DataFrame\" in function \"_sanitize_pipeline_weights\"\n  Type \"ArrayLike\" is not assignable to type \"DataFrame\"\n    \"Buffer\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 16,
-      "startLine": 2849
+      "startLine": 2893
     },
     {
       "endCharacter": 39,
-      "endLine": 2898,
+      "endLine": 2942,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"ArrayLike\" cannot be assigned to parameter \"features\" of type \"DataFrame\" in function \"_sanitize_pipeline_weights\"\n  Type \"ArrayLike\" is not assignable to type \"DataFrame\"\n    \"Buffer\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 2898
+      "startLine": 2942
     },
     {
       "endCharacter": 19,
-      "endLine": 3008,
+      "endLine": 3052,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"int | Any | None\" cannot be assigned to parameter \"gap\" of type \"int\" in function \"__init__\"\n  Type \"int | Any | None\" is not assignable to type \"int\"\n    \"None\" is not assignable to \"int\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 16,
-      "startLine": 3008
+      "startLine": 3052
     },
     {
       "endCharacter": 21,
-      "endLine": 3120,
+      "endLine": 3164,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"X\" of type \"DataFrame\" in function \"hp_objective\"\n  Type \"Any | None\" is not assignable to type \"DataFrame\"\n    \"None\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 3120
+      "startLine": 3164
     },
     {
       "endCharacter": 21,
-      "endLine": 3121,
+      "endLine": 3165,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"y\" of type \"DataFrame\" in function \"hp_objective\"\n  Type \"Any | None\" is not assignable to type \"DataFrame\"\n    \"None\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 3121
+      "startLine": 3165
     },
     {
       "endCharacter": 33,
-      "endLine": 3122,
+      "endLine": 3166,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"train_weights\" of type \"NDArray[floating[Any]]\" in function \"hp_objective\"\n  Type \"Any | None\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"None\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 3122
+      "startLine": 3166
     },
     {
       "endCharacter": 32,
-      "endLine": 3123,
+      "endLine": 3167,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"X_validation\" of type \"DataFrame\" in function \"hp_objective\"\n  Type \"Any | None\" is not assignable to type \"DataFrame\"\n    \"None\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 3123
+      "startLine": 3167
     },
     {
       "endCharacter": 32,
-      "endLine": 3124,
+      "endLine": 3168,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"y_validation\" of type \"DataFrame\" in function \"hp_objective\"\n  Type \"Any | None\" is not assignable to type \"DataFrame\"\n    \"None\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 3124
+      "startLine": 3168
     },
     {
       "endCharacter": 38,
-      "endLine": 3125,
+      "endLine": 3169,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"validation_weights\" of type \"NDArray[floating[Any]]\" in function \"hp_objective\"\n  Type \"Any | None\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"None\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 20,
-      "startLine": 3125
+      "startLine": 3169
     },
     {
       "endCharacter": 24,
-      "endLine": 3146,
+      "endLine": 3190,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"X_test\" of type \"DataFrame\" in function \"make_test_set_and_weights\"\n  Type \"Any | None\" is not assignable to type \"DataFrame\"\n    \"None\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 3146
+      "startLine": 3190
     },
     {
       "endCharacter": 24,
-      "endLine": 3147,
+      "endLine": 3191,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"y_test\" of type \"DataFrame\" in function \"make_test_set_and_weights\"\n  Type \"Any | None\" is not assignable to type \"DataFrame\"\n    \"None\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 3147
+      "startLine": 3191
     },
     {
       "endCharacter": 30,
-      "endLine": 3148,
+      "endLine": 3192,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"test_weights\" of type \"NDArray[floating[Any]]\" in function \"make_test_set_and_weights\"\n  Type \"Any | None\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"None\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 3148
+      "startLine": 3192
     },
     {
       "endCharacter": 15,
-      "endLine": 3154,
+      "endLine": 3198,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"X\" of type \"DataFrame\" in function \"fit_regressor\"\n  Type \"Any | None\" is not assignable to type \"DataFrame\"\n    \"None\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 14,
-      "startLine": 3154
+      "startLine": 3198
     },
     {
       "endCharacter": 15,
-      "endLine": 3155,
+      "endLine": 3199,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"y\" of type \"DataFrame\" in function \"fit_regressor\"\n  Type \"Any | None\" is not assignable to type \"DataFrame\"\n    \"None\" is not assignable to \"DataFrame\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 14,
-      "startLine": 3155
+      "startLine": 3199
     },
     {
       "endCharacter": 39,
-      "endLine": 3156,
+      "endLine": 3200,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"train_weights\" of type \"NDArray[floating[Any]]\" in function \"fit_regressor\"\n  Type \"Any | None\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"None\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 26,
-      "startLine": 3156
+      "startLine": 3200
     },
     {
       "endCharacter": 38,
-      "endLine": 3166,
+      "endLine": 3210,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "\"columns\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 31,
-      "startLine": 3166
+      "startLine": 3210
     },
     {
       "endCharacter": 34,
-      "endLine": 3167,
+      "endLine": 3211,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "\"index\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 29,
-      "startLine": 3167
+      "startLine": 3211
     },
     {
       "endCharacter": 82,
-      "endLine": 3169,
+      "endLine": 3213,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "\"copy\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 78,
-      "startLine": 3169
+      "startLine": 3213
     },
     {
       "endCharacter": 62,
-      "endLine": 3468,
+      "endLine": 3647,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "\"get_pair_dataframe\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 44,
-      "startLine": 3468
+      "startLine": 3647
     },
     {
       "endCharacter": 68,
-      "endLine": 3848,
+      "endLine": 4027,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Type \"floating[Any] | NDArray[float64]\" is not assignable to return type \"NDArray[floating[Any]]\"\n  Type \"floating[Any] | NDArray[float64]\" is not assignable to type \"NDArray[floating[Any]]\"\n    \"floating[Any]\" is not assignable to \"ndarray[_AnyShape, dtype[floating[Any]]]\"",
       "rule": "reportReturnType",
       "severity": "error",
       "startCharacter": 15,
-      "startLine": 3844
+      "startLine": 4023
     },
     {
       "endCharacter": 13,
-      "endLine": 3877,
+      "endLine": 4056,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "No overloads for \"cdist\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 19,
-      "startLine": 3872
+      "startLine": 4051
     },
     {
       "endCharacter": 38,
-      "endLine": 3875,
+      "endLine": 4054,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"str\" cannot be assigned to parameter \"metric\" of type \"_MetricFunc\" in function \"cdist\"\n  Type \"str\" is not assignable to type \"_MetricFunc\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 23,
-      "startLine": 3875
+      "startLine": 4054
     },
     {
       "endCharacter": 9,
-      "endLine": 3980,
+      "endLine": 4159,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "No overloads for \"pdist\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 36,
-      "startLine": 3978
+      "startLine": 4157
     },
     {
       "endCharacter": 42,
-      "endLine": 3979,
+      "endLine": 4158,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"str\" cannot be assigned to parameter \"metric\" of type \"_MetricFunc\" in function \"pdist\"\n  Type \"str\" is not assignable to type \"_MetricFunc\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 27,
-      "startLine": 3979
+      "startLine": 4158
     },
     {
       "endCharacter": 9,
-      "endLine": 4057,
+      "endLine": 4236,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "No overloads for \"cdist\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 15,
-      "startLine": 4052
+      "startLine": 4231
     },
     {
       "endCharacter": 34,
-      "endLine": 4055,
+      "endLine": 4234,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"str\" cannot be assigned to parameter \"metric\" of type \"_MetricFunc\" in function \"cdist\"\n  Type \"str\" is not assignable to type \"_MetricFunc\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 19,
-      "startLine": 4055
+      "startLine": 4234
     },
     {
       "endCharacter": 99,
-      "endLine": 4246,
+      "endLine": 4425,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "No overloads for \"pdist\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 12,
-      "startLine": 4246
+      "startLine": 4425
     },
     {
       "endCharacter": 79,
-      "endLine": 4246,
+      "endLine": 4425,
       "file": "quickadapter/user_data/freqaimodels/QuickAdapterRegressorV3.py",
       "message": "Argument of type \"str\" cannot be assigned to parameter \"metric\" of type \"_MetricFunc\" in function \"pdist\"\n  Type \"str\" is not assignable to type \"_MetricFunc\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 64,
-      "startLine": 4246
+      "startLine": 4425
     },
     {
       "endCharacter": 55,
index d83bd334978b38dc7ce1eee5219506a6cd4dab55..623a22bfceb52da8299052efaa92d5ff9c22d530 100644 (file)
@@ -3,7 +3,6 @@ import json
 import logging
 import random
 import time
-import warnings
 from collections.abc import Callable
 from collections.abc import Set as AbstractSet
 from dataclasses import dataclass
@@ -33,6 +32,7 @@ from datasieve.pipeline import Pipeline
 from datasieve.transforms import SKLearnWrapper
 from freqtrade.enums import TRADE_MODES
 from freqtrade.exceptions import DependencyException
+from freqtrade.exchange import timeframe_to_seconds
 from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
 from freqtrade.freqai.data_drawer import (
     FEATURE_PIPELINE,
@@ -123,11 +123,21 @@ from Utils import (
 )
 
 _DATE_PRED_DEDUP_SENTINEL = "_freqai_strategies_date_pred_repair_patched"
+_PRODUCED_PREDICTION_COLUMN = "_freqai_strategies_produced"
 
 
-def _recorded_prediction_mask(frame: pd.DataFrame) -> NDArray[np.bool_]:
-    """Identify recorded rows from metadata, never from prediction magnitudes."""
+def _legacy_produced_prediction_mask(frame: pd.DataFrame) -> NDArray[np.bool_]:
+    """Bootstrap has status 0; only nonzero, nonexpired statuses prove legacy production."""
+    if "do_predict" not in frame:
+        return np.zeros(len(frame), dtype=bool)
+    status = pd.to_numeric(frame["do_predict"], errors="coerce")
+    return (status.notna() & np.isfinite(status) & status.ne(0) & status.ne(2)).to_numpy(dtype=bool)
+
+
+def _recorded_prediction_rank(frame: pd.DataFrame) -> NDArray[np.int8]:
+    """Prefer provable predictions to ambiguous legacy rows and placeholders."""
     recorded = np.zeros(len(frame), dtype=bool)
+    status = None
     if "close_price" in frame:
         close = pd.to_numeric(frame["close_price"], errors="coerce")
         recorded |= (close.gt(0) & close.lt(np.inf)).fillna(False).to_numpy(dtype=bool)
@@ -138,44 +148,78 @@ def _recorded_prediction_mask(frame: pd.DataFrame) -> NDArray[np.bool_]:
             .fillna(False)
             .to_numpy(dtype=bool)
         )
-    return recorded
+        recorded &= status.ne(2).fillna(True).to_numpy(dtype=bool)
+    rank = recorded.astype(np.int8)
+    rank[_legacy_produced_prediction_mask(frame)] = 2
+    if _PRODUCED_PREDICTION_COLUMN in frame:
+        marker = frame[_PRODUCED_PREDICTION_COLUMN]
+        proven = marker.eq(True).fillna(False).to_numpy(dtype=bool)
+        if status is not None:
+            proven = proven & status.ne(2).fillna(True).to_numpy(dtype=bool)
+        rank[marker.notna().to_numpy(dtype=bool)] = 0
+        rank[proven] = 2
+    return rank
 
 
 def _produced_prediction_mask(frame: pd.DataFrame) -> NDArray[np.bool_]:
-    """Exclude expired-model placeholders from recorded model observations."""
-    produced = _recorded_prediction_mask(frame)
+    """Select real model outputs, excluding bootstrap and expired-model placeholders."""
+    produced = _legacy_produced_prediction_mask(frame)
+    if _PRODUCED_PREDICTION_COLUMN in frame:
+        marker = frame[_PRODUCED_PREDICTION_COLUMN]
+        produced = np.where(
+            marker.notna(), marker.eq(True).fillna(False).to_numpy(dtype=bool), produced
+        )
     if "do_predict" in frame:
         status = pd.to_numeric(frame["do_predict"], errors="coerce")
-        produced &= status.ne(2).fillna(True).to_numpy(dtype=bool)
+        produced = produced & status.ne(2).fillna(True).to_numpy(dtype=bool)
     return produced
 
 
-def _dedupe_historic_predictions_on_date_pred(frame: pd.DataFrame) -> pd.DataFrame:
-    """Normalize dates and retain the latest recorded row per candle, in date order.
+def _ensure_produced_prediction_column(frame: pd.DataFrame) -> pd.DataFrame:
+    if _PRODUCED_PREDICTION_COLUMN not in frame:
+        result = frame.copy()
+        result[_PRODUCED_PREDICTION_COLUMN] = _legacy_produced_prediction_mask(frame)
+        return result
+    missing = frame[_PRODUCED_PREDICTION_COLUMN].isna()
+    if not missing.any():
+        return frame
+    result = frame.copy()
+    result[_PRODUCED_PREDICTION_COLUMN] = frame[_PRODUCED_PREDICTION_COLUMN].where(
+        ~missing, _legacy_produced_prediction_mask(frame)
+    )
+    return result
+
+
+def _dedupe_historic_predictions_on_date_pred(frame: pd.DataFrame, pair: str) -> pd.DataFrame:
+    """Retain the most provable prediction per candle, in date order.
 
-    Freqtrade fills downtime rows with zeros/NaNs, without a candle close or a
-    prediction status. Those rows must not replace recorded predictions, including
-    zero predictions and rejected predictions (do_predict == 0 with a candle close).
-    Indistinguishable legacy rows use last-write-wins; label magnitudes never rank rows.
-    Invalid dates cannot match a candle and are discarded.
+    Proven outputs (legacy nonzero statuses or explicit markers) outrank
+    ambiguous close-bearing rows, which outrank downtime and expired placeholders.
+    Equally ranked rows use last-write-wins; invalid dates are discarded.
     """
     date_pred = pd.to_datetime(frame["date_pred"], utc=True, errors="coerce", format="mixed")
     valid = date_pred.notna()
-    if valid.all() and date_pred.is_monotonic_increasing and date_pred.is_unique:
+    if not valid.all():
+        logger.warning(
+            "FreqAI prediction history [%s]: discarded invalid date_pred entries (count=%d)",
+            pair,
+            len(frame) - int(valid.sum()),
+        )
+    elif date_pred.is_monotonic_increasing and date_pred.is_unique:
         if date_pred.dtype == frame["date_pred"].dtype:
             return frame
         result = frame.copy()
         result["date_pred"] = date_pred
         return result
 
-    recorded = _recorded_prediction_mask(frame)
+    rank = _recorded_prediction_rank(frame)
     # Rank only metadata, without copying or coercing all prediction columns.
     order = pd.DataFrame(
-        {"date_pred": date_pred.array, "recorded": recorded, "position": np.arange(len(frame))}
+        {"date_pred": date_pred.array, "rank": rank, "position": np.arange(len(frame))}
     )
     kept = (
         order.loc[valid.to_numpy()]
-        .sort_values(["date_pred", "recorded", "position"])
+        .sort_values(["date_pred", "rank", "position"])
         .drop_duplicates("date_pred", keep="last")
     )
     result = frame.iloc[kept.index].copy()
@@ -199,12 +243,11 @@ def _align_historic_predictions(history: pd.DataFrame, dataframe: pd.DataFrame)
 
 
 def _install_date_pred_dedup_patch() -> None:
-    """Repair persisted history and duplicates produced by older Freqtrade writers.
+    """Repair persisted prediction dates before Freqtrade's positional writes.
 
-    Normalize before upstream positional writes and after legacy duplicate writes.
-    Normalize before upstream disk repair can discard a recorded duplicate.
-    Already-clean upstream results are preserved. Recheck these synchronous
-    method contracts on Freqtrade upgrades.
+    Normalize before upstream disk repair discards a provable duplicate, and
+    align the returned candles after writes. Both model copies of this global
+    patch must have identical behavior regardless of import order.
     """
     names = (
         "set_initial_return_values",
@@ -233,12 +276,12 @@ def _install_date_pred_dedup_patch() -> None:
         self, pair: str, pred_df: pd.DataFrame, dataframe: pd.DataFrame
     ) -> None:
         self.historic_predictions[pair] = _dedupe_historic_predictions_on_date_pred(
-            self.historic_predictions[pair]
+            self.historic_predictions[pair], pair
         )
         original_set_initial(
             self, pair, pred_df.reset_index(drop=True), dataframe.reset_index(drop=True)
         )
-        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair])
+        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair], pair)
         self.historic_predictions[pair] = repaired
         self.model_return_values[pair] = _align_historic_predictions(repaired, dataframe)
 
@@ -252,10 +295,10 @@ def _install_date_pred_dedup_patch() -> None:
         strat_df: pd.DataFrame,
     ) -> None:
         self.historic_predictions[pair] = _dedupe_historic_predictions_on_date_pred(
-            self.historic_predictions[pair]
+            self.historic_predictions[pair], pair
         )
         if self.historic_predictions[pair].empty and not strat_df.empty:
-            # Legacy append requires an initialized row; let upstream construct it.
+            # Append requires an initialized row; let upstream construct it.
             original_set_initial(
                 self,
                 pair,
@@ -263,7 +306,7 @@ def _install_date_pred_dedup_patch() -> None:
                 strat_df.tail(1).reset_index(drop=True),
             )
         original_append(self, pair, predictions, do_preds, dk, strat_df)
-        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair])
+        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair], pair)
         self.historic_predictions[pair] = repaired
         self.model_return_values[pair] = _align_historic_predictions(repaired, strat_df)
 
@@ -271,7 +314,7 @@ def _install_date_pred_dedup_patch() -> None:
     def attach_return_values_to_return_dataframe(
         self, pair: str, dataframe: pd.DataFrame
     ) -> pd.DataFrame:
-        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair])
+        repaired = _dedupe_historic_predictions_on_date_pred(self.historic_predictions[pair], pair)
         self.historic_predictions[pair] = repaired
         self.model_return_values[pair] = _align_historic_predictions(repaired, dataframe)
         return original_attach(self, pair, dataframe)
@@ -286,7 +329,7 @@ def _install_date_pred_dedup_patch() -> None:
         @wraps(original_repair)
         def repair_historic_predictions(self, pair: str, pair_df: pd.DataFrame) -> pd.DataFrame:
             if "date_pred" in pair_df:
-                pair_df = _dedupe_historic_predictions_on_date_pred(pair_df)
+                pair_df = _dedupe_historic_predictions_on_date_pred(pair_df, pair)
             return original_repair(self, pair, pair_df)
 
         replacements += (repair_historic_predictions,)
@@ -309,7 +352,7 @@ SelectionMethod = DistanceMethod | ClusterMethod | DensityMethod
 ValidationMode = Literal["warn", "raise", "none"]
 _VALIDATION_MODES: Final[tuple[ValidationMode, ...]] = get_args(ValidationMode)
 SplitFn = Callable[[pd.DataFrame, pd.DataFrame, "SampleWeightInputs", pd.DataFrame], dict[str, Any]]
-warnings.simplefilter(action="ignore", category=FutureWarning)
+
 
 logger = logging.getLogger(__name__)
 
@@ -322,7 +365,7 @@ def _log_known_at_none_once(pair: str, context: str) -> None:
         return
     _KNOWN_AT_NONE_LOGGED.add(key)
     logger.info(
-        f"[{pair}] {context}: No <label>_known_at_lookahead column present; "
+        f"[{pair}] {context}: No usable label/weight known-at-lookahead data; "
         "causal guards use position-based purge only (label-aware filtering disabled)"
     )
 
@@ -407,8 +450,9 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
     https://github.com/sponsors/robcaulk
     """
 
-    version = "3.13.0-rc.10"
+    version = "3.13.0-rc.11"
 
+    _CALIBRATION_START_KEY: Final[str] = "quickadapter_calibration_start"
     _DEPLOYMENT_COORDINATE_MARKER_KEY: Final[str] = "quickadapter_deployment_coordinates"
     _DEPLOYMENT_COORDINATE_GENERATION: Final[str] = "frozen-pipelines-v1"
 
@@ -758,9 +802,8 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
             logger.info(f"{context}: Removed {removed} causal-unsafe train rows")
         if not keep_mask.any():
             raise ValueError(
-                f"{context}: causal guard removed all train rows "
-                f"(pivot-sparse training window; widen fit_live_predictions_candles "
-                f"or lower label_natr_multiplier)"
+                f"{context}: causal guard removed all train rows; "
+                "no train rows satisfy the causal availability cutoff"
             )
         return (
             train_features.loc[keep_mask],
@@ -1632,6 +1675,7 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         self._optuna_hp_value: dict[str, float] = {}
         self._holdout_rmse: dict[str, float] = {}
         self._session_fitted_pairs: set[str] = set()
+        self._calibration_current_candles: dict[str, pd.Timestamp] = {}
         self._optuna_label_values: dict[str, list[float | int]] = {}
         self._optuna_hp_params: dict[str, dict[str, Any]] = {}
         self._optuna_label_params: dict[str, dict[str, Any]] = {}
@@ -2586,9 +2630,9 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
                 data_dictionary["test_weights"] = data_dictionary["test_weights"][holdout_mask]
                 if data_dictionary["test_features"].empty:
                     logger.warning(
-                        f"[{pair}] Causal purge emptied the holdout (label horizon "
-                        f">= holdout span); skipping holdout evaluation "
-                        f"(holdout_rmse=inf)"
+                        f"[{pair}] No holdout rows passed the causal availability "
+                        "cutoff at the end of the data window; skipping holdout "
+                        "evaluation (holdout_rmse=inf)"
                     )
                     data_dictionary["holdout_purged_empty"] = True
 
@@ -3262,6 +3306,155 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
         if len(self._optuna_label_incremented_pairs) >= len(self.pairs):
             self._optuna_label_incremented_pairs = []
 
+    def set_initial_historic_predictions(
+        self, pred_df: pd.DataFrame, dk: FreqaiDataKitchen, pair: str, strat_df: pd.DataFrame
+    ) -> None:
+        super().set_initial_historic_predictions(pred_df, dk, pair, strat_df)
+        if self.live:
+            self.dd.historic_predictions[pair] = _ensure_produced_prediction_column(
+                self.dd.historic_predictions[pair]
+            )
+
+    def build_strategy_return_arrays(
+        self, dataframe: pd.DataFrame, dk: FreqaiDataKitchen, pair: str, trained_timestamp: int
+    ) -> None:
+        """Record real appends and use Freqtrade's current candle for calibration."""
+        had_returns = pair in self.dd.model_return_values
+        if self.live and pair in self.dd.historic_predictions:
+            self.dd.historic_predictions[pair] = _ensure_produced_prediction_column(
+                self.dd.historic_predictions[pair]
+            )
+        if not dataframe.empty:
+            self._calibration_current_candles[pair] = pd.to_datetime(
+                dataframe["date"].iloc[-1], utc=True, errors="coerce"
+            )
+        try:
+            super().build_strategy_return_arrays(dataframe, dk, pair, trained_timestamp)
+            if self.live and had_returns:
+                history = self.dd.historic_predictions[pair]
+                current = self._calibration_current_candles.get(pair)
+                # Native append overwrites the last row on the same date, but
+                # returns early if the strategy candle predates the saved one.
+                if (
+                    current is not None
+                    and pd.notna(current)
+                    and not history.empty
+                    and pd.to_datetime(history["date_pred"].iloc[-1], utc=True) == current
+                ):
+                    history.at[history.index[-1], _PRODUCED_PREDICTION_COLUMN] = True
+                    returned = self.dd.model_return_values[pair]
+                    if not returned.empty and _PRODUCED_PREDICTION_COLUMN in returned:
+                        returned.at[returned.index[-1], _PRODUCED_PREDICTION_COLUMN] = True
+            if _PRODUCED_PREDICTION_COLUMN in dk.return_dataframe:
+                dk.return_dataframe = dk.return_dataframe.drop(columns=_PRODUCED_PREDICTION_COLUMN)
+        finally:
+            self._calibration_current_candles.pop(pair, None)
+
+    def _persist_calibration_start(self, pair: str, start: pd.Timestamp) -> None:
+        """Persist a UTC boundary without mutating Freqtrade's shared empty extras."""
+        self.dd.get_pair_dict_info(pair)
+        pair_info = self.dd.pair_dict[pair]
+        extras = pair_info.get("extras", {})
+        value = start.isoformat()
+        if extras.get(self._CALIBRATION_START_KEY) == value:
+            return
+        self.dd.pair_dict[pair] = {
+            **pair_info,
+            "extras": {**extras, self._CALIBRATION_START_KEY: value},
+        }
+        self.dd.save_drawer_to_disk()
+
+    def _calibration_decision_time(
+        self, dk: FreqaiDataKitchen, pair: str, history: pd.DataFrame
+    ) -> pd.Timestamp | None:
+        """Use the live strategy candle; retain historical fallback for direct callers."""
+        if pair in self._calibration_current_candles:
+            current = self._calibration_current_candles[pair]
+            return current if pd.notna(current) else None
+        full_df = getattr(dk, "full_df", None)
+        if isinstance(full_df, pd.DataFrame) and "date" in full_df:
+            dates = pd.to_datetime(full_df["date"], utc=True, errors="coerce", format="mixed")
+            if not dates.empty and pd.notna(dates.max()):
+                return dates.max()
+        if not history.empty:
+            latest = pd.to_datetime(
+                history["date_pred"], utc=True, errors="coerce", format="mixed"
+            ).max()
+            if pd.notna(latest):
+                return latest
+        return None
+
+    def _migrate_calibration_start(
+        self, pair: str, history: pd.DataFrame, decision_time: pd.Timestamp
+    ) -> pd.Timestamp:
+        """Resume only provable legacy outputs; an ambiguous bootstrap starts cold."""
+        produced = (
+            _produced_prediction_mask(history) & history["date_pred"].le(decision_time).to_numpy()
+        )
+        if produced.any():
+            start = history.loc[produced, "date_pred"].min()
+            logger.info("[%s] Resuming calibration from prediction history", pair)
+        else:
+            start = decision_time
+            logger.info("[%s] Starting calibration after FreqAI bootstrap history", pair)
+        self._persist_calibration_start(pair, start)
+        return start
+
+    def _calibration_history(
+        self,
+        dk: FreqaiDataKitchen,
+        pair: str,
+        history: pd.DataFrame,
+        sample_size: int,
+    ) -> pd.DataFrame:
+        """Select persisted observations for the current calibration period."""
+        decision_time = self._calibration_decision_time(dk, pair, history)
+        if decision_time is None:
+            return history.iloc[:0]
+        self.dd.get_pair_dict_info(pair)
+        raw_start = self.dd.pair_dict[pair].get("extras", {}).get(self._CALIBRATION_START_KEY)
+        start = (
+            pd.to_datetime(raw_start, utc=True, errors="coerce")
+            if isinstance(raw_start, str)
+            else pd.NaT
+        )
+        if pd.isna(start) or start > decision_time:
+            start = self._migrate_calibration_start(pair, history, decision_time)
+        elif raw_start != start.isoformat():
+            self._persist_calibration_start(pair, start)
+
+        produced = (
+            _produced_prediction_mask(history) & history["date_pred"].le(decision_time).to_numpy()
+        )
+        eligible = history.loc[produced & history["date_pred"].ge(start).to_numpy()]
+        max_gap = pd.Timedelta(seconds=sample_size * timeframe_to_seconds(self.config["timeframe"]))
+        # Include the persisted boundary itself: history may have been rebuilt
+        # after a restart, leaving no rows from the preceding warmup segment.
+        gap_starts = eligible.loc[eligible["date_pred"].diff().gt(max_gap), "date_pred"]
+        if not eligible.empty and eligible["date_pred"].iloc[0] - start > max_gap:
+            gap_starts = pd.concat([eligible["date_pred"].iloc[:1], gap_starts])
+        if not gap_starts.empty:
+            start = gap_starts.iloc[-1]
+            logger.warning(
+                "[%s] Calibration history contains a gap longer than %s; resuming warmup after the gap",
+                pair,
+                max_gap,
+            )
+            self._persist_calibration_start(pair, start)
+            eligible = history.loc[produced & history["date_pred"].ge(start).to_numpy()]
+
+        last_observation = eligible["date_pred"].max() if not eligible.empty else start
+        if decision_time - last_observation > max_gap:
+            logger.warning(
+                "[%s] Calibration observations are older than %s; starting a new warmup",
+                pair,
+                max_gap,
+            )
+            start = decision_time
+            self._persist_calibration_start(pair, start)
+            eligible = history.loc[produced & history["date_pred"].ge(start).to_numpy()]
+        return eligible
+
     def fit_live_predictions(self, dk: FreqaiDataKitchen, pair: str) -> None:
         warmed_up = True
 
@@ -3280,22 +3473,8 @@ class QuickAdapterRegressorV3(BaseRegressionModel):
 
         history = self.dd.historic_predictions[pair]
         if self.live:
-            history = _dedupe_historic_predictions_on_date_pred(history)
-            if not hasattr(self, "_prediction_session_cutoffs"):
-                self._prediction_session_cutoffs: dict[str, pd.Timestamp] = {}
-            if pair not in self._prediction_session_cutoffs:
-                initial_dates = pd.to_datetime(
-                    self.dd.model_return_values[pair]["date_pred"],
-                    utc=True,
-                    errors="coerce",
-                    format="mixed",
-                )
-                self._prediction_session_cutoffs[pair] = initial_dates.max()
-            cutoff = self._prediction_session_cutoffs[pair]
-            eligible = (
-                _produced_prediction_mask(history) & history["date_pred"].gt(cutoff).to_numpy()
-            )
-            history = history.loc[eligible]
+            history = _dedupe_historic_predictions_on_date_pred(history, pair)
+            history = self._calibration_history(dk, pair, history, fit_live_predictions_candles)
             remaining = fit_live_predictions_candles - len(history)
             warmed_up = remaining <= 0
             if not warmed_up:
index 2cc0404bdfdf5fe6413e4f91256de70456ff38db..853642396beb5adb78542b9a23c444ac46495960 100644 (file)
@@ -256,12 +256,6 @@ def get_label_column_config(
                 specificity = float(sum(1 for c in pattern if c not in "*?[]"))
             matches.append((specificity, pattern, col_config))
 
-    if columns_config and not matches:
-        logger.warning(
-            f"Column '{column_name}' did not match any pattern in columns config. "
-            f"Available patterns: {list(columns_config.keys())}"
-        )
-
     matches.sort(key=lambda x: x[0])
 
     for _, _, col_config in matches:
index f5ee4b0581942d0a6e90e491feb34a06692f38e0..6ad115346696155a906577f5781a44729c63375d 100644 (file)
@@ -218,7 +218,7 @@ class QuickAdapterV3(IStrategy):
     _ANNOTATION_LINE_OFFSET_CANDLES: Final[int] = 10
 
     def version(self) -> str:
-        return "3.13.0-rc.10"
+        return "3.13.0-rc.11"
 
     timeframe = "5m"
     timeframe_minutes = timeframe_to_minutes(timeframe)