]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(reforcexy): correct audit findings (#270)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Sun, 20 Sep 2026 14:31:38 +0000 (16:31 +0200)
committerGitHub <noreply@github.com>
Sun, 20 Sep 2026 14:31:38 +0000 (16:31 +0200)
* fix(reforcexy): correct audit findings

* fix(reforcexy): preserve invalid PBRS provenance

* fix(reforcexy): warn on invalid exit modes

ReforceXY/.basedpyright/diagnostics.json
ReforceXY/reward_space_analysis/README.md
ReforceXY/reward_space_analysis/reward_space_analysis.py
ReforceXY/reward_space_analysis/test_reward_space_analysis_cli.py
ReforceXY/reward_space_analysis/tests/README.md
ReforceXY/reward_space_analysis/tests/cli/test_cli_params_and_csv.py
ReforceXY/reward_space_analysis/tests/pbrs/test_pbrs.py
ReforceXY/reward_space_analysis/tests/statistics/test_statistics.py
ReforceXY/tests/test_portfolio_returns.py
ReforceXY/user_data/freqaimodels/ReforceXY.py

index 509c3a81641d0ca054897ddbf5de5672764a614f..85cab2b5fd002dcfcc00e763ee1ab8a756256f7d 100644 (file)
   "diagnostics": [
     {
       "endCharacter": 58,
-      "endLine": 324,
+      "endLine": 323,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Type \"RewardParamValue\" is not assignable to declared type \"bool | None\"\n  Type \"RewardParamValue\" is not assignable to type \"bool | None\"\n    Type \"float\" is not assignable to type \"bool | None\"\n      \"float\" is not assignable to \"bool\"\n      \"float\" is not assignable to \"None\"",
       "rule": "reportAssignmentType",
       "severity": "error",
       "startCharacter": 18,
-      "startLine": 324
+      "startLine": 323
     },
     {
       "endCharacter": 92,
-      "endLine": 551,
+      "endLine": 571,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"RewardParamValue\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"RewardParamValue\" 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": 22,
-      "startLine": 551
+      "startLine": 571
     },
     {
       "endCharacter": 92,
-      "endLine": 551,
+      "endLine": 571,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"RewardParamValue\" cannot be assigned to parameter \"x\" of type \"ConvertibleToInt\" in function \"__new__\"\n  Type \"RewardParamValue\" 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": 22,
-      "startLine": 551
+      "startLine": 571
     },
     {
       "endCharacter": 5,
-      "endLine": 2048,
+      "endLine": 2095,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "No overloads for \"cut\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 17,
-      "startLine": 2043
+      "startLine": 2090
     },
     {
       "endCharacter": 21,
-      "endLine": 2045,
+      "endLine": 2092,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"_Array1D[Any]\" cannot be assigned to parameter \"bins\" of type \"int | Sequence[float] | Index[int] | Index[float] | IntervalIndex[Interval[Any]] | Series[Any]\" in function \"cut\"\n  Type \"_Array1D[Any]\" is not assignable to type \"int | Sequence[float] | Index[int] | Index[float] | IntervalIndex[Interval[Any]] | Series[Any]\"\n    \"ndarray[tuple[int], dtype[Any]]\" is not assignable to \"int\"\n    \"ndarray[tuple[int], dtype[Any]]\" is not assignable to \"Sequence[float]\"\n    \"ndarray[tuple[int], dtype[Any]]\" is not assignable to \"Index[int]\"\n    \"ndarray[tuple[int], dtype[Any]]\" is not assignable to \"Index[float]\"\n    \"ndarray[tuple[int], dtype[Any]]\" is not assignable to \"IntervalIndex[Interval[Any]]\"\n    \"ndarray[tuple[int], dtype[Any]]\" is not assignable to \"Series[Any]\"",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 13,
-      "startLine": 2045
+      "startLine": 2092
     },
     {
       "endCharacter": 5,
-      "endLine": 2070,
+      "endLine": 2117,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Type \"dict[bytes, bytes] | dict[str, RewardParamValue]\" is not assignable to declared type \"RewardParams\"\n  Type \"dict[bytes, bytes] | dict[str, RewardParamValue]\" is not assignable to type \"RewardParams\"\n    \"dict[bytes, bytes]\" is not assignable to \"dict[str, RewardParamValue]\"\n      Type parameter \"_KT@dict\" is invariant, but \"bytes\" is not the same as \"str\"\n      Type parameter \"_VT@dict\" is invariant, but \"bytes\" is not the same as \"RewardParamValue\"",
       "rule": "reportAssignmentType",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 2066
+      "startLine": 2113
     },
     {
       "endCharacter": 43,
-      "endLine": 2067,
+      "endLine": 2114,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "No overloads for \"__init__\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 8,
-      "startLine": 2067
+      "startLine": 2114
     },
     {
       "endCharacter": 42,
-      "endLine": 2067,
+      "endLine": 2114,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"iterable\" of type \"Iterable[list[bytes]]\" in function \"__init__\"\n  Type \"Any | None\" is not assignable to type \"Iterable[list[bytes]]\"\n    \"None\" is incompatible with protocol \"Iterable[list[bytes]]\"\n      \"__iter__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 13,
-      "startLine": 2067
+      "startLine": 2114
     },
     {
       "endCharacter": 60,
-      "endLine": 2327,
+      "endLine": 2374,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"importances_mean\" for class \"dict[Unknown, Bunch]\"\n  Attribute \"importances_mean\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 44,
-      "startLine": 2327
+      "startLine": 2374
     },
     {
       "endCharacter": 58,
-      "endLine": 2328,
+      "endLine": 2375,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"importances_std\" for class \"dict[Unknown, Bunch]\"\n  Attribute \"importances_std\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 43,
-      "startLine": 2328
+      "startLine": 2375
     },
     {
       "endCharacter": 88,
-      "endLine": 2347,
+      "endLine": 2394,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"columns\" for class \"NDArray[Unknown]\"\n  Attribute \"columns\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 81,
-      "startLine": 2347
+      "startLine": 2394
     },
     {
       "endCharacter": 88,
-      "endLine": 2347,
+      "endLine": 2394,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"columns\" for class \"list[Unknown]\"\n  Attribute \"columns\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 81,
-      "startLine": 2347
+      "startLine": 2394
     },
     {
       "endCharacter": 17,
-      "endLine": 2356,
+      "endLine": 2403,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Object of type \"None\" cannot be called",
       "rule": "reportOptionalCall",
       "severity": "error",
       "startCharacter": 28,
-      "startLine": 2350
+      "startLine": 2397
     },
     {
       "endCharacter": 40,
-      "endLine": 2575,
+      "endLine": 2632,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"Any | object | NAType | Unknown\" cannot be assigned to parameter \"arg1\" of type \"SupportsRichComparisonT@min\" in function \"min\"\n  Type \"Any | object | NAType | Unknown\" is not assignable to type \"SupportsRichComparison\"\n    Type \"Any | object | NAType | Unknown\" is not assignable to type \"SupportsRichComparison\"\n      Type \"object\" is not assignable to type \"SupportsRichComparison\"\n        \"object\" is incompatible with protocol \"SupportsDunderLT[Any]\"\n          \"__lt__\" is not present\n        \"object\" is incompatible with protocol \"SupportsDunderGT[Any]\"\n          \"__gt__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 22,
-      "startLine": 2575
+      "startLine": 2632
     },
     {
       "endCharacter": 38,
-      "endLine": 2575,
+      "endLine": 2632,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"min\" for class \"ExtensionArray\"\n  Attribute \"min\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 35,
-      "startLine": 2575
+      "startLine": 2632
     },
     {
       "endCharacter": 59,
-      "endLine": 2575,
+      "endLine": 2632,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"Any | object | NAType | Unknown\" cannot be assigned to parameter \"arg2\" of type \"SupportsRichComparisonT@min\" in function \"min\"\n  Type \"Any | object | NAType | Unknown\" is not assignable to type \"SupportsRichComparison\"\n    Type \"Any | object | NAType | Unknown\" is not assignable to type \"SupportsRichComparison\"\n      Type \"object\" is not assignable to type \"SupportsRichComparison\"\n        \"object\" is incompatible with protocol \"SupportsDunderLT[Any]\"\n          \"__lt__\" is not present\n        \"object\" is incompatible with protocol \"SupportsDunderGT[Any]\"\n          \"__gt__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 42,
-      "startLine": 2575
+      "startLine": 2632
     },
     {
       "endCharacter": 57,
-      "endLine": 2575,
+      "endLine": 2632,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"min\" for class \"ExtensionArray\"\n  Attribute \"min\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 54,
-      "startLine": 2575
+      "startLine": 2632
     },
     {
       "endCharacter": 40,
-      "endLine": 2576,
+      "endLine": 2633,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"Any | object | NAType | Unknown\" cannot be assigned to parameter \"arg1\" of type \"SupportsRichComparisonT@max\" in function \"max\"\n  Type \"Any | object | NAType | Unknown\" is not assignable to type \"SupportsRichComparison\"\n    Type \"Any | object | NAType | Unknown\" is not assignable to type \"SupportsRichComparison\"\n      Type \"object\" is not assignable to type \"SupportsRichComparison\"\n        \"object\" is incompatible with protocol \"SupportsDunderLT[Any]\"\n          \"__lt__\" is not present\n        \"object\" is incompatible with protocol \"SupportsDunderGT[Any]\"\n          \"__gt__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 22,
-      "startLine": 2576
+      "startLine": 2633
     },
     {
       "endCharacter": 38,
-      "endLine": 2576,
+      "endLine": 2633,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"max\" for class \"ExtensionArray\"\n  Attribute \"max\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 35,
-      "startLine": 2576
+      "startLine": 2633
     },
     {
       "endCharacter": 59,
-      "endLine": 2576,
+      "endLine": 2633,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"Any | object | NAType | Unknown\" cannot be assigned to parameter \"arg2\" of type \"SupportsRichComparisonT@max\" in function \"max\"\n  Type \"Any | object | NAType | Unknown\" is not assignable to type \"SupportsRichComparison\"\n    Type \"Any | object | NAType | Unknown\" is not assignable to type \"SupportsRichComparison\"\n      Type \"object\" is not assignable to type \"SupportsRichComparison\"\n        \"object\" is incompatible with protocol \"SupportsDunderLT[Any]\"\n          \"__lt__\" is not present\n        \"object\" is incompatible with protocol \"SupportsDunderGT[Any]\"\n          \"__gt__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 42,
-      "startLine": 2576
+      "startLine": 2633
     },
     {
       "endCharacter": 57,
-      "endLine": 2576,
+      "endLine": 2633,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"max\" for class \"ExtensionArray\"\n  Attribute \"max\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 54,
-      "startLine": 2576
+      "startLine": 2633
     },
     {
       "endCharacter": 76,
-      "endLine": 2592,
+      "endLine": 2649,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "No overloads for \"histogram\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 24,
-      "startLine": 2592
+      "startLine": 2649
     },
     {
       "endCharacter": 49,
-      "endLine": 2592,
+      "endLine": 2649,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" cannot be assigned to parameter \"a\" of type \"_ArrayLikeComplex_co\" in function \"histogram\"\n  Type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" is not assignable to type \"_ArrayLikeComplex_co\"\n    Type \"ExtensionArray\" is not assignable to type \"_ArrayLikeComplex_co\"\n      \"ExtensionArray\" is incompatible with protocol \"_SupportsArray[dtype[numpy.bool[builtins.bool] | number[Any, Any]]]\"\n        \"__array__\" is not present\n      \"ExtensionArray\" is incompatible with protocol \"_NestedSequence[_SupportsArray[dtype[numpy.bool[builtins.bool] | number[Any, Any]]]]\"\n        \"__reversed__\" is not present\n        \"count\" is not present\n        \"index\" is not present\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 37,
-      "startLine": 2592
+      "startLine": 2649
     },
     {
       "endCharacter": 74,
-      "endLine": 2593,
+      "endLine": 2650,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "No overloads for \"histogram\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 23,
-      "startLine": 2593
+      "startLine": 2650
     },
     {
       "endCharacter": 47,
-      "endLine": 2593,
+      "endLine": 2650,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" cannot be assigned to parameter \"a\" of type \"_ArrayLikeComplex_co\" in function \"histogram\"\n  Type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" is not assignable to type \"_ArrayLikeComplex_co\"\n    Type \"ExtensionArray\" is not assignable to type \"_ArrayLikeComplex_co\"\n      \"ExtensionArray\" is incompatible with protocol \"_SupportsArray[dtype[numpy.bool[builtins.bool] | number[Any, Any]]]\"\n        \"__array__\" is not present\n      \"ExtensionArray\" is incompatible with protocol \"_NestedSequence[_SupportsArray[dtype[numpy.bool[builtins.bool] | number[Any, Any]]]]\"\n        \"__reversed__\" is not present\n        \"count\" is not present\n        \"index\" is not present\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 36,
-      "startLine": 2593
+      "startLine": 2650
     },
     {
       "endCharacter": 51,
-      "endLine": 2608,
+      "endLine": 2678,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" cannot be assigned to parameter \"u_values\" of type \"ToFloatND\" in function \"wasserstein_distance\"\n  Type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" is not assignable to type \"ToFloatND\"\n    Type \"ExtensionArray\" is not assignable to type \"ToFloatND\"\n      \"ExtensionArray\" is incompatible with protocol \"_CanArrayND[floating_co]\"\n        \"__array__\" is not present\n      \"ExtensionArray\" is incompatible with protocol \"SequenceND[py_float | _CanArray[floating_co]]\"\n        \"__reversed__\" is not present\n        \"count\" is not present\n        \"index\" is not present\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 39,
-      "startLine": 2608
+      "startLine": 2678
     },
     {
       "endCharacter": 64,
-      "endLine": 2608,
+      "endLine": 2678,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" cannot be assigned to parameter \"v_values\" of type \"ToFloatND\" in function \"wasserstein_distance\"\n  Type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" is not assignable to type \"ToFloatND\"\n    Type \"ExtensionArray\" is not assignable to type \"ToFloatND\"\n      \"ExtensionArray\" is incompatible with protocol \"_CanArrayND[floating_co]\"\n        \"__array__\" is not present\n      \"ExtensionArray\" is incompatible with protocol \"SequenceND[py_float | _CanArray[floating_co]]\"\n        \"__reversed__\" is not present\n        \"count\" is not present\n        \"index\" is not present\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 53,
-      "startLine": 2608
+      "startLine": 2678
     },
     {
       "endCharacter": 68,
-      "endLine": 2611,
+      "endLine": 2681,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "No overloads for \"ks_2samp\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 27,
-      "startLine": 2611
+      "startLine": 2681
     },
     {
       "endCharacter": 54,
-      "endLine": 2611,
+      "endLine": 2681,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" cannot be assigned to parameter \"data1\" of type \"ToFloatND\" in function \"ks_2samp\"\n  Type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" is not assignable to type \"ToFloatND\"\n    Type \"ExtensionArray\" is not assignable to type \"ToFloatND\"\n      \"ExtensionArray\" is incompatible with protocol \"_CanArrayND[floating_co]\"\n        \"__array__\" is not present\n      \"ExtensionArray\" is incompatible with protocol \"SequenceND[py_float | _CanArray[floating_co]]\"\n        \"__reversed__\" is not present\n        \"count\" is not present\n        \"index\" is not present\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 42,
-      "startLine": 2611
+      "startLine": 2681
     },
     {
       "endCharacter": 67,
-      "endLine": 2611,
+      "endLine": 2681,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" cannot be assigned to parameter \"data2\" of type \"ToFloatND\" in function \"ks_2samp\"\n  Type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" is not assignable to type \"ToFloatND\"\n    Type \"ExtensionArray\" is not assignable to type \"ToFloatND\"\n      \"ExtensionArray\" is incompatible with protocol \"_CanArrayND[floating_co]\"\n        \"__array__\" is not present\n      \"ExtensionArray\" is incompatible with protocol \"SequenceND[py_float | _CanArray[floating_co]]\"\n        \"__reversed__\" is not present\n        \"count\" is not present\n        \"index\" is not present\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 56,
-      "startLine": 2611
+      "startLine": 2681
     },
     {
       "endCharacter": 29,
-      "endLine": 2911,
+      "endLine": 2981,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "No overloads for \"ptp\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 11,
-      "startLine": 2911
+      "startLine": 2981
     },
     {
       "endCharacter": 28,
-      "endLine": 2911,
+      "endLine": 2981,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" cannot be assigned to parameter \"a\" of type \"_ArrayLikeNumeric_co\" in function \"ptp\"\n  Type \"np_1darray[Any] | ExtensionArray | Categorical[object]\" is not assignable to type \"_ArrayLikeNumeric_co\"\n    Type \"ExtensionArray\" is not assignable to type \"_ArrayLikeNumeric_co\"\n      \"ExtensionArray\" is incompatible with protocol \"_SupportsArray[dtype[number[Any, Any] | numpy.bool[builtins.bool] | object_ | timedelta64[Any]]]\"\n        \"__array__\" is not present\n      \"ExtensionArray\" is incompatible with protocol \"_NestedSequence[_SupportsArray[dtype[number[Any, Any] | numpy.bool[builtins.bool] | object_ | timedelta64[Any]]]]\"\n        \"__reversed__\" is not present\n        \"count\" is not present\n        \"index\" is not present\n  ...",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 18,
-      "startLine": 2911
+      "startLine": 2981
     },
     {
       "endCharacter": 65,
-      "endLine": 2920,
+      "endLine": 2990,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"mean\" for class \"Categorical[object]\"\n  Attribute \"mean\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 61,
-      "startLine": 2920
+      "startLine": 2990
     },
     {
       "endCharacter": 65,
-      "endLine": 2920,
+      "endLine": 2990,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Cannot access attribute \"mean\" for class \"ExtensionArray\"\n  Attribute \"mean\" is unknown",
       "rule": "reportAttributeAccessIssue",
       "severity": "error",
       "startCharacter": 61,
-      "startLine": 2920
+      "startLine": 2990
     },
     {
       "endCharacter": 5,
-      "endLine": 3811,
+      "endLine": 3886,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Type \"dict[bytes, bytes] | dict[str, RewardParamValue]\" is not assignable to declared type \"RewardParams\"\n  Type \"dict[bytes, bytes] | dict[str, RewardParamValue]\" is not assignable to type \"RewardParams\"\n    \"dict[bytes, bytes]\" is not assignable to \"dict[str, RewardParamValue]\"\n      Type parameter \"_KT@dict\" is invariant, but \"bytes\" is not the same as \"str\"\n      Type parameter \"_VT@dict\" is invariant, but \"bytes\" is not the same as \"RewardParamValue\"",
       "rule": "reportAssignmentType",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 3807
+      "startLine": 3882
     },
     {
       "endCharacter": 43,
-      "endLine": 3808,
+      "endLine": 3883,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "No overloads for \"__init__\" match the provided arguments",
       "rule": "reportCallIssue",
       "severity": "error",
       "startCharacter": 8,
-      "startLine": 3808
+      "startLine": 3883
     },
     {
       "endCharacter": 42,
-      "endLine": 3808,
+      "endLine": 3883,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Argument of type \"Any | None\" cannot be assigned to parameter \"iterable\" of type \"Iterable[list[bytes]]\" in function \"__init__\"\n  Type \"Any | None\" is not assignable to type \"Iterable[list[bytes]]\"\n    \"None\" is incompatible with protocol \"Iterable[list[bytes]]\"\n      \"__iter__\" is not present",
       "rule": "reportArgumentType",
       "severity": "error",
       "startCharacter": 13,
-      "startLine": 3808
+      "startLine": 3883
     },
     {
       "endCharacter": 22,
     },
     {
       "endCharacter": 40,
-      "endLine": 4355,
+      "endLine": 4356,
       "file": "ReforceXY/user_data/freqaimodels/ReforceXY.py",
       "message": "\"Figure\" is not exported from module \"matplotlib.pyplot\"",
       "rule": "reportPrivateImportUsage",
       "severity": "error",
       "startCharacter": 34,
-      "startLine": 4355
+      "startLine": 4356
     },
     {
       "endCharacter": 54,
-      "endLine": 4858,
+      "endLine": 4859,
       "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": 4858
+      "startLine": 4859
     },
     {
       "endCharacter": 54,
-      "endLine": 4858,
+      "endLine": 4859,
       "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": 4858
+      "startLine": 4859
     },
     {
       "endCharacter": 50,
-      "endLine": 4870,
+      "endLine": 4871,
       "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": 4870
+      "startLine": 4871
     },
     {
       "endCharacter": 72,
-      "endLine": 4881,
+      "endLine": 4882,
       "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": 4881
+      "startLine": 4882
     },
     {
       "endCharacter": 72,
-      "endLine": 4881,
+      "endLine": 4882,
       "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": 4881
+      "startLine": 4882
     },
     {
       "endCharacter": 73,
-      "endLine": 4890,
+      "endLine": 4891,
       "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": 4890
+      "startLine": 4891
     },
     {
       "endCharacter": 73,
-      "endLine": 4890,
+      "endLine": 4891,
       "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": 4890
+      "startLine": 4891
     },
     {
       "endCharacter": 58,
-      "endLine": 4899,
+      "endLine": 4900,
       "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": 4899
+      "startLine": 4900
     },
     {
       "endCharacter": 58,
-      "endLine": 4899,
+      "endLine": 4900,
       "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": 4899
+      "startLine": 4900
     },
     {
       "endCharacter": 50,
-      "endLine": 5130,
+      "endLine": 5131,
       "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": 5130
+      "startLine": 5131
     },
     {
       "endCharacter": 20,
-      "endLine": 5307,
+      "endLine": 5308,
       "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": 5307
+      "startLine": 5308
     },
     {
       "endCharacter": 20,
-      "endLine": 5307,
+      "endLine": 5308,
       "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": 5307
+      "startLine": 5308
     },
     {
       "endCharacter": 59,
-      "endLine": 5312,
+      "endLine": 5313,
       "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": 5312
+      "startLine": 5313
     },
     {
       "endCharacter": 59,
-      "endLine": 5312,
+      "endLine": 5313,
       "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": 5312
+      "startLine": 5313
     },
     {
       "endCharacter": 65,
-      "endLine": 5313,
+      "endLine": 5314,
       "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": 5313
+      "startLine": 5314
     },
     {
       "endCharacter": 65,
-      "endLine": 5313,
+      "endLine": 5314,
       "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": 5313
+      "startLine": 5314
     },
     {
       "endCharacter": 57,
-      "endLine": 5314,
+      "endLine": 5315,
       "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": 5314
+      "startLine": 5315
     },
     {
       "endCharacter": 57,
-      "endLine": 5314,
+      "endLine": 5315,
       "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": 5314
+      "startLine": 5315
     },
     {
       "endCharacter": 63,
-      "endLine": 5316,
+      "endLine": 5317,
       "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": 5316
+      "startLine": 5317
     },
     {
       "endCharacter": 63,
-      "endLine": 5316,
+      "endLine": 5317,
       "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": 5316
+      "startLine": 5317
     },
     {
       "endCharacter": 61,
-      "endLine": 5318,
+      "endLine": 5319,
       "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": 5318
+      "startLine": 5319
     },
     {
       "endCharacter": 61,
-      "endLine": 5318,
+      "endLine": 5319,
       "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": 5318
+      "startLine": 5319
     },
     {
       "endCharacter": 67,
-      "endLine": 5319,
+      "endLine": 5320,
       "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": 5319
+      "startLine": 5320
     },
     {
       "endCharacter": 67,
-      "endLine": 5319,
+      "endLine": 5320,
       "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": 5319
+      "startLine": 5320
     },
     {
       "endCharacter": 73,
-      "endLine": 5320,
+      "endLine": 5321,
       "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": 5320
+      "startLine": 5321
     },
     {
       "endCharacter": 73,
-      "endLine": 5320,
+      "endLine": 5321,
       "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": 5320
+      "startLine": 5321
     },
     {
       "endCharacter": 61,
-      "endLine": 5321,
+      "endLine": 5322,
       "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": 5321
+      "startLine": 5322
     },
     {
       "endCharacter": 61,
-      "endLine": 5321,
+      "endLine": 5322,
       "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": 5321
+      "startLine": 5322
     },
     {
       "endCharacter": 89,
-      "endLine": 5328,
+      "endLine": 5329,
       "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": 5328
+      "startLine": 5329
     },
     {
       "endCharacter": 89,
-      "endLine": 5328,
+      "endLine": 5329,
       "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": 5328
+      "startLine": 5329
     },
     {
       "endCharacter": 83,
-      "endLine": 5329,
+      "endLine": 5330,
       "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": 5329
+      "startLine": 5330
     },
     {
       "endCharacter": 83,
-      "endLine": 5329,
+      "endLine": 5330,
       "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": 5329
+      "startLine": 5330
     },
     {
       "endCharacter": 57,
-      "endLine": 5354,
+      "endLine": 5355,
       "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": 5354
+      "startLine": 5355
     },
     {
       "endCharacter": 57,
-      "endLine": 5354,
+      "endLine": 5355,
       "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": 5354
+      "startLine": 5355
     },
     {
       "endCharacter": 65,
-      "endLine": 5355,
+      "endLine": 5356,
       "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": 5355
+      "startLine": 5356
     },
     {
       "endCharacter": 65,
-      "endLine": 5355,
+      "endLine": 5356,
       "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": 5355
+      "startLine": 5356
     },
     {
       "endCharacter": 67,
-      "endLine": 5357,
+      "endLine": 5358,
       "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": 5357
+      "startLine": 5358
     },
     {
       "endCharacter": 67,
-      "endLine": 5357,
+      "endLine": 5358,
       "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": 5357
+      "startLine": 5358
     },
     {
       "endCharacter": 87,
-      "endLine": 5360,
+      "endLine": 5361,
       "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": 5360
+      "startLine": 5361
     },
     {
       "endCharacter": 87,
-      "endLine": 5360,
+      "endLine": 5361,
       "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": 5360
+      "startLine": 5361
     },
     {
       "endCharacter": 93,
-      "endLine": 5361,
+      "endLine": 5362,
       "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": 5361
+      "startLine": 5362
     },
     {
       "endCharacter": 93,
-      "endLine": 5361,
+      "endLine": 5362,
       "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": 5361
+      "startLine": 5362
     },
     {
       "endCharacter": 89,
-      "endLine": 5362,
+      "endLine": 5363,
       "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": 5362
+      "startLine": 5363
     },
     {
       "endCharacter": 89,
-      "endLine": 5362,
+      "endLine": 5363,
       "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": 5362
+      "startLine": 5363
     },
     {
       "endCharacter": 89,
-      "endLine": 5363,
+      "endLine": 5364,
       "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": 5363
+      "startLine": 5364
     },
     {
       "endCharacter": 89,
-      "endLine": 5363,
+      "endLine": 5364,
       "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": 5363
+      "startLine": 5364
     },
     {
       "endCharacter": 75,
-      "endLine": 5364,
+      "endLine": 5365,
       "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": 5364
+      "startLine": 5365
     },
     {
       "endCharacter": 75,
-      "endLine": 5364,
+      "endLine": 5365,
       "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": 5364
+      "startLine": 5365
     },
     {
       "endCharacter": 33,
index e4290dbb571c311b2b8c4f505cdb60443e4ffcb2..28ccd426752933a87da08ceef66fb96262684139 100644 (file)
@@ -197,8 +197,10 @@ be overridden via `--params`.
 - **`--check_invariants`** (bool, default: true) – Enable runtime invariant
   checks (diagnostics become advisory if disabled). Toggle rarely; disabling may
   hide reward drift or invariance violations.
-- **`--strict_validation`** (flag, default: true) – Enforce parameter bounds and
-  finite checks; raises instead of silent clamp/discard when enabled.
+- **`--strict_validation`** (flag, default: true) – Enforce parameter bounds,
+  finite checks, and exact `exit_potential_mode` choices; raises when enabled.
+  Relaxed API validation clamps bounds and canonicalizes an invalid exit mode with
+  a recorded adjustment.
 - **`--strict_diagnostics`** (flag, default: false) – Raise on extreme distribution
   moments instead of warning. In both modes, constants retain exact mean/std,
   while undefined higher moments, normality tests and Q-Q fits remain N/A.
@@ -334,12 +336,17 @@ where `kernel_function` depends on `exit_attenuation_mode`. See
 | `entry_fee_rate`         | 0.0       | Entry fee rate (`price · (1 + fee)`) |
 | `exit_fee_rate`          | 0.0       | Exit fee rate (`price / (1 + fee)`)  |
 
+Direct `calculate_reward()` calls warn on an invalid exit mode before applying the
+canonical fallback. `simulate_samples()` emits this warning once per simulation.
+
 PBRS verification is evidence-based, never a raw shaping sum: complete ordered
 episodes (contiguous `transition_index`, single terminal), the local identity
 `reward_shaping = gamma * next_potential - prev_potential`, temporal potential
 continuity, and the discounted terminal boundary residual must all hold with
 sufficient ordered data; otherwise the report classifies the observed PBRS as
 "Not verified" even in canonical configuration.
+Imported or API-created data with an invalid `exit_potential_mode` is reported with
+its original value and cannot receive canonical verification.
 
 In canonical mode, the entry/exit additive terms are suppressed even if the
 corresponding `*_additive_enabled` flags are set.
index f75b1c1cb3c7ff8f27f06ae22f6883c7e99c4575..960d072ea90131fd523034b277ddd0994877234c 100644 (file)
@@ -23,7 +23,6 @@ from typing import TYPE_CHECKING, Any, Final, Literal
 import numpy as np
 import pandas as pd
 from scipy import stats
-from scipy.spatial.distance import jensenshannon
 from scipy.stats import entropy, probplot
 
 if TYPE_CHECKING:
@@ -271,7 +270,7 @@ class RewardDiagnosticsWarning(RuntimeWarning):
 
 def _warn_unknown_mode(
     mode_type: str,
-    provided_value: str,
+    provided_value: Any,
     valid_values: Iterable[str],
     fallback_value: str,
     stacklevel: int = 2,
@@ -344,18 +343,39 @@ def _resolve_additive_enablement(
         (entry_additive_effective, exit_additive_effective, additives_suppressed)
     """
 
+    resolved_mode = _resolve_exit_potential_mode(exit_potential_mode)
     entry_additive_effective = (
-        bool(entry_additive_enabled_raw) if exit_potential_mode != "canonical" else False
+        bool(entry_additive_enabled_raw) if resolved_mode != "canonical" else False
     )
     exit_additive_effective = (
-        bool(exit_additive_enabled_raw) if exit_potential_mode != "canonical" else False
+        bool(exit_additive_enabled_raw) if resolved_mode != "canonical" else False
     )
-    additives_suppressed = exit_potential_mode == "canonical" and bool(
+    additives_suppressed = resolved_mode == "canonical" and bool(
         entry_additive_enabled_raw or exit_additive_enabled_raw
     )
     return entry_additive_effective, exit_additive_effective, additives_suppressed
 
 
+def _resolve_exit_potential_mode(
+    value: Any,
+    *,
+    warn_invalid: bool = False,
+    stacklevel: int = 2,
+) -> str:
+    """Return a supported exit-potential mode, defaulting invalid values to canonical."""
+    if isinstance(value, str) and value in ALLOWED_EXIT_POTENTIAL_MODES:
+        return value
+    if warn_invalid:
+        _warn_unknown_mode(
+            "exit_potential_mode",
+            value,
+            sorted(ALLOWED_EXIT_POTENTIAL_MODES),
+            "canonical",
+            stacklevel=stacklevel,
+        )
+    return "canonical"
+
+
 def _get_float_param(
     params: RewardParams, key: str, default: RewardParamValue | None = None
 ) -> float:
@@ -595,6 +615,22 @@ def validate_reward_parameters(
                     "reason": "bool_coerce",
                     "validation_mode": "strict" if strict else "relaxed",
                 }
+    if "exit_potential_mode" in sanitized:
+        original_mode = sanitized["exit_potential_mode"]
+        resolved_mode = _resolve_exit_potential_mode(original_mode)
+        if resolved_mode != original_mode:
+            if strict:
+                raise ValueError(
+                    "Param: 'exit_potential_mode'="
+                    f"{original_mode!r} must be one of {sorted(ALLOWED_EXIT_POTENTIAL_MODES)}"
+                )
+            sanitized["exit_potential_mode"] = resolved_mode
+            adjustments["exit_potential_mode"] = {
+                "original": original_mode,
+                "adjusted": resolved_mode,
+                "reason": "invalid_choice",
+                "validation_mode": "relaxed",
+            }
 
     # Coerce and clamp numeric-bounded parameters
     for key, bounds in _PARAMETER_BOUNDS.items():
@@ -652,7 +688,7 @@ def validate_reward_parameters(
             strict=strict,
         )
 
-        if not np.isclose(adjusted, original_numeric):
+        if reason_parts:
             sanitized[key] = adjusted
             prev_reason = adjustments.get(key, {}).get("reason")
             reason: list[str] = []
@@ -1399,7 +1435,11 @@ def calculate_reward(
         breakdown.exit_pnl = terminal_context.current_pnl
 
     # Apply PBRS only if enabled and not neutral self-loop
-    exit_mode = _get_str_param(params, "exit_potential_mode")
+    exit_mode = _resolve_exit_potential_mode(
+        params.get("exit_potential_mode", DEFAULT_MODEL_REWARD_PARAMETERS["exit_potential_mode"]),
+        warn_invalid=True,
+        stacklevel=3,
+    )
 
     hold_potential_enabled = _get_bool_param(params, "hold_potential_enabled")
     entry_additive_enabled = (
@@ -1656,13 +1696,20 @@ def simulate_samples(
     - Realized PnL appears on the exit step (position still Long/Short).
     """
 
+    source_params = dict(params)
+    params = dict(source_params)
+    exit_mode = _resolve_exit_potential_mode(
+        params.get("exit_potential_mode", DEFAULT_MODEL_REWARD_PARAMETERS["exit_potential_mode"]),
+        warn_invalid=True,
+        stacklevel=3,
+    )
+    params["exit_potential_mode"] = exit_mode
     rng = random.Random(seed)
     max_trade_duration_candles = _get_int_param(params, "max_trade_duration_candles")
     short_allowed = _is_short_allowed(trading_mode)
     action_masking = _get_bool_param(params, "action_masking", True)
 
     # Theoretical PBRS invariance flag
-    exit_mode = _get_str_param(params, "exit_potential_mode")
     entry_enabled_raw = _get_bool_param(params, "entry_additive_enabled")
     exit_enabled_raw = _get_bool_param(params, "exit_additive_enabled")
 
@@ -1897,7 +1944,7 @@ def simulate_samples(
         )
 
     df = pd.DataFrame(samples)
-    df.attrs["reward_params"] = dict(params)
+    df.attrs["reward_params"] = source_params
 
     # Validate critical algorithmic invariants
     _validate_simulation_invariants(df)
@@ -2549,6 +2596,16 @@ def load_real_episodes(
     return df
 
 
+def _stabilize_divergence(value: float, *, bin_count: int, metric_name: str) -> float:
+    """Clamp only roundoff-sized negative divergences to their exact lower bound."""
+    if value < 0.0:
+        tolerance = 8.0 * np.finfo(float).eps * bin_count
+        if value >= -tolerance:
+            return 0.0
+        raise AssertionError(f"Stats: {metric_name} must be >= 0, got {value:.17g}")
+    return value
+
+
 def compute_distribution_shift_metrics(
     synthetic_df: pd.DataFrame,
     real_df: pd.DataFrame,
@@ -2589,22 +2646,35 @@ def compute_distribution_shift_metrics(
             continue
         bins = np.unique(np.linspace(min_val, max_val, 50))
 
-        # Use density=False to get counts, then normalize to probabilities
+        # Normalize counts before smoothing so proportional histograms stay identical.
         hist_synth, _ = np.histogram(synth_values, bins=bins, density=False)
         hist_real, _ = np.histogram(real_values, bins=bins, density=False)
+        hist_synth = hist_synth / hist_synth.sum()
+        hist_real = hist_real / hist_real.sum()
 
-        # Add small epsilon to avoid log(0) in KL divergence
+        # Add a common epsilon to avoid log(0), then restore unit mass.
         epsilon = float(INTERNAL_GUARDS["histogram_epsilon"])
         hist_synth = hist_synth + epsilon
         hist_real = hist_real + epsilon
-        # Normalize to create probability distributions (sum to 1)
         hist_synth = hist_synth / hist_synth.sum()
         hist_real = hist_real / hist_real.sum()
 
         # KL(synthetic||real): measures how much synthetic diverges from real
-        metrics[f"{feature}_kl_divergence"] = float(entropy(hist_synth, hist_real))
-        # JS distance (square root of JS divergence) is symmetric
-        metrics[f"{feature}_js_distance"] = float(jensenshannon(hist_synth, hist_real))
+        bin_count = len(hist_synth)
+        kl_divergence = _stabilize_divergence(
+            float(entropy(hist_synth, hist_real)),
+            bin_count=bin_count,
+            metric_name=f"{feature} KL divergence",
+        )
+        metrics[f"{feature}_kl_divergence"] = kl_divergence
+        # JS distance is the square root of the symmetric JS divergence.
+        mixture = 0.5 * (hist_synth + hist_real)
+        js_divergence = _stabilize_divergence(
+            0.5 * (float(entropy(hist_synth, mixture)) + float(entropy(hist_real, mixture))),
+            bin_count=bin_count,
+            metric_name=f"{feature} JS divergence",
+        )
+        metrics[f"{feature}_js_distance"] = math.sqrt(js_divergence)
         metrics[f"{feature}_wasserstein"] = float(
             stats.wasserstein_distance(synth_values, real_values)
         )
@@ -3325,9 +3395,17 @@ def _compute_exit_potential(
     prev_potential: float,
     params: RewardParams,
     gamma: float,
+    *,
+    mode: str | None = None,
 ) -> float:
     """Return exit potential using the selected mode and validated PBRS gamma."""
-    mode = _get_str_param(params, "exit_potential_mode")
+    mode = _resolve_exit_potential_mode(
+        params.get("exit_potential_mode", DEFAULT_MODEL_REWARD_PARAMETERS["exit_potential_mode"])
+        if mode is None
+        else mode,
+        warn_invalid=True,
+        stacklevel=3,
+    )
     if mode == "canonical" or mode == "non_canonical":
         return _fail_safely("canonical_exit_potential")
 
@@ -3363,14 +3441,7 @@ def _compute_exit_potential(
     elif mode == "retain_previous":
         next_potential = prev_potential
     else:
-        _warn_unknown_mode(
-            "exit_potential_mode",
-            mode,
-            sorted(ALLOWED_EXIT_POTENTIAL_MODES),
-            "canonical (via _fail_safely)",
-            stacklevel=2,
-        )
-        next_potential = _fail_safely("invalid_exit_potential_mode")
+        return _fail_safely("invalid_exit_potential_mode")
 
     if not np.isfinite(next_potential):
         next_potential = _fail_safely("non_finite_next_exit_potential")
@@ -3442,13 +3513,17 @@ def compute_pbrs_components(
 
     prev_potential = float(prev_potential) if np.isfinite(prev_potential) else 0.0
 
-    exit_mode = _get_str_param(params, "exit_potential_mode")
+    exit_mode = _resolve_exit_potential_mode(
+        params.get("exit_potential_mode", DEFAULT_MODEL_REWARD_PARAMETERS["exit_potential_mode"]),
+        warn_invalid=True,
+        stacklevel=3,
+    )
     canonical_mode = exit_mode == "canonical"
 
     hold_potential_enabled = _get_bool_param(params, "hold_potential_enabled")
 
     if is_exit:
-        next_potential = _compute_exit_potential(prev_potential, params, gamma)
+        next_potential = _compute_exit_potential(prev_potential, params, gamma, mode=exit_mode)
         pbrs_delta = gamma * next_potential - prev_potential
         reward_shaping = pbrs_delta
     else:
@@ -3816,6 +3891,14 @@ def write_complete_statistical_analysis(
         "exit_additive_enabled",
     }
     classification_metadata_available = classification_keys.issubset(reward_params)
+    raw_exit_potential_mode = (
+        reward_params.get("exit_potential_mode") if classification_metadata_available else None
+    )
+    classification_metadata_valid = (
+        classification_metadata_available
+        and isinstance(raw_exit_potential_mode, str)
+        and raw_exit_potential_mode in ALLOWED_EXIT_POTENTIAL_MODES
+    )
     max_trade_duration_candles = _get_int_param(reward_params, "max_trade_duration_candles")
 
     # Helpers: consistent Markdown table renderers
@@ -4233,8 +4316,10 @@ def write_complete_statistical_analysis(
 
             # Get configuration for proper invariance assessment
             if classification_metadata_available:
-                exit_potential_mode: str | None = _get_str_param(
-                    reward_params, "exit_potential_mode"
+                exit_potential_mode: str | None = (
+                    raw_exit_potential_mode
+                    if isinstance(raw_exit_potential_mode, str)
+                    else repr(raw_exit_potential_mode)
                 )
                 entry_additive_enabled_raw: bool | None = _get_bool_param(
                     reward_params, "entry_additive_enabled", False
@@ -4242,15 +4327,20 @@ def write_complete_statistical_analysis(
                 exit_additive_enabled_raw: bool | None = _get_bool_param(
                     reward_params, "exit_additive_enabled", False
                 )
-                (
-                    entry_additive_effective,
-                    exit_additive_effective,
-                    additives_suppressed,
-                ) = _resolve_additive_enablement(
-                    exit_potential_mode,
-                    entry_additive_enabled_raw,
-                    exit_additive_enabled_raw,
-                )
+                if classification_metadata_valid:
+                    (
+                        entry_additive_effective,
+                        exit_additive_effective,
+                        additives_suppressed,
+                    ) = _resolve_additive_enablement(
+                        exit_potential_mode,
+                        entry_additive_enabled_raw,
+                        exit_additive_enabled_raw,
+                    )
+                else:
+                    entry_additive_effective = False
+                    exit_additive_effective = False
+                    additives_suppressed = False
             else:
                 exit_potential_mode = None
                 entry_additive_enabled_raw = None
@@ -4267,7 +4357,7 @@ def write_complete_statistical_analysis(
                     "reason": "Trajectory verification skipped without reward configuration evidence",
                 }
             )
-            canonical_configuration = classification_metadata_available and (
+            canonical_configuration = classification_metadata_valid and (
                 exit_potential_mode == "canonical"
                 and not (entry_additive_effective or exit_additive_effective)
             )
@@ -4283,7 +4373,7 @@ def write_complete_statistical_analysis(
                     observed_additive_issues.append(f"{column} contains non-zero values")
             canonical_observations = not observed_additive_issues
 
-            if not classification_metadata_available:
+            if not classification_metadata_available or not classification_metadata_valid:
                 invariance_status = "Not verified"
             elif not canonical_configuration:
                 invariance_status = "Non-canonical: not verified"
@@ -4295,6 +4385,11 @@ def write_complete_statistical_analysis(
             invariance_note = evidence["reason"] + ". Raw shaping sums do not certify invariance."
             if not classification_metadata_available:
                 invariance_note += " Reward configuration evidence is missing or incomplete."
+            elif not classification_metadata_valid:
+                invariance_note += (
+                    " Reward configuration evidence is invalid: "
+                    f"invalid exit_potential_mode={raw_exit_potential_mode!r}."
+                )
             elif not canonical_configuration:
                 reasons = []
                 if exit_potential_mode != "canonical":
@@ -4331,10 +4426,10 @@ def write_complete_statistical_analysis(
                 f"| Exit Additive Enabled | {bool(exit_additive_enabled_raw) if exit_additive_enabled_raw is not None else 'unknown'} |\n"
             )
             f.write(
-                f"| Entry Additive Effective | {bool(entry_additive_effective) if classification_metadata_available else 'unknown'} |\n"
+                f"| Entry Additive Effective | {bool(entry_additive_effective) if classification_metadata_valid else 'unknown'} |\n"
             )
             f.write(
-                f"| Exit Additive Effective | {bool(exit_additive_effective) if classification_metadata_available else 'unknown'} |\n"
+                f"| Exit Additive Effective | {bool(exit_additive_effective) if classification_metadata_valid else 'unknown'} |\n"
             )
             f.write(f"| Σ Shaping Reward | {total_shaping:.6f} |\n")
             f.write(f"| Abs Σ Shaping Reward | {abs(total_shaping):.6e} |\n")
index 30d6f12fd26bd5d745edc56ffeeed7efc23091e9..339574359148e19de1c31706ea9e33f5931ba973 100644 (file)
@@ -88,7 +88,9 @@ class SummaryResult(TypedDict, total=False):
     interrupted: NotRequired[bool]
 
 
-_WARN_HEADER_RE = re.compile(r"^\s*(?:[A-Za-z]+Warning|WARNING)\b:?", re.IGNORECASE)
+_WARN_HEADER_RE = re.compile(
+    r"^(?:WARNING:|(?:[A-Za-z_][A-Za-z0-9_]*)?Warning:|.+:[1-9]\d*: (?:[A-Za-z_][A-Za-z0-9_]*)?Warning:)"
+)
 
 
 def _is_warning_header(line: str) -> bool:
@@ -97,7 +99,7 @@ def _is_warning_header(line: str) -> bool:
         return False
     if "warnings.warn" in line_str.lower():
         return False
-    return bool(_WARN_HEADER_RE.search(line_str))
+    return bool(_WARN_HEADER_RE.match(line_str))
 
 
 def build_arg_matrix(
index a5172e84269edb38c1c166ceb3d240293cf9a1f5..9388c7607fb987a8987044c605504fda4f3d4ab7 100644 (file)
@@ -208,6 +208,12 @@ Columns:
 | statistics-independence-contract-123          | statistics  | Inferential helpers require independent_observations=True                           | statistics/test_statistics.py:636         | Covers hypothesis tests and bootstrap intervals                                                                                                                                |
 | report-independent-sections-124               | integration | CI, diagnostics and shift sections do not depend on hypothesis-test output          | integration/test_report_formatting.py:28  | Also verifies the reported bootstrap resample count                                                                                                                            |
 | pbrs-discounted-evidence-125                  | pbrs        | Verification requires local identity, continuity and discounted terminal boundary   | pbrs/test_pbrs.py:1699                    | Discontinuous potentials are not verified                                                                                                                                      |
+| statistics-proportional-histograms-126        | statistics  | Proportional histograms ignore sample count; moved mass yields positive KL/JS        | statistics/test_statistics.py:119         | KL and JS remain finite and non-negative                                                                                                                                       |
+| pbrs-near-bound-clamp-127                     | pbrs        | Relaxed near-bound clamps apply exact endpoints and retain all reasons               | pbrs/test_pbrs.py:1276                     | Includes numeric-string coercion                                                                                                                                               |
+| pbrs-exit-mode-validation-128                 | pbrs        | Exit-potential choices are strict or canonicalized; direct calls fail safe           | pbrs/test_pbrs.py:1296                     | Direct calculation and simulation boundaries warn before fallback; PBRS calls suppress additives                                                                              |
+| cli-invalid-exit-mode-129                     | cli         | Invalid `--params exit_potential_mode` fails before artifacts                       | cli/test_cli_params_and_csv.py:322         | Strict CLI validation                                                                                                                                                          |
+| cli-warning-header-recognition-130            | cli         | Warning counts accept only anchored Python warning header formats                    | cli/test_cli_params_and_csv.py:49          | Covers POSIX, relative, synthetic and Windows source locations                                                                                                                 |
+| pbrs-invalid-mode-provenance-131              | pbrs        | Invalid imported exit-mode metadata cannot certify canonical invariance              | pbrs/test_pbrs.py:475                      | Preserves the invalid raw value and reports effective additive settings as unknown                                                                                             |
 
 ### Non-Owning Smoke / Reference Checks
 
index 8236e55ea7fa4dfe66c8b379b292072b66887dbc..f5a07f2592faf4ebddac4195a5f8d3a0f1fdd0f0 100644 (file)
@@ -17,6 +17,7 @@ from reward_space_analysis import (
     Actions,
     get_max_idle_duration_candles,
 )
+from test_reward_space_analysis_cli import _is_warning_header, run_scenario
 
 from ..constants import SCENARIOS, SEEDS, TOLERANCE
 from ..test_base import RewardSpaceTestBase
@@ -46,6 +47,66 @@ def _assert_cli_success(
     testcase.assertEqual(result.returncode, 0, f"CLI failed: {result.stderr}")
 
 
+class TestWarningHeaderRecognition(RewardSpaceTestBase):
+    """Recognize only real Python warning header formats."""
+
+    def test_warning_header_positive_and_negative_formats(self):
+        positive = (
+            "WARNING: explicit header",
+            "Warning: base category header",
+            "UserWarning: category header",
+            "RewardDiagnosticsWarning: custom category",
+            "/tmp/pkg/file.py:12: UserWarning: POSIX path",
+            "/tmp/a.py:1: Warning: base category path",
+            "relative/file.py:1: RuntimeWarning: relative path",
+            "<string>:7: FutureWarning: synthetic source",
+            r"C:\work\file.py:42: DeprecationWarning: Windows path",
+        )
+        negative = (
+            "warning: wrong case",
+            "Some prose about UserWarning: not a header",
+            "warnings.warn('source code', UserWarning)",
+            "/tmp/file.py:0: UserWarning: zero line",
+            "/tmp/file.py:-1: UserWarning: negative line",
+            "/tmp/file.py:1: WarningExtra: wrong suffix",
+            "/tmp/file.py:1: UserWarningExtra: wrong suffix",
+            "UserWarningExtra: wrong suffix",
+            "WARNING without colon",
+            "prefix WARNING: embedded prose",
+        )
+
+        for line in positive:
+            with self.subTest(line=line):
+                self.assertTrue(_is_warning_header(line))
+        for line in negative:
+            with self.subTest(line=line):
+                self.assertFalse(_is_warning_header(line))
+
+    def test_run_scenario_counts_only_warning_headers(self):
+        """Scenario warning totals use the same strict header recognizer."""
+        script = self.output_path / "warning_emitter.py"
+        script.write_text(
+            "import sys\n"
+            "print('UserWarning: first')\n"
+            "print('ordinary prose mentioning Warning:')\n"
+            "print('<string>:2: RuntimeWarning: second', file=sys.stderr)\n",
+            encoding="utf-8",
+        )
+
+        result = run_scenario(
+            script=script,
+            out_dir=self.output_path / "warning_counter",
+            idx=0,
+            num_samples=1,
+            conf=("canonical", "linear", 0.95, 0, 0, 0),
+            strict=False,
+            timeout=10,
+        )
+
+        self.assertEqual(result["status"], "ok")
+        self.assertEqual(result["warnings"], 2)
+
+
 class TestCsvEncoding(RewardSpaceTestBase):
     """Validate CSV output encoding invariants."""
 
@@ -261,6 +322,19 @@ class TestParamsPropagation(RewardSpaceTestBase):
             int(rp["max_trade_duration_candles"]), SCENARIOS.CLI_MAX_TRADE_DURATION_PARAMS
         )
 
+    def test_invalid_exit_potential_mode_params_fails_before_artifacts(self):
+        """An invalid --params exit-potential mode fails strict validation before output creation."""
+        out_dir = self.output_path / "invalid_exit_potential_mode"
+
+        result = _run_cli(
+            out_dir=out_dir,
+            args=["--params", "exit_potential_mode=unsupported"],
+        )
+
+        self.assertNotEqual(result.returncode, 0)
+        self.assertIn("exit_potential_mode", result.stderr + result.stdout)
+        self.assertFalse(out_dir.exists())
+
     def test_missing_real_episodes_fails_before_artifacts(self):
         """An explicitly requested but missing episodes file fails the run with no artifacts."""
         out_dir = self.output_path / "missing_real"
index c08b766a8a38f96412cf640300f8ae8b255936d8..183a306a246246bed7d73e869f97356b6817afef 100644 (file)
@@ -473,6 +473,44 @@ class TestSimulationParity(RewardSpaceTestBase):
         self.assertIn("reward_entry_additive contains non-zero values", content)
         self.assertIn("| Σ Entry Additive | 0.000000 |", content)
 
+    def test_report_rejects_invalid_exit_mode_provenance(self):
+        """Invalid imported mode metadata cannot certify canonical invariance."""
+        df = simulate_samples(
+            params=self.base_params(
+                exit_potential_mode="canonical",
+                entry_additive_enabled=False,
+                exit_additive_enabled=False,
+                hold_potential_enabled=True,
+            ),
+            num_samples=40,
+            seed=SEEDS.BASE,
+            base_factor=PARAMS.BASE_FACTOR,
+            profit_aim=PARAMS.PROFIT_AIM,
+            risk_reward_ratio=PARAMS.RISK_REWARD_RATIO,
+            max_duration_ratio=2.0,
+            trading_mode="futures",
+            pnl_base_std=PARAMS.PNL_STD,
+            pnl_duration_vol_scale=PARAMS.PNL_DUR_VOL_SCALE,
+        )
+        df.attrs["reward_params"]["exit_potential_mode"] = "canoncial"
+        out_dir = self.output_path / "invalid_exit_mode_provenance"
+        write_complete_statistical_analysis(
+            df,
+            output_dir=out_dir,
+            profit_aim=PARAMS.PROFIT_AIM,
+            risk_reward_ratio=PARAMS.RISK_REWARD_RATIO,
+            seed=SEEDS.BASE,
+            skip_feature_analysis=True,
+            skip_partial_dependence=True,
+            bootstrap_resamples=SCENARIOS.BOOTSTRAP_MINIMAL_ITERATIONS,
+        )
+        content = (out_dir / "statistical_analysis.md").read_text(encoding="utf-8")
+        self.assertIn("| Invariance Status | Not verified |", content)
+        self.assertIn("invalid exit_potential_mode='canoncial'", content)
+        self.assertIn("| Exit Potential Mode | canoncial |", content)
+        self.assertIn("| Entry Additive Effective | unknown |", content)
+        self.assertNotIn("Canonical: observed PBRS verified", content)
+
     def test_non_canonical_report_classifies_both_outputs(self):
         """Zero correction cannot certify a non-canonical potential mode."""
         df = simulate_samples(
@@ -832,6 +870,46 @@ class TestPBRS(RewardSpaceTestBase):
             msg="PBRS disabled total must equal base_reward",
         )
 
+    def test_invalid_exit_mode_warns_at_direct_and_simulation_boundaries(self):
+        """Invalid direct modes warn once before canonical fallback can disable PBRS."""
+        params = self.base_params(
+            exit_potential_mode="unsupported",
+            hold_potential_enabled=False,
+            entry_additive_enabled=True,
+            exit_additive_enabled=True,
+        )
+        context = self.make_ctx(position=Positions.Neutral, action=Actions.Neutral)
+        warning_match = "unknown exit_potential_mode 'unsupported'.*falling back to 'canonical'"
+
+        with pytest.warns(
+            reward_space_analysis.RewardDiagnosticsWarning, match=warning_match
+        ) as direct_warnings:
+            breakdown = calculate_reward_with_defaults(context, params)
+
+        self.assertEqual(len(direct_warnings), 1)
+        self.assertNearZero(breakdown.reward_shaping, atol=TOLERANCE.IDENTITY_STRICT)
+        self.assertNearZero(breakdown.entry_additive, atol=TOLERANCE.IDENTITY_STRICT)
+        self.assertNearZero(breakdown.exit_additive, atol=TOLERANCE.IDENTITY_STRICT)
+
+        with pytest.warns(
+            reward_space_analysis.RewardDiagnosticsWarning, match=warning_match
+        ) as simulation_warnings:
+            simulated = simulate_samples(
+                params=params,
+                num_samples=4,
+                seed=SEEDS.BASE,
+                base_factor=PARAMS.BASE_FACTOR,
+                profit_aim=PARAMS.PROFIT_AIM,
+                risk_reward_ratio=PARAMS.RISK_REWARD_RATIO,
+                max_duration_ratio=2.0,
+                trading_mode="futures",
+                pnl_base_std=PARAMS.PNL_STD,
+                pnl_duration_vol_scale=PARAMS.PNL_DUR_VOL_SCALE,
+            )
+
+        self.assertEqual(len(simulation_warnings), 1)
+        self.assertEqual(simulated.attrs["reward_params"]["exit_potential_mode"], "unsupported")
+
     def test_exit_potential_canonical(self):
         """Verifies canonical exit resets potential (no params mutation)."""
         params = self.base_params(
@@ -1274,6 +1352,78 @@ class TestPBRS(RewardSpaceTestBase):
             },
         )
 
+    def test_validate_reward_parameters_records_near_bound_clamps_exactly(self):
+        """Relaxed validation applies near-bound clamps without approximate-equality suppression."""
+        cases = (
+            (-1e-12, 0.0, "min=0.0"),
+            (1.0 + 1e-12, 1.0, "max=1.0"),
+            ("-1e-12", 0.0, "numeric_coerce,min=0.0"),
+        )
+        for original, expected, expected_reason in cases:
+            with self.subTest(original=original):
+                params = DEFAULT_MODEL_REWARD_PARAMETERS.copy()
+                params["potential_gamma"] = original
+
+                sanitized, adjustments = validate_reward_parameters(params, strict=False)
+
+                self.assertEqual(sanitized["potential_gamma"], expected)
+                self.assertEqual(adjustments["potential_gamma"]["original"], original)
+                self.assertEqual(adjustments["potential_gamma"]["adjusted"], expected)
+                self.assertEqual(adjustments["potential_gamma"]["reason"], expected_reason)
+                self.assertEqual(adjustments["potential_gamma"]["validation_mode"], "relaxed")
+
+    def test_validate_reward_parameters_enforces_exit_potential_mode_choice(self):
+        """Invalid exit-potential modes raise in strict mode and canonicalize in relaxed mode."""
+        params = DEFAULT_MODEL_REWARD_PARAMETERS.copy()
+        params["exit_potential_mode"] = "Canonical"
+
+        with self.assertRaisesRegex(ValueError, "exit_potential_mode"):
+            validate_reward_parameters(params, strict=True)
+
+        sanitized, adjustments = validate_reward_parameters(params, strict=False)
+        self.assertEqual(sanitized["exit_potential_mode"], "canonical")
+        self.assertEqual(
+            adjustments["exit_potential_mode"],
+            {
+                "original": "Canonical",
+                "adjusted": "canonical",
+                "reason": "invalid_choice",
+                "validation_mode": "relaxed",
+            },
+        )
+
+    def test_compute_pbrs_components_invalid_mode_falls_back_to_canonical(self):
+        """Direct PBRS calls warn and suppress additives for an invalid mode."""
+        params = self.base_params(
+            exit_potential_mode="unsupported",
+            hold_potential_enabled=True,
+            entry_additive_enabled=True,
+            exit_additive_enabled=True,
+        )
+
+        with self.assertWarnsRegex(
+            reward_space_analysis.RewardDiagnosticsWarning,
+            "unknown exit_potential_mode 'unsupported'.*falling back to 'canonical'",
+        ):
+            components = reward_space_analysis.compute_pbrs_components(
+                current_pnl=0.02,
+                pnl_target=PARAMS.PROFIT_AIM * PARAMS.RISK_REWARD_RATIO,
+                current_duration_ratio=0.5,
+                next_pnl=0.01,
+                next_duration_ratio=0.6,
+                params=params,
+                base_factor=PARAMS.BASE_FACTOR,
+                risk_reward_ratio=PARAMS.RISK_REWARD_RATIO,
+                prev_potential=0.25,
+                entry_pnl=0.0,
+                is_exit=True,
+                is_entry=True,
+            )
+
+        self.assertEqual(components[1], 0.0)
+        self.assertEqual(components[3], 0.0)
+        self.assertEqual(components[4], 0.0)
+
     # ---------------- Exit potential mode comparisons ---------------- #
 
     def test_compute_exit_potential_mode_differences(self):
index 183fb1df5f5fdc6a7ef159ac96a1fdce448270e1..e32f445c406f36c91a013a64510a37512727ec54 100644 (file)
@@ -117,6 +117,45 @@ class TestStatistics(RewardSpaceTestBase):
                     msg=f"Expected 1.0 for {p_key}",
                 )
 
+    def test_distribution_shift_proportional_histograms_ignore_sample_count(self):
+        """Proportional histograms of different sizes have finite, non-negative zero shift."""
+        values = np.repeat(np.arange(4, dtype=float), 10)
+        synthetic = pd.DataFrame(
+            {
+                "pnl": values,
+                "trade_duration": values + 10.0,
+                "idle_duration": values + 20.0,
+            }
+        )
+        real = pd.concat([synthetic, synthetic], ignore_index=True)
+
+        metrics = compute_distribution_shift_metrics(synthetic, real)
+
+        for feature in ("pnl", "trade_duration", "idle_duration"):
+            for suffix in ("kl_divergence", "js_distance"):
+                value = metrics[f"{feature}_{suffix}"]
+                self.assertTrue(np.isfinite(value))
+                self.assertGreaterEqual(value, 0.0)
+                self.assertLessEqual(value, TOLERANCE.GENERIC_EQ)
+
+    def test_distribution_shift_detects_one_observation_moved_between_bins(self):
+        """Moving one observation between occupied bins produces positive KL and JS shift."""
+        values = np.repeat(np.arange(4, dtype=float), 10)
+        synthetic = pd.DataFrame(
+            {
+                "pnl": values,
+                "trade_duration": values + 10.0,
+                "idle_duration": values + 20.0,
+            }
+        )
+        real = synthetic.copy()
+        real.loc[0, "pnl"] = 3.0
+
+        metrics = compute_distribution_shift_metrics(synthetic, real)
+
+        self.assertGreater(metrics["pnl_kl_divergence"], 0.0)
+        self.assertGreater(metrics["pnl_js_distance"], 0.0)
+
     def test_statistics_distribution_shift_metrics(self):
         """KL/JS/Wasserstein metrics."""
         df1 = self._make_idle_variance_df(100)
index 3cc6c209331974144d3a628be0993af3b0395baa..b545b733610cd1d56722c12de53707c6ce10dcf3 100644 (file)
@@ -10,8 +10,8 @@ from ReforceXY.user_data.freqaimodels.ReforceXY import Actions, MyRLEnv
 
 
 class PortfolioReturnsTest(unittest.TestCase):
-    def make_env(self, prices, *, compound=True, fee=0.0015):
-        frame = pd.DataFrame({"open": prices})
+    def make_env(self, prices, *, compound=True, fee=0.0015, price_index=None):
+        frame = pd.DataFrame({"open": prices}, index=price_index)
         env = MyRLEnv(
             df=frame.copy(),
             prices=frame,
@@ -184,6 +184,34 @@ class PortfolioReturnsTest(unittest.TestCase):
             [(1, "long_enter"), (2, "long_exit")],
         )
 
+    def test_history_joins_prices_by_position_without_mutating_price_index(self):
+        """Price labels never affect transition joins, including collisions and duplicates."""
+        prices = [100.0, 100.0, 110.0, 110.0]
+        index_cases = {
+            "zero_based": [0, 1, 2, 3],
+            "nonzero": [10, 11, 12, 13],
+            "collision": [1, 2, 1, 2],
+            "duplicated": [7, 7, 7, 7],
+        }
+        baseline = None
+
+        for name, price_index in index_cases.items():
+            with self.subTest(index=name):
+                env = self.make_env(prices, price_index=price_index)
+                original_index = env.prices.index.copy()
+                env.step(Actions.Long_enter.value)
+                env.step(Actions.Long_exit.value)
+
+                history = env.get_env_history()
+
+                self.assertEqual(len(history), 2)
+                self.assertEqual(history["open"].tolist(), [110.0, 110.0])
+                self.assertTrue(env.prices.index.equals(original_index))
+                if baseline is None:
+                    baseline = history
+                else:
+                    pd.testing.assert_frame_equal(history, baseline)
+
     def test_terminal_entry_preserves_two_events_and_plot_markers(self):
         env = self.make_env([100.0, 100.0, 90.0])
         _, _, terminated, truncated, info = env.step(Actions.Long_enter.value)
index 5e542b2fc812a4e09169be9b8e221d84f3261688..c2e573a26b2abf1f47ec0e40ec5ff04c99a89ce9 100644 (file)
@@ -4338,10 +4338,11 @@ class MyRLEnv(Base5ActionRLEnv):
         try:
             history = merge(
                 _history_df,
-                self.prices,
+                self.prices.reset_index(drop=True),
                 left_on="tick",
                 right_index=True,
                 how="left",
+                sort=False,
             )
         except Exception as e:
             logger.warning(