diff --git a/pair_signals/xauusd.py b/pair_signals/xauusd.py index 1eaaea7..0089534 100644 --- a/pair_signals/xauusd.py +++ b/pair_signals/xauusd.py @@ -197,6 +197,13 @@ def make_config( config.xau_strategy.blocked_hours_utc = [ h for h in config.xau_strategy.blocked_hours_utc if h != 21 ] + [9] + # Deliberately NOT mirrored into config.backtest.blocked_hours_utc: + # that copy is the generic non-XAU fallback carrying the JSON base + # only. Propagating a gold-validated per-TF delta there would leak + # XAU-calibrated hours to a future pair (pair-isolation rule). + # Every XAU consumer branches to the xau_strategy copy. Enforced + # by the Module 13 test "M5 factory appends hour 9 on top of a + # JSON base". # BUY-only chart patterns on M5 — see module docstring. config.xau_strategy.cp_enabled = True config.xau_strategy.cp_patterns = ["inv_hs", "double_bottom", "bull_flag"] diff --git a/test_all.py b/test_all.py index 3e6029d..41adc32 100644 --- a/test_all.py +++ b/test_all.py @@ -5573,6 +5573,40 @@ def _(): assert gpu_eng.fallback_count == 0, "sweep must never miss the cache" +@test("factory: M5 per-TF hour delta stays XAU-side (pair isolation)") +def _(): + # 2026-08-04 audit E18 verdict: the xau_strategy/backtest hour lists + # SHOULD diverge after the M5 factory — the backtest copy is the + # generic non-XAU fallback and must never inherit gold-validated + # deltas. All XAU consumers branch to the xau_strategy copy + # (signal_aggregator.py session gates, gpu_backtest outer filter). + from pair_signals.xauusd import make_config as _mk + cfg = _mk(Timeframe.M5) + assert 9 in cfg.xau_strategy.blocked_hours_utc + assert 21 not in cfg.xau_strategy.blocked_hours_utc + assert 9 not in cfg.backtest.blocked_hours_utc, \ + "gold-validated hour-9 delta must NOT leak into the generic copy" + assert 21 in cfg.backtest.blocked_hours_utc + + +@test("factory: shipped M5 entry-path state is locked (PB off, CP BUY-only)") +def _(): + # 2026-08-04 audit: the CP path bypassing exhaustion/M5-score gates is + # VALIDATED behavior (226/586 trades, +38.9R in the pinned 12mo book). + # This test pins the shipped state so any flip is a conscious, + # revalidated decision — not an accident. + from pair_signals.xauusd import make_config as _mk + m5 = _mk(Timeframe.M5) + assert m5.xau_strategy.m5_pullback_enabled is False + assert m5.xau_strategy.cp_enabled is True + assert m5.xau_strategy.cp_patterns == ["inv_hs", "double_bottom", "bull_flag"] + assert m5.technical.ema_partial_neutral is True + for tf in (Timeframe.M15, Timeframe.H1, Timeframe.H4): + cfg = _mk(tf) + assert cfg.xau_strategy.cp_enabled is False, f"{tf}: CP must stay off" + assert cfg.technical.ema_partial_neutral is False + + @test("GPU parity: cache miss falls back LOUDLY (counter + warning)") def _(): import gpu_backtest as _gb