M15 trail arm 0.70 (validated grid) + backtest/live parity hardening

Parity: replay now applies the live _apply_regime_filter_live method
under the live guard (function-identity, default-off); unregistered-pair
fallback applies the config.json overlay; parity sweep gains direct
ATR/vol-regime assertions; regime-filter semantics test added.

Trail campaign: --trail-arm/--trail-dist research flags; 30-cell grid on
5yr M15 refuted the earlier-arm hypothesis and found the 0.70-0.80 arm
plateau. Shipped M15-only trail_arm_r=0.70 (factory override):
+210.7R PF 1.35 WR 57.8% vs +175.7R PF 1.30 WR 53.9% baseline; both
halves and 2024-06->2025-03 OOS better (+57.4 vs +45.4); 5yr max-DD
clause (33.9R vs 29.1R) explicitly waived by owner. M5 unchanged.
Factory replay reproduces the grid cell trade-for-trade; 201/201 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPNF37MP9Kuv62B1VBrZ1b
This commit is contained in:
Harwinder Sekhon
2026-07-21 16:40:33 -04:00
parent 11ff49befb
commit bf8e5fbf39
5 changed files with 101 additions and 2 deletions
+19
View File
@@ -1530,6 +1530,13 @@ def _replay_span(span: Tuple[int, int]):
use_news=False,
)
# Live parity: analyze_pair applies the optional higher-TF ADX
# regime filter after delegation, under this exact guard. Calling
# the SAME method on the SAME sliced dfs keeps the two paths
# identical if the (default-off) filter is ever enabled.
if platform_cfg.backtest.regime_filter:
aggregator._apply_regime_filter_live(report, slice_dfs)
if report.trade_setup is None:
continue
if report.signal_quality not in bt_cfg.quality_filter:
@@ -2141,6 +2148,10 @@ def _parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
p.add_argument("--max-hold-bars", type=int, default=300)
p.add_argument("--swing-sr-lookback", type=int, default=0,
help="Override TechnicalConfig.swing_sr_lookback (0 = use config default)")
p.add_argument("--trail-arm", type=float, default=None,
help="Override signals.trail_arm_r for this run (research knob)")
p.add_argument("--trail-dist", type=float, default=None,
help="Override signals.trail_dist_r for this run (research knob)")
p.add_argument("--quality", default="A+,A,B,C", help="Comma-separated allowed grades")
p.add_argument("--strategies", default="",
help="Comma-separated strategy tags to include (XAU,XAU_PB,XAU_CP,GENERIC). Empty = all.")
@@ -2214,8 +2225,16 @@ def main(argv: Optional[List[str]] = None) -> int:
primary_timeframe=primary_tf,
confirmation_timeframes=confirm_tfs or [Timeframe.H1, Timeframe.H4],
)
# The factory path applies the config.json overlay inside
# make_config; the fallback must match (live == backtest).
from app_config import apply_pair_overrides # noqa: E402
apply_pair_overrides(platform_cfg)
if args.swing_sr_lookback > 0:
platform_cfg.technical.swing_sr_lookback = args.swing_sr_lookback
if args.trail_arm is not None:
platform_cfg.signals.trail_arm_r = args.trail_arm
if args.trail_dist is not None:
platform_cfg.signals.trail_dist_r = args.trail_dist
if args.cp:
platform_cfg.xau_strategy.cp_enabled = True
if args.cp_patterns: