},
{
"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,
- **`--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
`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
# Synthetic market state
current_open = 1.0
+ sampled_open = current_open
entry_open = current_open
for _ in range(num_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
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:
)
# 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(
| 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
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(