]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(reforcexy): close persisted reward and QA boundary findings
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Fri, 2 Oct 2026 18:43:29 +0000 (20:43 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Fri, 2 Oct 2026 18:43:29 +0000 (20:43 +0200)
README.md
ReforceXY/.basedpyright/diagnostics.json
ReforceXY/.coveragerc
ReforceXY/tests/qa_support.py
ReforceXY/tests/test_model_pbrs_transitions.py
ReforceXY/tests/test_model_training_observations.py
ReforceXY/tests/test_strategy_semantics.py
ReforceXY/tests/test_suite_contract.py
ReforceXY/user_data/freqaimodels/ReforceXY.py

index fd206751ba6579b605d24732cc559f217825c1c9..e8f16cfc59ae8b05a626ad9614050d482665546e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -395,11 +395,13 @@ Freqtrade's `proposed_leverage` before clamping.
 Continual learning trains an independent copy of the deployed policy with its
 fitted feature pipeline. DQN/QRDQN deployments each persist their replay buffer;
 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
-when their objective identity matches.
+data prevents continuation but does not prevent inference. The deployment
+generation covers frozen feature coordinates and PBRS reward semantics. Older
+generations, including `chronological-frozen-pipelines-v2`, remain usable for
+inference but cannot continue training. Reset trained models or use a new
+`freqai.identifier` to migrate. Training disables `shuffle_after_split`. HPO
+studies and saved best parameters are reused only when their objective identity
+matches.
 
 Backtests continue only from a saved policy whose training cutoff precedes
 the current window's end and its last available candle boundary. A later
@@ -424,6 +426,9 @@ 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.
 
+An expired model closes only open positions for the current pair, using each
+position's own side; positions for other pairs cannot generate its exit signals.
+
 With `hold_potential_enabled=true`, ReforceXY enables `add_state_info` before
 constructing environments so training and inference use the same observations.
 Freqtrade does not support these state observations in backtesting; disable hold
@@ -465,7 +470,8 @@ Non-positive or non-finite equity produces NaN diagnostics rather than a zero
 return. These diagnostics do not change the training reward or realized capital.
 Rewards combine the fill-time base components with a potential-based shaping
 delta over the returned next observation. Termination liquidates any remaining
-position once and clears the terminal potential. `get_env_history()` returns one
+position once and sets terminal shaping to `-prev_potential`, even for tiny
+nonzero potentials. `get_env_history()` returns one
 metrics/price row per transition. Its `execution_tick` is the transition/action/fill
 key before the tick increment; its `tick` is the returned post-increment price and
 observation row (normally `execution_tick + 1`). Exit-efficiency extrema include
@@ -483,11 +489,13 @@ effective additives are disabled. It is a conservative configuration check, not
 a guarantee about fitted policies: theoretical invariance also requires an
 observable state potential, the learner's discount, and appropriate boundaries.
 
-An absent learner `gamma` uses the canonical discount. An effective `gamma: null`
-is rejected before constructing or replacing environments. Validation follows
-HPO overrides and the resumed learner's gamma, so a valid higher-priority value
-can supersede a null base value. Learner and environment receive the same
-effective discount.
+An absent configured `gamma` uses the canonical discount. The effective discount
+must be a finite Python/NumPy integer or floating scalar in `[0, 1]`, normalized
+to a Python float. Null, booleans, strings, complex values and arrays are rejected
+before constructing or replacing environments. Validation follows HPO overrides
+and the resumed learner's gamma, so a valid higher-priority value can supersede
+an invalid base value. A persisted learner with missing or invalid gamma cannot
+continue training. Learner and environment use the same effective discount.
 The corrected reward objective invalidates persisted HPO studies and best
 parameters from the previous objective identity; incompatible studies are reset
 and stale best-parameter payloads are ignored rather than warm-started.
@@ -523,8 +531,9 @@ Each strategy QA matrix entry runs its type check and canonical coverage suite.
 ReforceXY additionally runs once with `FREQAI_QA_SHUFFLE_SEED=1`. This permutes
 methods within test classes, not module or class order. `QaTestCase` restores the
 action-mask cache and the Python, NumPy and CPU Torch RNG states before and after
-each test. Tests must isolate other mutable state themselves; the alternate order
-does not prove the absence of all global-state leaks.
+each test. Lifecycle probes cover successful cases, body failures and setup or
+teardown errors. Tests must isolate other mutable state themselves; the alternate
+order does not prove the absence of all global-state leaks.
 
 The ReforceXY gate measures **only `ReforceXY/user_data`**, with branch coverage
 and non-imported namespace files included. Tests, the analytical package and
index e0c2a4f02402901fbb35610f9c9e7719b04999bb..e69d425cf99bc25e01b5589d17ca38db3a14f31b 100644 (file)
     },
     {
       "endCharacter": 22,
-      "endLine": 1017,
+      "endLine": 1025,
       "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": 1017
+      "startLine": 1025
     },
     {
       "endCharacter": 37,
-      "endLine": 1017,
+      "endLine": 1025,
       "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": 1017
+      "startLine": 1025
     },
     {
       "endCharacter": 24,
-      "endLine": 1276,
+      "endLine": 1284,
       "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": 1276
+      "startLine": 1284
     },
     {
       "endCharacter": 38,
-      "endLine": 1504,
+      "endLine": 1517,
       "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": 1504
+      "startLine": 1517
     },
     {
       "endCharacter": 36,
-      "endLine": 1508,
+      "endLine": 1521,
       "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": 1508
+      "startLine": 1521
     },
     {
       "endCharacter": 80,
-      "endLine": 1511,
+      "endLine": 1524,
       "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": 1511
+      "startLine": 1524
     },
     {
       "endCharacter": 78,
-      "endLine": 1512,
+      "endLine": 1525,
       "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": 1512
+      "startLine": 1525
     },
     {
       "endCharacter": 80,
-      "endLine": 1513,
+      "endLine": 1526,
       "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": 1513
+      "startLine": 1526
     },
     {
       "endCharacter": 56,
-      "endLine": 1599,
+      "endLine": 1610,
       "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": 1599
+      "startLine": 1610
     },
     {
       "endCharacter": 84,
-      "endLine": 1619,
+      "endLine": 1630,
       "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": 1619
+      "startLine": 1630
     },
     {
       "endCharacter": 84,
-      "endLine": 1619,
+      "endLine": 1630,
       "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": 1619
+      "startLine": 1630
     },
     {
       "endCharacter": 44,
-      "endLine": 2693,
+      "endLine": 2704,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"best_trial_params\" is possibly unbound",
       "rule": "reportPossiblyUnboundVariable",
       "severity": "error",
       "startCharacter": 27,
-      "startLine": 2693
+      "startLine": 2704
     },
     {
       "endCharacter": 12,
-      "endLine": 2713,
+      "endLine": 2724,
       "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": 2713
+      "startLine": 2724
     },
     {
       "endCharacter": 16,
-      "endLine": 2714,
+      "endLine": 2725,
       "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": 2714
+      "startLine": 2725
     },
     {
       "endCharacter": 51,
-      "endLine": 2757,
+      "endLine": 2768,
       "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": 2757
+      "startLine": 2768
     },
     {
       "endCharacter": 53,
-      "endLine": 2763,
+      "endLine": 2774,
       "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": 2763
+      "startLine": 2774
     },
     {
       "endCharacter": 22,
-      "endLine": 2826,
+      "endLine": 2837,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 15,
-      "startLine": 2826
+      "startLine": 2837
     },
     {
       "endCharacter": 96,
-      "endLine": 2828,
+      "endLine": 2839,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 89,
-      "startLine": 2828
+      "startLine": 2839
     },
     {
       "endCharacter": 23,
-      "endLine": 2831,
+      "endLine": 2842,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 16,
-      "startLine": 2831
+      "startLine": 2842
     },
     {
       "endCharacter": 98,
-      "endLine": 2833,
+      "endLine": 2844,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \"*\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 91,
-      "startLine": 2833
+      "startLine": 2844
     },
     {
       "endCharacter": 44,
-      "endLine": 2845,
+      "endLine": 2856,
       "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": 2845
+      "startLine": 2856
     },
     {
       "endCharacter": 133,
-      "endLine": 2847,
+      "endLine": 2858,
       "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": 2847
+      "startLine": 2858
     },
     {
       "endCharacter": 30,
-      "endLine": 2850,
+      "endLine": 2861,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "Operator \">\" not supported for \"None\"",
       "rule": "reportOptionalOperand",
       "severity": "error",
       "startCharacter": 15,
-      "startLine": 2850
+      "startLine": 2861
     },
     {
       "endCharacter": 54,
-      "endLine": 2916,
+      "endLine": 2927,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"is_pruned\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 45,
-      "startLine": 2916
+      "startLine": 2927
     },
     {
       "endCharacter": 67,
-      "endLine": 2919,
+      "endLine": 2930,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"use_masking\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 56,
-      "startLine": 2919
+      "startLine": 2930
     },
     {
       "endCharacter": 60,
-      "endLine": 2927,
+      "endLine": 2938,
       "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": 2927
+      "startLine": 2938
     },
     {
       "endCharacter": 84,
-      "endLine": 2927,
+      "endLine": 2938,
       "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": 2927
+      "startLine": 2938
     },
     {
       "endCharacter": 84,
-      "endLine": 2927,
+      "endLine": 2938,
       "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": 2927
+      "startLine": 2938
     },
     {
       "endCharacter": 46,
-      "endLine": 2981,
+      "endLine": 2992,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"is_pruned\" is not a known attribute of \"None\"",
       "rule": "reportOptionalMemberAccess",
       "severity": "error",
       "startCharacter": 37,
-      "startLine": 2981
+      "startLine": 2992
     },
     {
       "endCharacter": 57,
-      "endLine": 2984,
+      "endLine": 2995,
       "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": 2984
+      "startLine": 2995
     },
     {
       "endCharacter": 37,
-      "endLine": 2994,
+      "endLine": 3005,
       "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": 2994
+      "startLine": 3005
     },
     {
       "endCharacter": 36,
-      "endLine": 2999,
+      "endLine": 3010,
       "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": 2999
+      "startLine": 3010
     },
     {
       "endCharacter": 7,
-      "endLine": 3044,
+      "endLine": 3055,
       "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": 3044
+      "startLine": 3055
     },
     {
       "endCharacter": 36,
-      "endLine": 3056,
+      "endLine": 3067,
       "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": 3056
+      "startLine": 3067
     },
     {
       "endCharacter": 13,
-      "endLine": 3644,
+      "endLine": 3659,
       "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": 3644
+      "startLine": 3659
     },
     {
       "endCharacter": 26,
-      "endLine": 3665,
+      "endLine": 3680,
       "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": 3665
+      "startLine": 3680
     },
     {
       "endCharacter": 55,
-      "endLine": 3973,
+      "endLine": 3988,
       "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": 3973
+      "startLine": 3988
     },
     {
       "endCharacter": 55,
-      "endLine": 3973,
+      "endLine": 3988,
       "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": 3973
+      "startLine": 3988
     },
     {
       "endCharacter": 59,
-      "endLine": 3997,
+      "endLine": 4012,
       "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": 3997
+      "startLine": 4012
     },
     {
       "endCharacter": 59,
-      "endLine": 3997,
+      "endLine": 4012,
       "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": 3997
+      "startLine": 4012
     },
     {
       "endCharacter": 24,
-      "endLine": 4026,
+      "endLine": 4041,
       "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": 4026
+      "startLine": 4041
     },
     {
       "endCharacter": 12,
-      "endLine": 4128,
+      "endLine": 4134,
       "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": 4128
+      "startLine": 4134
     },
     {
       "endCharacter": 20,
-      "endLine": 4318,
+      "endLine": 4324,
       "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": 4318
+      "startLine": 4324
     },
     {
       "endCharacter": 40,
-      "endLine": 4467,
+      "endLine": 4473,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"Figure\" is not exported from module \"matplotlib.pyplot\"",
       "rule": "reportPrivateImportUsage",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 4467
+      "startLine": 4473
     },
     {
       "endCharacter": 54,
-      "endLine": 4970,
+      "endLine": 4976,
       "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": 4970
+      "startLine": 4976
     },
     {
       "endCharacter": 54,
-      "endLine": 4970,
+      "endLine": 4976,
       "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": 4970
+      "startLine": 4976
     },
     {
       "endCharacter": 50,
-      "endLine": 4982,
+      "endLine": 4988,
       "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": 4982
+      "startLine": 4988
     },
     {
       "endCharacter": 72,
-      "endLine": 4993,
+      "endLine": 4999,
       "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": 4993
+      "startLine": 4999
     },
     {
       "endCharacter": 72,
-      "endLine": 4993,
+      "endLine": 4999,
       "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": 4993
+      "startLine": 4999
     },
     {
       "endCharacter": 73,
-      "endLine": 5002,
+      "endLine": 5008,
       "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": 5002
+      "startLine": 5008
     },
     {
       "endCharacter": 73,
-      "endLine": 5002,
+      "endLine": 5008,
       "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": 5002
+      "startLine": 5008
     },
     {
       "endCharacter": 58,
-      "endLine": 5011,
+      "endLine": 5017,
       "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": 5011
+      "startLine": 5017
     },
     {
       "endCharacter": 58,
-      "endLine": 5011,
+      "endLine": 5017,
       "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": 5011
+      "startLine": 5017
     },
     {
       "endCharacter": 50,
-      "endLine": 5242,
+      "endLine": 5248,
       "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": 5242
+      "startLine": 5248
     },
     {
       "endCharacter": 20,
-      "endLine": 5419,
+      "endLine": 5425,
       "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": 5419
+      "startLine": 5425
     },
     {
       "endCharacter": 20,
-      "endLine": 5419,
+      "endLine": 5425,
       "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": 5419
+      "startLine": 5425
     },
     {
       "endCharacter": 59,
-      "endLine": 5424,
+      "endLine": 5430,
       "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": 5424
+      "startLine": 5430
     },
     {
       "endCharacter": 59,
-      "endLine": 5424,
+      "endLine": 5430,
       "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": 5424
+      "startLine": 5430
     },
     {
       "endCharacter": 65,
-      "endLine": 5425,
+      "endLine": 5431,
       "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": 5425
+      "startLine": 5431
     },
     {
       "endCharacter": 65,
-      "endLine": 5425,
+      "endLine": 5431,
       "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": 5425
-    },
-    {
-      "endCharacter": 57,
-      "endLine": 5426,
-      "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": 5426
-    },
-    {
-      "endCharacter": 57,
-      "endLine": 5426,
-      "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": 5426
+      "startLine": 5431
     },
     {
       "endCharacter": 63,
-      "endLine": 5428,
+      "endLine": 5434,
       "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": 5428
+      "startLine": 5434
     },
     {
       "endCharacter": 63,
-      "endLine": 5428,
+      "endLine": 5434,
       "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": 5428
+      "startLine": 5434
     },
     {
       "endCharacter": 61,
-      "endLine": 5430,
+      "endLine": 5436,
       "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": 5430
+      "startLine": 5436
     },
     {
       "endCharacter": 61,
-      "endLine": 5430,
+      "endLine": 5436,
       "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": 5430
+      "startLine": 5436
     },
     {
       "endCharacter": 67,
-      "endLine": 5431,
+      "endLine": 5437,
       "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": 5431
+      "startLine": 5437
     },
     {
       "endCharacter": 67,
-      "endLine": 5431,
+      "endLine": 5437,
       "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": 5431
+      "startLine": 5437
     },
     {
       "endCharacter": 73,
-      "endLine": 5432,
+      "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": 39,
-      "startLine": 5432
+      "startLine": 5438
     },
     {
       "endCharacter": 73,
-      "endLine": 5432,
+      "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": 39,
-      "startLine": 5432
+      "startLine": 5438
     },
     {
       "endCharacter": 61,
-      "endLine": 5433,
+      "endLine": 5439,
       "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": 5433
+      "startLine": 5439
     },
     {
       "endCharacter": 61,
-      "endLine": 5433,
+      "endLine": 5439,
       "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": 5433
+      "startLine": 5439
     },
     {
       "endCharacter": 89,
-      "endLine": 5440,
+      "endLine": 5446,
       "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": 5440
+      "startLine": 5446
     },
     {
       "endCharacter": 89,
-      "endLine": 5440,
+      "endLine": 5446,
       "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": 5440
+      "startLine": 5446
     },
     {
       "endCharacter": 83,
-      "endLine": 5441,
+      "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": 49,
-      "startLine": 5441
+      "startLine": 5447
     },
     {
       "endCharacter": 83,
-      "endLine": 5441,
+      "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": 49,
-      "startLine": 5441
-    },
-    {
-      "endCharacter": 57,
-      "endLine": 5466,
-      "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": 5466
-    },
-    {
-      "endCharacter": 57,
-      "endLine": 5466,
-      "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": 5466
+      "startLine": 5447
     },
     {
       "endCharacter": 65,
-      "endLine": 5467,
+      "endLine": 5473,
       "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": 5467
+      "startLine": 5473
     },
     {
       "endCharacter": 65,
-      "endLine": 5467,
+      "endLine": 5473,
       "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": 5467
+      "startLine": 5473
     },
     {
       "endCharacter": 67,
-      "endLine": 5469,
+      "endLine": 5475,
       "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": 5469
+      "startLine": 5475
     },
     {
       "endCharacter": 67,
-      "endLine": 5469,
+      "endLine": 5475,
       "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": 5469
+      "startLine": 5475
     },
     {
       "endCharacter": 87,
-      "endLine": 5472,
+      "endLine": 5478,
       "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": 5472
+      "startLine": 5478
     },
     {
       "endCharacter": 87,
-      "endLine": 5472,
+      "endLine": 5478,
       "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": 5472
+      "startLine": 5478
     },
     {
       "endCharacter": 93,
-      "endLine": 5473,
+      "endLine": 5479,
       "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": 5473
+      "startLine": 5479
     },
     {
       "endCharacter": 93,
-      "endLine": 5473,
+      "endLine": 5479,
       "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": 5473
+      "startLine": 5479
     },
     {
       "endCharacter": 89,
-      "endLine": 5474,
+      "endLine": 5480,
       "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": 5474
+      "startLine": 5480
     },
     {
       "endCharacter": 89,
-      "endLine": 5474,
+      "endLine": 5480,
       "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": 5474
+      "startLine": 5480
     },
     {
       "endCharacter": 89,
-      "endLine": 5475,
+      "endLine": 5481,
       "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": 5475
+      "startLine": 5481
     },
     {
       "endCharacter": 89,
-      "endLine": 5475,
+      "endLine": 5481,
       "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": 5475
+      "startLine": 5481
     },
     {
       "endCharacter": 75,
-      "endLine": 5476,
+      "endLine": 5482,
       "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": 5476
+      "startLine": 5482
     },
     {
       "endCharacter": 75,
-      "endLine": 5476,
+      "endLine": 5482,
       "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": 5476
+      "startLine": 5482
     },
     {
       "endCharacter": 33,
index a922b88fdeaff391584efe3defc16b9dbf2d42f9..4aa29b011ef1c9af3b4d0fe841019b2a521c55d8 100644 (file)
@@ -8,5 +8,5 @@ relative_files = True
 show_missing = True
 precision = 1
 include_namespace_packages = True
-# measured 2026-10-02 at 75.3% — raise only, never lower
+# measured 2026-10-02 at 75.6% — raise only, never lower
 fail_under = 70
\ No newline at end of file
index 91c942554e35895e05203576964f02591bad3d3c..4ebedf33e1efcf18d4dadc08038b8fabb390eca0 100644 (file)
@@ -68,13 +68,8 @@ _BASE_CONFIG: dict[str, Any] = {
             "check_envs": False,
             "add_state_info": False,
         },
-        # `create_sampler` resolves `auto` through optunahub.load_module, which is an
-        # outbound fetch to hub.optuna.org, so the sampler is pinned here. The pin holds
-        # only while callers go through `**overrides`: two tests assign `rl_config_optuna`
-        # wholesale and would discard it. The guarantee that survives that is in the
-        # production default — `create_sampler` falls back to "tpe" and raises for anything
-        # outside `_Samplers`, so no config value reaches the network either way. There is
-        # no `pruner` key: `create_pruner` is a staticmethod taking three ints.
+        # Use local TPE by default in QA. Explicit overrides are preserved; `auto`
+        # loads an OptunaHub sampler and may require network access.
         "rl_config_optuna": {"enabled": False, "sampler": "tpe"},
     },
 }
index db4265dfb564b8e3729f89bcd573201472c4a8ee..d49877bd16d1ae0d066293a7bc939bf2e6c62de6 100644 (file)
@@ -3,12 +3,15 @@
 import math
 import tempfile
 import unittest
+from pathlib import Path
 from types import SimpleNamespace
+from unittest import mock
 
 import numpy as np
 import pandas as pd
 from qa_support import QaTestCase, model_config
 from sb3_contrib import MaskablePPO
+from sb3_contrib.common.maskable.evaluation import evaluate_policy
 
 from ReforceXY.user_data.freqaimodels.ReforceXY import Actions, MyRLEnv, Positions, ReforceXY
 
@@ -143,6 +146,48 @@ class PbrsTransitionsTest(QaTestCase):
                         self.assertNotEqual(additive, 0.0)
                     self.assertEqual(info["pbrs_invariant"], mode == "canonical")
 
+    def test_tiny_terminal_potentials_reconcile_reward_history_and_discounted_sum(self):
+        for mode, explicit_exit in (
+            ("canonical", False),
+            ("retain_previous", False),
+            ("retain_previous", True),
+        ):
+            with self.subTest(mode=mode, explicit_exit=explicit_exit):
+                shaped = self.env(fee=0.0015, hold_potential_ratio=1e-10, exit_potential_mode=mode)
+                base = self.env(fee=0.0015, hold_potential_enabled=False, exit_potential_mode=mode)
+                shaping = []
+                for tick in range(20):
+                    action = Actions.Long_enter if tick == 0 else Actions.Neutral
+                    if explicit_exit and tick == 2:
+                        action = Actions.Long_exit
+                    _, reward, done, truncated, _ = shaped.step(action.value)
+                    _, base_reward, base_done, base_truncated, _ = base.step(action.value)
+                    self.assertEqual((done, truncated), (base_done, base_truncated))
+                    expected = 0.8 * shaped._last_next_potential - shaped._last_prev_potential
+                    self.assertEqual(shaped._last_reward_shaping, expected)
+                    self.assertAlmostEqual(reward - base_reward, expected, places=14)
+                    shaping.append(expected)
+                    if done or truncated:
+                        break
+                self.assertTrue(done or truncated)
+                self.assertGreater(abs(shaped._last_prev_potential), 0.0)
+                self.assertLess(abs(shaped._last_prev_potential), 1e-8)
+                self.assertEqual(shaped._last_next_potential, 0.0)
+                self.assertEqual(shaped._last_reward_shaping, -shaped._last_prev_potential)
+                self.assertAlmostEqual(
+                    math.fsum(0.8**t * f for t, f in enumerate(shaping)), 0.0, places=20
+                )
+                self.assertAlmostEqual(shaped._total_reward_shaping, math.fsum(shaping), places=20)
+                self.assertEqual(len(shaped.trade_history), 2)
+                history = shaped.get_env_history()
+                self.assertEqual(history.iloc[-1]["reward_shaping"], -shaped._last_prev_potential)
+                self.assertEqual(history.iloc[-1]["next_potential"], 0.0)
+
+    def test_standalone_environment_rejects_invalid_potential_discounts(self):
+        for gamma in (None, math.nan, math.inf, -math.inf, -0.1, 1.1, True, "0.8"):
+            with self.subTest(gamma=gamma), self.assertRaises(ValueError):
+                self.env(potential_gamma=gamma)
+
     def test_reset_discards_the_previous_episode_potential_and_additive_totals(self):
         env = self.env(
             exit_potential_mode="retain_previous", entry_additive_enabled=True, fee=0.0015
@@ -241,6 +286,48 @@ class DiscountResolutionTest(QaTestCase):
         with self.assertRaises(ValueError):
             model.pack_env_dict("BTC/USDT", {"gamma": None})
 
+    def test_effective_discount_requires_a_finite_real_scalar_in_unit_interval(self):
+        model = self.model(gamma=0.83)
+        for gamma in (
+            math.nan,
+            math.inf,
+            -math.inf,
+            -0.1,
+            1.1,
+            True,
+            np.bool_(False),
+            "0.8",
+            0.8 + 0j,
+            [0.8],
+            np.array(0.8),
+            np.nextafter(np.longdouble(1), np.longdouble(math.inf)),
+        ):
+            with self.subTest(gamma=gamma), self.assertRaises(ValueError):
+                model.pack_env_dict("BTC/USDT", {"gamma": gamma})
+        for gamma in (0, 1, np.int64(1), np.float32(0.79), np.float64(0.83)):
+            with self.subTest(gamma=gamma):
+                kwargs = model.pack_env_dict("BTC/USDT", {"gamma": gamma})
+                resolved = kwargs["config"]["freqai"]["rl_config"]["model_reward_parameters"][
+                    "potential_gamma"
+                ]
+                self.assertIs(type(resolved), float)
+                self.assertEqual(resolved, float(gamma))
+                prices = pd.DataFrame({"open": [100.0] * 16})
+                env = MyRLEnv(df=prices.copy(), prices=prices, **kwargs)
+                self.addCleanup(env.close)
+                learner = MaskablePPO(
+                    "MlpPolicy",
+                    env,
+                    gamma=resolved,
+                    n_steps=8,
+                    batch_size=8,
+                    n_epochs=1,
+                    device="cpu",
+                )
+                learner.learn(8)
+                self.assertEqual(learner.gamma, env._potential_gamma)
+                self.assertGreater(learner._n_updates, 0)
+
     def test_invalid_replacement_discount_does_not_close_running_environments(self):
         model = self.model(gamma=0.83)
         features = pd.DataFrame({"f": np.arange(16, dtype=float)})
@@ -250,8 +337,13 @@ class DiscountResolutionTest(QaTestCase):
         model.set_train_and_eval_environments(data, prices, prices, kitchen)
         running_train, running_eval = model.train_env, model.eval_env
         first = running_train.reset()
-        with self.assertRaises(ValueError):
-            model.set_train_and_eval_environments(data, prices, prices, kitchen, {"gamma": None})
+        for gamma in (None, math.nan, math.inf, -math.inf, -0.1, 1.1, True, "0.8"):
+            with self.subTest(gamma=gamma), self.assertRaises(ValueError):
+                model.set_train_and_eval_environments(
+                    data, prices, prices, kitchen, {"gamma": gamma}
+                )
+            self.assertIs(model.train_env, running_train)
+            self.assertIs(model.eval_env, running_eval)
         self.assertIs(model.train_env, running_train)
         self.assertIs(model.eval_env, running_eval)
         second, _, done, _ = running_train.step([Actions.Neutral.value])
@@ -272,6 +364,61 @@ class DiscountResolutionTest(QaTestCase):
         self.assertEqual(learner.gamma, env._potential_gamma)
         self.assertGreater(learner._n_updates, 0)
 
+    def test_persisted_numpy_learner_discount_overrides_finite_and_null_base(self):
+        with tempfile.TemporaryDirectory() as temp:
+            gamma = np.float32(0.79)
+            prices = pd.DataFrame({"open": [100.0] * 16})
+            for base in (0.95, None):
+                with self.subTest(base=base):
+                    owner = self.model(gamma=base)
+                    kwargs = owner.pack_env_dict("BTC/USDT", {"gamma": gamma})
+                    env = MyRLEnv(df=prices.copy(), prices=prices, **kwargs)
+                    self.addCleanup(env.close)
+                    learner = MaskablePPO(
+                        "MlpPolicy",
+                        env,
+                        gamma=gamma,
+                        n_steps=8,
+                        batch_size=8,
+                        n_epochs=1,
+                        device="cpu",
+                    )
+                    learner.learn(8)
+                    archive = Path(temp) / f"learner-{base}"
+                    learner.save(archive)
+                    loaded = MaskablePPO.load(archive)
+                    self.assertIsInstance(loaded.gamma, np.floating)
+                    previous_updates = loaded._n_updates
+                    observed = []
+
+                    def evaluate(policy, environment, observed=observed, **parameters):
+                        observed.append(
+                            (
+                                policy.gamma,
+                                policy.get_env().get_attr("_potential_gamma"),
+                                environment.get_attr("_potential_gamma"),
+                            )
+                        )
+                        return evaluate_policy(policy, environment, **parameters)
+
+                    data = {"train_features": prices.copy(), "test_features": prices.copy()}
+                    kitchen = SimpleNamespace(pair="BTC/USDT", data_path=Path(temp) / f"fit-{base}")
+                    kitchen.data_path.mkdir()
+                    with mock.patch(
+                        "ReforceXY.user_data.freqaimodels.ReforceXY.evaluate_policy",
+                        side_effect=evaluate,
+                    ):
+                        continued = owner.fit(
+                            data,
+                            kitchen,
+                            prices_train=prices,
+                            prices_test=prices,
+                            deployment_state=(loaded, None),
+                        )
+                    self.assertEqual(observed, [(gamma, [float(gamma)], [float(gamma)])])
+                    self.assertEqual(float(continued.gamma), float(gamma))
+                    self.assertGreater(loaded._n_updates, previous_updates)
+
 
 if __name__ == "__main__":
     unittest.main()
index 4748f683187d7bf99efd174717f0ef841a482e05..c57fee94f84f5d2a9ca5c8f5ac75f719917a337f 100644 (file)
@@ -1,5 +1,6 @@
 """Regression contracts for learning eligibility, replay and temporal observations."""
 
+import json
 import tempfile
 from pathlib import Path
 from types import SimpleNamespace
@@ -10,7 +11,7 @@ import pandas as pd
 from freqtrade.enums import RunMode
 from freqtrade.exceptions import DependencyException
 from freqtrade.exchange import timeframe_to_seconds
-from freqtrade.freqai.data_drawer import FreqaiDataDrawer
+from freqtrade.freqai.data_drawer import METADATA, FreqaiDataDrawer
 from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
 from optuna import TrialPruned, create_study
 from qa_support import QaTestCase, RecordingPolicy, model_config
@@ -80,6 +81,42 @@ class TrainingObservationsTest(QaTestCase):
                 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)
+                compatible, pipeline = model._resolve_deployment_state(dk, dk.pair)
+                previous_updates = compatible._n_updates
+                continued_dk = FreqaiDataKitchen(config, live=True, pair=dk.pair)
+                continued_dk.data_path = Path(temp) / "continued"
+                continued_dk.data_path.mkdir()
+                model.fit(
+                    dk.data_dictionary,
+                    continued_dk,
+                    prices_train=dk.data_dictionary["train_prices"],
+                    prices_test=dk.data_dictionary["test_prices"],
+                    deployment_state=(compatible, pipeline),
+                )
+                self.assertGreater(compatible._n_updates, previous_updates)
+                metadata_path = dk.data_path / f"{dk.model_filename}_{METADATA}.json"
+                metadata = json.loads(metadata_path.read_text())
+                current_generation = metadata[model._DEPLOYMENT_COORDINATE_MARKER_KEY]
+                metadata[model._DEPLOYMENT_COORDINATE_MARKER_KEY] = (
+                    "chronological-frozen-pipelines-v2"
+                )
+                metadata_path.write_text(json.dumps(metadata))
+                for cached in (False, True):
+                    with self.subTest(legacy_cached=cached):
+                        model.dd.model_dictionary.clear()
+                        model.dd.meta_data_dictionary.clear()
+                        if cached:
+                            inference = model.dd.load_data(dk.pair, dk)
+                            action, _ = inference.predict(
+                                np.zeros(inference.observation_space.shape, dtype=np.float32),
+                                deterministic=True,
+                            )
+                            self.assertTrue(inference.action_space.contains(action))
+                        with self.assertRaisesRegex(DependencyException, "Reset trained models"):
+                            model._resolve_deployment_state(dk, dk.pair)
+                metadata[model._DEPLOYMENT_COORDINATE_MARKER_KEY] = current_generation
+                metadata_path.write_text(json.dumps(metadata))
+                model.dd.meta_data_dictionary.clear()
                 model.dd.model_dictionary.clear()
                 (dk.data_path / dk.data["reforcexy_replay"]).unlink()
                 inference_model = model.dd.load_data(dk.pair, dk)
index 292623ca8bdd3451e1131d33c000224a4e779ebb..23ba4e66a33b1e688e049491d70943acab949e32 100644 (file)
@@ -2,7 +2,6 @@
 
 import unittest
 from datetime import datetime, timezone
-from types import SimpleNamespace
 from unittest import mock
 
 import numpy as np
@@ -270,43 +269,31 @@ class ExitSignalTest(QaTestCase):
                 self.assertFalse(_raised(result["exit_long"])[0])
                 self.assertFalse(_raised(result["exit_short"])[0])
 
-    def test_a_rejected_prediction_closes_the_open_position_of_its_own_side(self):
-        # do_predict == 2 is a rejected prediction: the open trade must be closed, and
-        # its side decides which flag is set, not the recorded action.
-        frame = _frame([1], do_predict=2)
-        for is_short, expected in ((False, "exit_long"), (True, "exit_short")):
-            with self.subTest(is_short=is_short):
-                trade = SimpleNamespace(is_short=is_short)
-                with mock.patch(
-                    "ReforceXY.user_data.strategies.RLAgentStrategy.Trade.get_trades_proxy",
-                    return_value=[trade],
-                ):
-                    result = self._strategy().populate_exit_trend(
-                        frame.copy(), {"pair": "BTC/USDT"}
-                    )
-                self.assertTrue(_raised(result[expected])[0])
-                other = "exit_short" if expected == "exit_long" else "exit_long"
-                self.assertFalse(_raised(result[other])[0])
-
-    def test_a_rejected_prediction_ignores_closed_positions(self):
-        # Reached through freqtrade's own backtest collections rather than by asserting
-        # the call arguments, so a strategy that filtered differently would still pass.
-        # With `is_open` dropped the proxy returns closed trades too, and this goes red.
-        closed = SimpleNamespace(pair="BTC/USDT", is_open=False, is_short=False)
-        opened = SimpleNamespace(pair="BTC/USDT", is_open=True, is_short=True)
-        for open_trades, expected_short in (([], False), ([opened], True)):
-            with self.subTest(open_short=expected_short):
+    def test_an_expired_model_closes_only_open_positions_for_the_current_pair(self):
+        closed = LocalTrade(pair="BTC/USDT", is_open=False, is_short=False)
+        long = LocalTrade(pair="BTC/USDT", is_open=True, is_short=False)
+        short = LocalTrade(pair="BTC/USDT", is_open=True, is_short=True)
+        unrelated_long = LocalTrade(pair="ETH/USDT", is_open=True, is_short=False)
+        unrelated_short = LocalTrade(pair="ETH/USDT", is_open=True, is_short=True)
+        for open_trades, expected in (
+            ([], (False, False)),
+            ([short], (False, True)),
+            ([long, unrelated_short], (True, False)),
+            ([short, unrelated_long], (False, True)),
+            ([unrelated_long], (False, False)),
+            ([unrelated_short], (False, False)),
+        ):
+            with self.subTest(expected=expected, trades=open_trades):
                 with (
                     mock.patch.object(Trade, "use_db", False),
                     mock.patch.object(LocalTrade, "bt_trades", [closed]),
                     mock.patch.object(LocalTrade, "bt_trades_open", open_trades),
                 ):
                     result = self._strategy().populate_exit_trend(
-                        _frame([Actions.Neutral.value], do_predict=2),
-                        {"pair": "BTC/USDT"},
+                        _frame([Actions.Neutral.value], do_predict=2), {"pair": "BTC/USDT"}
                     )
-                self.assertEqual([False], _raised(result["exit_long"]))
-                self.assertEqual([expected_short], _raised(result["exit_short"]))
+                self.assertEqual([expected[0]], _raised(result["exit_long"]))
+                self.assertEqual([expected[1]], _raised(result["exit_short"]))
 
     def test_a_row_without_a_prediction_raises_no_exit(self):
         result = self._strategy().populate_exit_trend(
@@ -319,16 +306,6 @@ class ExitSignalTest(QaTestCase):
         self.assertEqual([False, False], _raised(result["exit_long"]))
         self.assertEqual([False, False], _raised(result["exit_short"]))
 
-    def test_a_rejected_prediction_with_no_open_trade_raises_no_exit(self):
-        frame = _frame([1], do_predict=2)
-        with mock.patch(
-            "ReforceXY.user_data.strategies.RLAgentStrategy.Trade.get_trades_proxy",
-            return_value=[],
-        ):
-            result = self._strategy().populate_exit_trend(frame, {"pair": "BTC/USDT"})
-        self.assertFalse(_raised(result["exit_long"])[0])
-        self.assertFalse(_raised(result["exit_short"])[0])
-
 
 class FeatureEngineeringTest(QaTestCase):
     def _strategy(self):
index 146bbb5c0aceddf9a1d0e8fd722a399d3967b6ab..e9b039f1bbc807af69d682e117641d336748bd60 100644 (file)
@@ -159,35 +159,82 @@ class SuiteContractTest(unittest.TestCase):
         finally:
             reseed(previous)
 
-    def test_the_restore_puts_the_process_globals_back(self):
+    def test_the_lifecycle_restores_globals_before_body_and_after_all_outcomes(self):
         cache = _PRODUCTION.ReforceXY._action_masks_cache
-        # Dirty every global the way a test that trains a model would.
-        cache[("dirty", 0.5)] = None
-        random.seed(1234)
-        np.random.seed(1234)
-        torch.manual_seed(1234)
 
-        self.addCleanup(QaTestCase._restore_process_globals)
-        QaTestCase._restore_process_globals()
+        def snapshot():
+            return (
+                dict(cache),
+                random.getstate(),
+                np.random.get_state(),
+                torch.random.get_rng_state().clone(),
+            )
 
-        self.assertEqual(
-            dict(_ACTION_MASKS_CACHE_AT_IMPORT),
-            dict(cache),
-            "_action_masks_cache was not restored",
-        )
-        self.assertEqual(_RANDOM_STATE_AT_IMPORT, random.getstate())
-        self.assertEqual(
-            _NUMPY_STATE_AT_IMPORT[1].tolist(),
-            np.random.get_state()[1].tolist(),
-            "the numpy bit generator was not rewound",
-        )
-        self.assertEqual(
-            int(_NUMPY_STATE_AT_IMPORT[2]),
-            int(np.random.get_state()[2]),
-            "the numpy stream position advanced and was not restored",
-        )
-        if _TORCH_STATE_AT_IMPORT is not None:
-            self.assertTrue(torch.equal(torch.random.get_rng_state(), _TORCH_STATE_AT_IMPORT))
+        entry = snapshot()
+
+        def restore_entry():
+            cache.clear()
+            cache.update(entry[0])
+            random.setstate(entry[1])
+            np.random.set_state(entry[2])
+            torch.random.set_rng_state(entry[3])
+
+        self.addCleanup(restore_entry)
+
+        def dirty():
+            cache[("lifecycle-probe", 0.5)] = None
+            random.seed(1234)
+            np.random.seed(1234)
+            torch.manual_seed(1234)
+
+        def assert_baseline(state):
+            self.assertEqual(state[0], dict(_ACTION_MASKS_CACHE_AT_IMPORT))
+            self.assertEqual(state[1], _RANDOM_STATE_AT_IMPORT)
+            self.assertEqual(state[2][0], _NUMPY_STATE_AT_IMPORT[0])
+            np.testing.assert_array_equal(state[2][1], _NUMPY_STATE_AT_IMPORT[1])
+            self.assertEqual(state[2][2:], _NUMPY_STATE_AT_IMPORT[2:])
+            if _TORCH_STATE_AT_IMPORT is not None:
+                self.assertTrue(torch.equal(state[3], _TORCH_STATE_AT_IMPORT))
+
+        class Probe(QaTestCase):
+            def __init__(self, outcome):
+                super().__init__()
+                self.outcome = outcome
+                self.observed = []
+
+            def setUp(self):
+                super().setUp()
+                self.observed.append(snapshot())
+                if self.outcome == "setup-error":
+                    dirty()
+                    raise RuntimeError("intentional setup-error")
+
+            def runTest(self):
+                self.observed.append(snapshot())
+                dirty()
+                if self.outcome == "body-failure":
+                    self.fail("intentional body-failure")
+
+            def tearDown(self):
+                if self.outcome == "teardown-error":
+                    dirty()
+                    raise RuntimeError("intentional teardown-error")
+
+        for outcome in ("success", "body-failure", "setup-error", "teardown-error"):
+            with self.subTest(outcome=outcome):
+                dirty()
+                result = unittest.TestResult()
+                probe = Probe(outcome)
+                probe.run(result)
+                self.assertEqual(result.testsRun, 1)
+                self.assertEqual(len(probe.observed), 1 if outcome == "setup-error" else 2)
+                for state in probe.observed:
+                    assert_baseline(state)
+                assert_baseline(snapshot())
+                self.assertEqual(len(result.failures), int(outcome == "body-failure"))
+                self.assertEqual(len(result.errors), int(outcome.endswith("error")))
+                for _, traceback in result.failures + result.errors:
+                    self.assertIn(f"intentional {outcome}", traceback)
 
 
 if __name__ == "__main__":
index ffdcfcb6476042ea93c39f77d39e8d787451219f..b6d1ab2c2d0b30857dfc04bd1c155b2b9fab61b0 100644 (file)
@@ -433,7 +433,7 @@ class ReforceXY(BaseReinforcementLearningModel):
     _LOG_2: Final[float] = math.log(2.0)
 
     _DEPLOYMENT_COORDINATE_MARKER_KEY: Final[str] = "reforcexy_deployment_coordinates"
-    _DEPLOYMENT_COORDINATE_GENERATION: Final[str] = "chronological-frozen-pipelines-v2"
+    _DEPLOYMENT_COORDINATE_GENERATION: Final[str] = "chronological-frozen-pipelines-pbrs-v3"
 
     DEFAULT_BASE_FACTOR: Final[float] = 100.0
     DEFAULT_DISCOUNT_GAMMA: Final[float] = 0.95
@@ -549,7 +549,7 @@ class ReforceXY(BaseReinforcementLearningModel):
     _BEST_PARAMS_LOCK_FILENAME: Final[str] = ".hyperopt-best-params.lock"
     # Bump on objective changes that make persisted trials or best params incompatible.
     _OPTUNA_OBJECTIVE_IDENTITY: Final[str] = (
-        "terminal-liquidation-risk-normalized-trained-policy-v4"
+        "terminal-liquidation-risk-normalized-trained-policy-v5"
     )
     _PPO_N_STEPS: Final[tuple[int, ...]] = (512, 1024, 2048, 4096)
     _PPO_N_STEPS_MIN: Final[int] = min(_PPO_N_STEPS)
@@ -952,13 +952,21 @@ class ReforceXY(BaseReinforcementLearningModel):
 
     @staticmethod
     def _resolve_discount_gamma(model_params: Mapping[str, Any]) -> float:
-        """Resolve discount after precedence, rejecting explicit null instead of defaulting."""
+        """Validate the winning Python/NumPy real scalar discount and normalize to float."""
         gamma = model_params.get("gamma", ReforceXY.DEFAULT_DISCOUNT_GAMMA)
         if gamma is None:
             raise ValueError(
                 "Config [global]: effective model_training_parameters.gamma must not be null"
             )
-        return cast("float", gamma)
+        if (
+            isinstance(gamma, (bool, np.bool_))
+            or not isinstance(gamma, (int, float, np.integer, np.floating))
+            or not 0 <= gamma <= 1
+        ):
+            raise ValueError(
+                "Config [global]: effective discount gamma must be a finite real scalar in [0, 1]"
+            )
+        return float(gamma)
 
     def set_train_and_eval_environments(
         self,
@@ -1315,6 +1323,7 @@ class ReforceXY(BaseReinforcementLearningModel):
             cached = self.dd.meta_data_dictionary.get(pair, {})
             metadata = cached.get(METADATA)
             feature_pipeline = cached.get(FEATURE_PIPELINE)
+            prefix: Path | None = None
             if model is None or metadata is None or feature_pipeline is None:
                 # The current kitchen points to the new window, not deployed files.
                 previous_dk = copy.copy(dk)
@@ -1324,21 +1333,23 @@ class ReforceXY(BaseReinforcementLearningModel):
                 if metadata is None:
                     with Path(f"{prefix}_{METADATA}.json").open("r") as fp:
                         metadata = rapidjson.load(fp, number_mode=METADATA_NUMBER_MODE)
-                if feature_pipeline is None:
-                    with Path(f"{prefix}_{FEATURE_PIPELINE}.pkl").open("rb") as fp:
-                        feature_pipeline = cloudpickle.load(fp)
-                if model is None:
-                    model = self.MODELCLASS.load(Path(f"{prefix}_model"))
-                    self._restore_replay(model, metadata, previous_dk.data_path, pair)
             if (
                 metadata.get(self._DEPLOYMENT_COORDINATE_MARKER_KEY)
                 != self._DEPLOYMENT_COORDINATE_GENERATION
             ):
                 raise ValueError(
-                    "persisted deployment coordinate generation is missing or incompatible"
+                    "persisted deployment coordinate/reward generation is missing or incompatible"
                 )
+            if feature_pipeline is None:
+                assert prefix is not None
+                with Path(f"{prefix}_{FEATURE_PIPELINE}.pkl").open("rb") as fp:
+                    feature_pipeline = cloudpickle.load(fp)
+            if model is None:
+                assert prefix is not None
+                model = self.MODELCLASS.load(Path(f"{prefix}_model"))
             if not isinstance(model, self.MODELCLASS):
                 raise ValueError("persisted policy type differs from the configured model")
+            ReforceXY._resolve_discount_gamma({"gamma": getattr(model, "gamma", None)})
             for name, expected, actual in (
                 ("features", dk.training_features_list, metadata["training_features_list"]),
                 ("labels", dk.label_list, metadata["label_list"]),
@@ -1350,6 +1361,8 @@ class ReforceXY(BaseReinforcementLearningModel):
             ):
                 if list(expected) != list(actual):
                     raise ValueError(f"persisted {name} differ in names or order")
+            if cached_model is None:
+                self._restore_replay(model, metadata, Path(previous["data_path"]), pair)
             if cached_model is not None:
                 # SB3 archives preserve policy/optimizer state but exclude envs and loggers.
                 with BytesIO() as archive:
@@ -1570,9 +1583,7 @@ class ReforceXY(BaseReinforcementLearningModel):
         model = deployment_state[0] if deployment_state is not None else None
         effective_params = dict(model_params)
         if model is not None:
-            learner_gamma = getattr(model, "gamma", None)
-            if isinstance(learner_gamma, (int, float)) and np.isfinite(learner_gamma):
-                effective_params["gamma"] = float(learner_gamma)
+            effective_params["gamma"] = getattr(model, "gamma", None)
         effective_params["gamma"] = ReforceXY._resolve_discount_gamma(effective_params)
         # Preserve raw prices and the resumed learner's discount in its environments.
         try:
@@ -3087,8 +3098,12 @@ class MyRLEnv(Base5ActionRLEnv):
             )
         )
         # === PBRS COMMON PARAMETERS ===
-        self._potential_gamma = float(
-            model_reward_parameters.get("potential_gamma", ReforceXY.DEFAULT_DISCOUNT_GAMMA)
+        self._potential_gamma = ReforceXY._resolve_discount_gamma(
+            {
+                "gamma": model_reward_parameters.get(
+                    "potential_gamma", ReforceXY.DEFAULT_DISCOUNT_GAMMA
+                )
+            }
         )
         if np.isclose(self._potential_gamma, 0.0):
             logger.warning(
@@ -4111,15 +4126,6 @@ class MyRLEnv(Base5ActionRLEnv):
         computed_reward_shaping = self._last_reward_shaping
         terminal_reward_shaping = -prev_potential
         reward_shaping_delta = terminal_reward_shaping - computed_reward_shaping
-        last_next_potential = self._last_next_potential
-
-        if np.isclose(computed_reward_shaping, terminal_reward_shaping) and np.isclose(
-            last_next_potential, 0.0
-        ):
-            self._last_potential = 0.0
-            self._last_next_potential = 0.0
-            return reward
-
         self._last_potential = 0.0
         self._last_next_potential = 0.0
         self._last_reward_shaping = terminal_reward_shaping
@@ -5424,7 +5430,7 @@ def convert_optuna_params_to_model_params(
             {
                 "n_steps": int(optuna_params.get("n_steps")),
                 "batch_size": int(optuna_params.get("batch_size")),
-                "gamma": float(optuna_params.get("gamma")),
+                "gamma": ReforceXY._resolve_discount_gamma(optuna_params),
                 "learning_rate": lr,
                 "ent_coef": float(optuna_params.get("ent_coef")),
                 "clip_range": cr,
@@ -5464,7 +5470,7 @@ def convert_optuna_params_to_model_params(
 
         model_params.update(
             {
-                "gamma": float(optuna_params.get("gamma")),
+                "gamma": ReforceXY._resolve_discount_gamma(optuna_params),
                 "batch_size": int(optuna_params.get("batch_size")),
                 "learning_rate": lr,
                 "buffer_size": int(optuna_params.get("buffer_size")),