]> Piment Noir Git Repositories - freqai-strategies.git/commitdiff
fix(reward): preserve profitable synthetic paths at high fees
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 23 Sep 2026 19:03:59 +0000 (21:03 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 23 Sep 2026 19:03:59 +0000 (21:03 +0200)
ReforceXY/.basedpyright/diagnostics.json
ReforceXY/reward_space_analysis/README.md
ReforceXY/reward_space_analysis/reward_space_analysis.py
ReforceXY/reward_space_analysis/tests/README.md
ReforceXY/reward_space_analysis/tests/pbrs/test_pbrs.py

index 52db6511582f2e1946ee95efc577954dc0b82998..0a0397460b1d114dc22f4d821b9edd122be5a4bf 100644 (file)
     },
     {
       "endCharacter": 5,
-      "endLine": 2102,
+      "endLine": 2108,
       "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": 2097
+      "startLine": 2103
     },
     {
       "endCharacter": 21,
-      "endLine": 2099,
+      "endLine": 2105,
       "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": 2099
+      "startLine": 2105
     },
     {
       "endCharacter": 5,
-      "endLine": 2124,
+      "endLine": 2130,
       "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": 2120
+      "startLine": 2126
     },
     {
       "endCharacter": 43,
-      "endLine": 2121,
+      "endLine": 2127,
       "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": 2121
+      "startLine": 2127
     },
     {
       "endCharacter": 42,
-      "endLine": 2121,
+      "endLine": 2127,
       "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": 2121
+      "startLine": 2127
     },
     {
       "endCharacter": 60,
-      "endLine": 2381,
+      "endLine": 2387,
       "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": 2381
+      "startLine": 2387
     },
     {
       "endCharacter": 58,
-      "endLine": 2382,
+      "endLine": 2388,
       "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": 2382
+      "startLine": 2388
     },
     {
       "endCharacter": 88,
-      "endLine": 2401,
+      "endLine": 2407,
       "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": 2401
+      "startLine": 2407
     },
     {
       "endCharacter": 88,
-      "endLine": 2401,
+      "endLine": 2407,
       "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": 2401
+      "startLine": 2407
     },
     {
       "endCharacter": 17,
-      "endLine": 2410,
+      "endLine": 2416,
       "file": "ReforceXY/reward_space_analysis/reward_space_analysis.py",
       "message": "Object of type \"None\" cannot be called",
       "rule": "reportOptionalCall",
       "severity": "error",
       "startCharacter": 28,
-      "startLine": 2404
+      "startLine": 2410
     },
     {
       "endCharacter": 40,
-      "endLine": 2639,
+      "endLine": 2645,
       "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": 2639
+      "startLine": 2645
     },
     {
       "endCharacter": 38,
-      "endLine": 2639,
+      "endLine": 2645,
       "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": 2639
+      "startLine": 2645
     },
     {
       "endCharacter": 59,
-      "endLine": 2639,
+      "endLine": 2645,
       "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": 2639
+      "startLine": 2645
     },
     {
       "endCharacter": 57,
-      "endLine": 2639,
+      "endLine": 2645,
       "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": 2639
+      "startLine": 2645
     },
     {
       "endCharacter": 40,
-      "endLine": 2640,
+      "endLine": 2646,
       "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": 2640
+      "startLine": 2646
     },
     {
       "endCharacter": 38,
-      "endLine": 2640,
+      "endLine": 2646,
       "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": 2640
+      "startLine": 2646
     },
     {
       "endCharacter": 59,
-      "endLine": 2640,
+      "endLine": 2646,
       "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": 2640
+      "startLine": 2646
     },
     {
       "endCharacter": 57,
-      "endLine": 2640,
+      "endLine": 2646,
       "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": 2640
+      "startLine": 2646
     },
     {
       "endCharacter": 76,
-      "endLine": 2656,
+      "endLine": 2662,
       "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": 2656
+      "startLine": 2662
     },
     {
       "endCharacter": 49,
-      "endLine": 2656,
+      "endLine": 2662,
       "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": 2656
+      "startLine": 2662
     },
     {
       "endCharacter": 74,
-      "endLine": 2657,
+      "endLine": 2663,
       "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": 2657
+      "startLine": 2663
     },
     {
       "endCharacter": 47,
-      "endLine": 2657,
+      "endLine": 2663,
       "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": 2657
+      "startLine": 2663
     },
     {
       "endCharacter": 51,
-      "endLine": 2685,
+      "endLine": 2691,
       "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": 2685
+      "startLine": 2691
     },
     {
       "endCharacter": 64,
-      "endLine": 2685,
+      "endLine": 2691,
       "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": 2685
+      "startLine": 2691
     },
     {
       "endCharacter": 68,
-      "endLine": 2688,
+      "endLine": 2694,
       "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": 2688
+      "startLine": 2694
     },
     {
       "endCharacter": 54,
-      "endLine": 2688,
+      "endLine": 2694,
       "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": 2688
+      "startLine": 2694
     },
     {
       "endCharacter": 67,
-      "endLine": 2688,
+      "endLine": 2694,
       "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": 2688
+      "startLine": 2694
     },
     {
       "endCharacter": 29,
-      "endLine": 2988,
+      "endLine": 2994,
       "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": 2988
+      "startLine": 2994
     },
     {
       "endCharacter": 28,
-      "endLine": 2988,
+      "endLine": 2994,
       "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": 2988
+      "startLine": 2994
     },
     {
       "endCharacter": 65,
-      "endLine": 2997,
+      "endLine": 3003,
       "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": 2997
+      "startLine": 3003
     },
     {
       "endCharacter": 65,
-      "endLine": 2997,
+      "endLine": 3003,
       "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": 2997
+      "startLine": 3003
     },
     {
       "endCharacter": 5,
-      "endLine": 3893,
+      "endLine": 3899,
       "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": 3889
+      "startLine": 3895
     },
     {
       "endCharacter": 43,
-      "endLine": 3890,
+      "endLine": 3896,
       "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": 3890
+      "startLine": 3896
     },
     {
       "endCharacter": 42,
-      "endLine": 3890,
+      "endLine": 3896,
       "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": 3890
+      "startLine": 3896
     },
     {
       "endCharacter": 22,
index c5381a9efc3cb49a89d31027e0d3d5efe86eb254..65744f31f1e2e0b0026d4d4eaa0ee05f88266789 100644 (file)
@@ -165,10 +165,10 @@ Generates shift metrics for comparison (see Outputs section).
 - **`--real_episodes`** (path, optional) – Episodes pickle for real vs synthetic
   distribution shift metrics. (Simulation-only; triggers additional outputs when
   provided).
-- **`--unrealized_pnl`** (flag, default: false) – Transform the retained
-  in-position synthetic price/PnL trajectory using fee-aware unrealized PnL.
-  This affects subsequent retained extrema and enabled reward terms that depend
-  on PnL. (Simulation-only.)
+- **`--unrealized_pnl`** (flag, default: false) – Track a sampled market
+  price separately from the retained price within each trade; map its fee-aware
+  PnL through duration-based tanh scaling before retaining the mark. Retained
+  marks affect exit-efficiency extrema and PnL-dependent rewards. (Simulation-only.)
 
 ### Hybrid Simulation Scalars
 
@@ -289,12 +289,15 @@ Let `max_u = max_unrealized_profit`, `min_u = min_unrealized_profit`,
   `efficiency_coefficient = 1 + efficiency_weight · (efficiency_center - ratio)`
 - Else: `efficiency_coefficient = 1`
 
-The extrema start with the fee-adjusted PnL at the entry fill and then include
-each retained market mark. In synthetic `unrealized_pnl` mode, a sampled
-candidate discarded by the transform is not an extremum.
-Synthetic marks are capped at +0.15; their lower bound is the lesser of -0.15
-and the fee-adjusted entry PnL. The extreme-PnL check also permits the
-configured fee loss, rather than rejecting a valid short entry at high fees.
+In synthetic `unrealized_pnl` mode, sampled market prices accumulate each
+candle's return independently of the transformed, retained price. The
+fee-aware sampled PnL is bounded, scaled by the duration-dependent tanh
+factor, and converted to the retained price. Exit-efficiency extrema start
+with the fee-adjusted PnL at the entry fill and then include each retained
+mark; a sampled candidate is never retained as an extremum. Synthetic marks
+are capped at +0.15; their lower bound is the lesser of -0.15 and the
+fee-adjusted entry PnL. The extreme-PnL check permits that fee loss and
+floating-point roundoff at its boundary, but rejects larger excursions.
 
 ##### Exit Attenuation
 
index 412268b28d8fc5dbdd241aa4991ba475f83b298f..eb8736b58779d4f7976abd8998705de7156b8848 100644 (file)
@@ -1739,6 +1739,7 @@ def simulate_samples(
 
     # Synthetic market state
     current_open = 1.0
+    sampled_open = current_open
     entry_open = current_open
 
     for _ in range(num_samples):
@@ -1778,6 +1779,7 @@ def simulate_samples(
                     position, entry_open=entry_open, current_open=entry_open, params=params
                 )
                 max_unrealized_profit = min_unrealized_profit = entry_pnl
+                sampled_open = entry_open
                 pnl_floor = min(-0.15, entry_pnl)
         else:
             idle_duration = 0
@@ -1810,33 +1812,36 @@ def simulate_samples(
             step_return = 0.0
         step_return = float(np.clip(step_return, -0.95, 0.95))
 
-        current_open = float(max(1e-6, current_open * (1.0 + step_return)))
-        # Always sample the random-walk price so both modes consume the same RNG stream.
-        # Unrealized-PnL mode replaces it below with the fee-aware price implied by
-        # the target PnL before reward calculation.
+        # Sample once in both modes; keep the raw market path separate from the
+        # transformed price while a position is open.
+        transform_unrealized_pnl = position in (
+            Positions.Long,
+            Positions.Short,
+        ) and _get_bool_param(params, "unrealized_pnl", False)
+        if transform_unrealized_pnl:
+            sampled_open = float(max(1e-6, sampled_open * (1.0 + step_return)))
+            candidate_open = sampled_open
+        else:
+            current_open = float(max(1e-6, current_open * (1.0 + step_return)))
+            candidate_open = current_open
         if position in (Positions.Long, Positions.Short):
             candidate_pnl = float(
                 np.clip(
                     _compute_unrealized_pnl_estimate(
                         position,
                         entry_open=entry_open,
-                        current_open=current_open,
+                        current_open=candidate_open,
                         params=params,
                     ),
                     pnl_floor,
                     0.15,
                 )
             )
-            if _get_bool_param(params, "unrealized_pnl", False):
-                # Let the sampled market move shape the next retained PnL without
-                # storing the discarded candidate in exit-efficiency extrema.
-                prospective_max = max(max_unrealized_profit, candidate_pnl)
-                prospective_min = min(min_unrealized_profit, candidate_pnl)
-                center_unrealized = 0.5 * (prospective_max + prospective_min)
+            if transform_unrealized_pnl:
                 beta = _get_float_param(params, "pnl_amplification_sensitivity")
                 hold_ratio = _compute_duration_ratio(trade_duration, max_trade_duration_candles)
                 target_pnl = float(
-                    np.clip(center_unrealized * math.tanh(beta * hold_ratio), pnl_floor, 0.15)
+                    np.clip(candidate_pnl * math.tanh(beta * hold_ratio), pnl_floor, 0.15)
                 )
                 entry_fee_rate, exit_fee_rate = _get_fee_rates(params)
                 if position == Positions.Long:
@@ -2031,7 +2036,8 @@ def _validate_simulation_invariants(df: pd.DataFrame, params: RewardParams) -> N
         )
 
     # INVARIANT 5: Bounded values
-    extreme_pnl = df[(df["pnl"].abs() > thr_extreme)]
+    # Fee-adjusted prices can differ from the nominal fee product by a few ULPs.
+    extreme_pnl = df[df["pnl"].abs() > thr_extreme + eps_pnl]
     if len(extreme_pnl) > 0:
         max_abs_pnl = float(df["pnl"].abs().max())
         raise AssertionError(
index 457742f45dc31cfba5e09dc35d0cc91a7aa7914f..e4203b21818e659f2cdf8b382f8eb708084d7b40 100644 (file)
@@ -216,6 +216,9 @@ Columns:
 | cli-warning-header-recognition-130            | cli         | Warning counts accept only anchored Python warning header formats                   | cli/test_cli_params_and_csv.py::test_warning_header_positive_and_negative_formats                  | 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::test_report_rejects_invalid_exit_mode_provenance                                | Preserves the invalid raw value and reports effective additive settings as unknown                                                                                             |
 | pbrs-synthetic-fee-floor-132                  | pbrs        | High-fee entry loss remains in long/short synthetic PnL and exit rewards            | pbrs/test_pbrs.py::test_synthetic_fee_loss_extrema_match_retained_pnl                              | Direct and transformed trajectories                                                                                                                                            |
+| pbrs-synthetic-profitable-mark-133            | pbrs        | Favorable long/short marks remain profitable after high-fee synthetic transformation | pbrs/test_pbrs.py::test_synthetic_high_fee_winner_retains_profitable_mark                           | Covers immediate gains and recovery after an initial loss on the independent sampled market path                                                                                |
+| pbrs-synthetic-fee-boundary-134               | pbrs        | Fee-boundary rounding is accepted while materially extreme PnL is rejected          | pbrs/test_pbrs.py::test_synthetic_high_fee_short_boundary_rejects_real_excess                        | Non-unit entry price exposes floating-point roundoff                                                                                                                           |
+| pbrs-synthetic-latent-price-135               | pbrs        | Clipped candidate PnL does not erase the sampled market path for later holds         | pbrs/test_pbrs.py::test_unrealized_pnl_retains_sampled_market_path_after_candidate_cap            | Equal first retained marks from distinct market prices diverge after the same adverse return                                                                                    |
 
 ### Non-Owning Smoke / Reference Checks
 
index 2b600bf663a2d15da42f4802ec4b16da1fffcf8e..7f2eb92e0967f7b0aadf29bc2f8d271dd129ecb4 100644 (file)
@@ -296,6 +296,148 @@ class TestSimulationParity(RewardSpaceTestBase):
                         places=8,
                     )
 
+    def test_synthetic_high_fee_winner_retains_profitable_mark(self):
+        """Keep favorable retained PnL and exit reward positive with high fees.
+
+        **Invariant:** pbrs-synthetic-profitable-mark-133
+        """
+        for direction, innovation in (("long", 0.299), ("short", -0.299)):
+            for flat_first in (False, True):
+                with self.subTest(direction=direction, flat_first=flat_first):
+                    params = self.base_params(
+                        unrealized_pnl=True,
+                        entry_fee_rate=0.1,
+                        exit_fee_rate=0.1,
+                        max_trade_duration_candles=1,
+                    )
+                    enter = Actions.Long_enter if direction == "long" else Actions.Short_enter
+                    exit_action = Actions.Long_exit if direction == "long" else Actions.Short_exit
+                    actions = [(enter, 1.0, 0.0, 0.0)]
+                    # The directional drift completes a 30% favorable market move.
+                    innovations = [innovation]
+                    if flat_first:
+                        actions.append((Actions.Neutral, 0.0, 0.0, 1.0))
+                        innovations = [-0.001 if direction == "long" else 0.001, innovation]
+                    actions.append((exit_action, 0.0, 1.0, 0.0))
+                    innovations.append(0.0)
+                    with (
+                        patch.object(reward_space_analysis, "_sample_action", side_effect=actions),
+                        patch.object(
+                            reward_space_analysis.random.Random,
+                            "gauss",
+                            side_effect=innovations,
+                        ),
+                    ):
+                        samples = simulate_samples(
+                            num_samples=len(actions),
+                            seed=SEEDS.BASE,
+                            params=params,
+                            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,
+                        )
+                    if flat_first:
+                        self.assertLess(float(samples.iloc[0]["next_pnl"]), 0.0)
+                    self.assertGreater(float(samples.iloc[-2]["next_pnl"]), 0.0)
+                    self.assertGreater(float(samples.iloc[-1]["exit_pnl"]), 0.0)
+                    self.assertGreater(float(samples.iloc[-1]["reward_exit"]), 0.0)
+
+    def test_synthetic_high_fee_short_boundary_rejects_real_excess(self):
+        """Accept rounding at the fee bound but reject a material loss beyond it.
+
+        **Invariant:** pbrs-synthetic-fee-boundary-134
+        """
+        params = self.base_params(
+            entry_fee_rate=0.1,
+            exit_fee_rate=0.1,
+            max_trade_duration_candles=1,
+        )
+        with (
+            patch.object(
+                reward_space_analysis,
+                "_sample_action",
+                side_effect=[
+                    (Actions.Neutral, 0.0, 0.0, 1.0),
+                    (Actions.Short_enter, 1.0, 0.0, 0.0),
+                    (Actions.Short_exit, 0.0, 1.0, 0.0),
+                ],
+            ),
+            patch.object(
+                reward_space_analysis.random.Random,
+                "gauss",
+                # Neutral move sets entry price to 1.03; next move cancels short drift.
+                side_effect=[0.03, 0.001, 0.0],
+            ),
+        ):
+            samples = simulate_samples(
+                num_samples=3,
+                seed=SEEDS.BASE,
+                params=params,
+                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,
+            )
+        fee_factor = (1.0 + params["entry_fee_rate"]) * (1.0 + params["exit_fee_rate"])
+        self.assertAlmostEqual(float(samples.iloc[-1]["pnl"]), 1.0 - fee_factor, places=12)
+        beyond_bound = samples.copy()
+        beyond_bound.loc[beyond_bound.index[-1], "pnl"] = 1.0 - fee_factor - 0.001
+        with self.assertRaisesRegex(AssertionError, "extreme PnL"):
+            reward_space_analysis._validate_simulation_invariants(beyond_bound, params)
+
+    def test_unrealized_pnl_retains_sampled_market_path_after_candidate_cap(self):
+        """Keep distinct market paths after their first retained PnL is capped.
+
+        **Invariant:** pbrs-synthetic-latent-price-135
+        """
+        params = self.base_params(unrealized_pnl=True, max_trade_duration_candles=1)
+        actions = [
+            (Actions.Long_enter, 1.0, 0.0, 0.0),
+            (Actions.Neutral, 0.0, 0.0, 1.0),
+            (Actions.Long_exit, 0.0, 1.0, 0.0),
+        ]
+
+        def sample(first_return: float) -> pd.DataFrame:
+            with (
+                patch.object(reward_space_analysis, "_sample_action", side_effect=actions),
+                patch.object(
+                    reward_space_analysis.random.Random,
+                    "gauss",
+                    # Both first candidates clip at +0.15; the following move is -30%.
+                    side_effect=[first_return, -0.301, 0.0],
+                ),
+            ):
+                return simulate_samples(
+                    num_samples=len(actions),
+                    seed=SEEDS.BASE,
+                    params=params,
+                    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,
+                )
+
+        lower_price = sample(0.599)
+        higher_price = sample(0.949)
+        self.assertAlmostEqual(
+            float(lower_price.iloc[0]["next_pnl"]), float(higher_price.iloc[0]["next_pnl"])
+        )
+        self.assertGreater(float(lower_price.iloc[1]["next_pnl"]), 0.0)
+        self.assertGreater(
+            float(higher_price.iloc[1]["next_pnl"]),
+            float(lower_price.iloc[1]["next_pnl"]),
+        )
+
     def test_unrealized_pnl_uses_each_sampled_market_move(self):
         """Later Gaussian innovations affect later retained PnL without becoming extrema."""
         params = self.base_params(