From: Jérôme Benoit Date: Sun, 20 Sep 2026 14:31:38 +0000 (+0200) Subject: fix(reforcexy): correct audit findings (#270) X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=5eb5036e32fac85214c6a1817ba7352aa6aee1bf;p=freqai-strategies.git fix(reforcexy): correct audit findings (#270) * fix(reforcexy): correct audit findings * fix(reforcexy): preserve invalid PBRS provenance * fix(reforcexy): warn on invalid exit modes --- diff --git a/ReforceXY/.basedpyright/diagnostics.json b/ReforceXY/.basedpyright/diagnostics.json index 509c3a8..85cab2b 100644 --- a/ReforceXY/.basedpyright/diagnostics.json +++ b/ReforceXY/.basedpyright/diagnostics.json @@ -3,373 +3,373 @@ "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, @@ -803,493 +803,493 @@ }, { "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, diff --git a/ReforceXY/reward_space_analysis/README.md b/ReforceXY/reward_space_analysis/README.md index e4290db..28ccd42 100644 --- a/ReforceXY/reward_space_analysis/README.md +++ b/ReforceXY/reward_space_analysis/README.md @@ -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. diff --git a/ReforceXY/reward_space_analysis/reward_space_analysis.py b/ReforceXY/reward_space_analysis/reward_space_analysis.py index f75b1c1..960d072 100644 --- a/ReforceXY/reward_space_analysis/reward_space_analysis.py +++ b/ReforceXY/reward_space_analysis/reward_space_analysis.py @@ -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") diff --git a/ReforceXY/reward_space_analysis/test_reward_space_analysis_cli.py b/ReforceXY/reward_space_analysis/test_reward_space_analysis_cli.py index 30d6f12..3395743 100644 --- a/ReforceXY/reward_space_analysis/test_reward_space_analysis_cli.py +++ b/ReforceXY/reward_space_analysis/test_reward_space_analysis_cli.py @@ -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( diff --git a/ReforceXY/reward_space_analysis/tests/README.md b/ReforceXY/reward_space_analysis/tests/README.md index a5172e8..9388c76 100644 --- a/ReforceXY/reward_space_analysis/tests/README.md +++ b/ReforceXY/reward_space_analysis/tests/README.md @@ -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 diff --git a/ReforceXY/reward_space_analysis/tests/cli/test_cli_params_and_csv.py b/ReforceXY/reward_space_analysis/tests/cli/test_cli_params_and_csv.py index 8236e55..f5a07f2 100644 --- a/ReforceXY/reward_space_analysis/tests/cli/test_cli_params_and_csv.py +++ b/ReforceXY/reward_space_analysis/tests/cli/test_cli_params_and_csv.py @@ -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", + ":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(':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" diff --git a/ReforceXY/reward_space_analysis/tests/pbrs/test_pbrs.py b/ReforceXY/reward_space_analysis/tests/pbrs/test_pbrs.py index c08b766..183a306 100644 --- a/ReforceXY/reward_space_analysis/tests/pbrs/test_pbrs.py +++ b/ReforceXY/reward_space_analysis/tests/pbrs/test_pbrs.py @@ -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): diff --git a/ReforceXY/reward_space_analysis/tests/statistics/test_statistics.py b/ReforceXY/reward_space_analysis/tests/statistics/test_statistics.py index 183fb1d..e32f445 100644 --- a/ReforceXY/reward_space_analysis/tests/statistics/test_statistics.py +++ b/ReforceXY/reward_space_analysis/tests/statistics/test_statistics.py @@ -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) diff --git a/ReforceXY/tests/test_portfolio_returns.py b/ReforceXY/tests/test_portfolio_returns.py index 3cc6c20..b545b73 100644 --- a/ReforceXY/tests/test_portfolio_returns.py +++ b/ReforceXY/tests/test_portfolio_returns.py @@ -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) diff --git a/ReforceXY/user_data/freqaimodels/ReforceXY.py b/ReforceXY/user_data/freqaimodels/ReforceXY.py index 5e542b2..c2e573a 100644 --- a/ReforceXY/user_data/freqaimodels/ReforceXY.py +++ b/ReforceXY/user_data/freqaimodels/ReforceXY.py @@ -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(