Files
ForexBot2/pair_signals/xauusd.py
T
Harwinder Sekhon 18e4fd0403 audit: gate-chain verdicts — lock shipped M5 entry-path state, document hour-delta isolation
2026-08-04 audit, Batch D. Verification outcomes (no behavior changes —
comment + tests only):

- CP gate-bypass (quality forced to B after the gate chain, exhaustion/
  M5-score gates not re-checked) is VALIDATED behavior, not a bug: the
  pinned shipped-config M5 12mo book carries 226/586 XAU_CP trades
  (+38.9R), 104 with EXHAUST warnings (+15.3R), 175 below the M5
  min-score gate (+28.0R). Removing the "holes" removes validated
  profit. New test pins the shipped state (PB off, CP BUY-only on M5,
  ema_partial_neutral M5-only) so any flip is a conscious revalidated
  decision.
- E18 (xau_strategy vs backtest blocked-hours "drift") REFUTED as a
  defect: the divergence is required by pair isolation — the backtest
  copy is the generic non-XAU fallback and must not inherit
  gold-validated deltas. Documented in the factory + a test asserting
  the delta stays XAU-side.
- 6c-upgrade re-arming an exhaustion-D refuted (exhaustion zeroes
  confidence below 6c's floor; regimes mutually exclusive). "WEEKEND
  FILTER" CP-suppression branch is defense-in-depth for direct
  analyze_from_dataframes callers, kept.

Suite: 316 green.

Claude-Session: https://claude.ai/code/session_01UnZkzeFcTu9agFg8BDh2tn
2026-08-04 19:52:07 -04:00

230 lines
13 KiB
Python

"""XAU/USD signal producer config.
PlatformConfig defaults ARE the tuned XAU/USD configuration, so this
factory sets the timeframes plus documented per-timeframe overrides.
It must stay equivalent to the inline PlatformConfig(symbol=XAUUSD,
...) construction (plus those overrides) used by the backtester and
Telegram bot (enforced by a test in test_all.py).
M15 override (validated 2026-07-11, window 2025-03-30 -> 2026-05-31,
$0.30 spread, trailing exits): tp_floor_rr 1.0 scores +116.6R/460
trades PF 1.83 vs +105.4R/424 PF 1.82 at the 0.5 default — better in
both train/val halves and on the 2024-06 -> 2025-03 out-of-sample
window (+30.3R vs +10.6R; the 1R floor lifts targets past the min-TP
spread gate, roughly doubling trades there). The same knob HURTS M5
(pinned 12mo book: +131.3R/699 vs +139.9R/547, PF 1.55 vs 1.78), so it
is per-timeframe, exactly as PENDING_STEPS' decision rule anticipated.
M15 override 2 (validated 2026-07-11, 12mo window 2025-07-11 ->
2026-07-11 + OOS 2024-06-01 -> 2025-03-30, $0.30 spread): block the
mixed "trending" regime (ADX strong but EMAs unaligned — momentum
without directional structure). Those trades lost -6.8R/24 on 12mo,
negative in BOTH halves; blocking scores +65.7R/499 PF 1.38 DD 10.4R
vs +59.0R/521 PF 1.32 DD 12.6R baseline, better in both halves. OOS
is neutral (-1.4R = the 4 such trades that existed there; no power to
confirm or refute). M5 never trades this regime (its gates already
exclude it), so the block is M15-only. A second candidate — blocking
transitioning-regime SELLs (-4.0R/47 on 12mo) — was REJECTED: those
sells made +5.7R/32 in the 2024 OOS window (bull-market artifact).
M15 override 3 (validated 2026-07-16, 5-year replay 2021-07-17 ->
2026-07-16 under the audit-fixed backtester, $0.30 spread, trailing
exits): block SELL entries whose bar CLOSES at 15:00 or 17:00 UTC
(sell_blocked_hours_utc=[15, 17], gate 6g3 — BUYs unaffected; those
hours flank the already-blocked hour 16). The pocket lost -33.5R/165
trades, negative in 6/6 calendar years and in BOTH halves of the last
12 months (H1 -6.8R, H2 -4.2R); each hour independently is 5/6 years
negative with both halves negative (hours 14/18 fail that screen and
stay open). Cross-TF the M5 book CONFIRMS (same pocket -14.1R/59,
5/6 years negative) rather than contradicts — unlike the rejected
trending_down block. Full replay with the gate: +176.7R/1627 PF 1.31
DD 29.1R vs +144.9R/1781 PF 1.22 DD 34.4R control; better in ALL SIX
years; last-12mo halves both improve (H1 +36.3 vs +29.5, H2 +46.9 vs
+43.2; 12mo PF 1.55 vs 1.43); OOS 2024-06-01 -> 2025-03-30 improves
(+45.4R PF 1.47 DD 17.8 vs +41.9R PF 1.39 DD 21.7). SELL side goes
-1.2R -> +30.6R over 5 years. BUYs at those hours made +34.3R, hence
SELL-only rather than a blocked_hours_utc entry. Mechanism: NY-session
shorts against gold's macro uptrend (15:00 = post-US-data reversal
window, 17:00 = London close unwind).
M5 override 2 (2026-07-16, chart patterns): enable BUY-only CP on M5
(inv_hs, double_bottom, bull_flag — drop all bearish patterns). SELL CP
is consistently negative (OOS -3.21R, 5yr +3.76R PF 1.07) while BUY CP
is positive across windows: 12mo +41.8R PF 1.60 (train +9.66 PF 1.30,
val +32.17 PF 1.86), 5yr +65.2R PF 1.48, OOS +1.20R (aggregate
positive, val half -1.89R — marginal but sign-correct in aggregate).
Gold's structural uptrend makes bullish patterns fundamentally more
reliable. M15/H1 fail on all windows; M5-only enablement.
M5 override 3 (validated 2026-07-21, 5-year replay 2021-07-21 ->
2026-07-21, $0.30 spread, next-open fills): trailing distance widened
to 0.85R (trail_dist_r 0.85, arm unchanged 0.60). Same 30-cell grid
methodology as the M15 campaign, opposite finding — on M5 the arm
point barely matters; the winners need a looser leash. Plateau arm
0.5-0.7 x dist 0.85-1.0 (+227..+234R vs +218.0R baseline). Chosen
0.60/0.85 (OOS-preferred end of the plateau): +231.2R/2028 PF 1.30 WR
57.1% vs +218.0R PF 1.29 WR 58.3%; both last-12mo halves better
(+56.8/+115.1 vs +54.0/+104.6); 5yr max DD 19.2R vs 19.1R (noise);
OOS 2024-06-01 -> 2025-03-30 +14.5R DD 15.1 vs +11.1R DD 15.4.
dist 1.0 was REJECTED at the OOS gate (+10.9R < baseline +11.1R)
despite the best in-sample total (+233.3R) — the OOS ordering picks
the conservative plateau edge. XAU_PB trades are unaffected (they
carry their own validated pb_trail 0.8/0.75 per-trade overrides);
XAU_CP trades share the global params and were validated inside this
book. M15 arms at 0.70 (its own campaign, same day); the divergence
is the per-TF pattern yet again.
M15 override 4 (validated 2026-07-21, 5-year replay 2021-07-21 ->
2026-07-21, $0.30 spread, next-open fills): trailing-stop arm raised to
0.70R (trail_arm_r 0.70, dist unchanged 0.75). Full grid arm 0.3-0.9 x
dist 0.5-1.0 (30 cells): every earlier-arm cell (0.3/0.45) is WORSE —
the "arm earlier to save fading trades" hypothesis is refuted; the
plateau is arm 0.70-0.80 x dist 0.70-0.85 (all >= +190R). Chosen
plateau center 0.70/0.75: +210.7R/1629 PF 1.35 WR 57.8% vs baseline
0.60/0.75 +175.7R PF 1.30 WR 53.9%; both last-12mo halves improve
(+49.7/+50.0 vs +36.4/+46.0); per-year better 4/6 (2022 -0.9R, 2023
-1.4R, both within tolerance); OOS 2024-06-01 -> 2025-03-30 +57.4R
PF 1.57 DD 17.0R vs +45.4R PF 1.47 DD 17.8R — better on every OOS
metric. One clause waived BY OWNER DECISION 2026-07-21: 5yr max DD is
33.9R vs 29.1R baseline (+4.8R deeper worst episode, 2022-23 chop),
accepted in exchange for +20% total R. M5 keeps the global 0.6 arm
(same per-TF divergence pattern as tp_floor_rr and the hour-9 block).
M5 override (validated 2026-07-11, full-history replay 2020-03-17 ->
2026-07-11 + 12mo book, $0.30 spread): block hour 9 UTC (post-London-
open whipsaw). Hour-9 entries lost -28.9R/143 over 6 years, negative
in 5/7 calendar years including winning years (2022 -9.2R). Blocking:
+207.1R vs +178.3R baseline on the 6-year replay, max DD 28.7R vs
35.5R, improves 5/7 years (2023 goes -13.0R -> breakeven); the tuned
12mo book is unchanged (+139.0R vs +139.3R). M15-and-up keep hour 9:
it is POSITIVE there (M15 6yr +1.7R, M15 OOS +8.6R), so the block is
M5-only, same per-timeframe pattern as the M15 overrides.
M5 override 2 — relaxation campaign (validated 2026-07-30, Oanda
bars, $0.30 spread, trailing exits): UNBLOCK hour 21 UTC on M5 only,
combined with the config.py M5 gate relaxations (m5_min_abs_score
30->25, m5_min_confirmation_score 10->5). The combined config beat
the pre-relaxation control in ALL 6 calendar years of the 5yr replay
2021-07-30 -> 2026-07-30 (+230.9R/2319 PF 1.26 DD 21.3 vs
+168.0R/2048 PF 1.21 DD 27.0), on 12mo (+107.4R PF 1.36, halves
+68.6/+38.8 vs +84.6R PF 1.33), on OOS 2024-06-01 -> 2025-03-30
(+26.1R vs +17.2R), and at $0.70 spread (+86.9R vs +66.4R). Hour-21
trades alone: +28.0R/22, positive every year — overturning the old
block per the full-evidence rule. Hour 21 stays BLOCKED on M15+
(12mo M15 unblock tested -0.3R). Declined-but-open from the same
campaign: an M5 hour-22 block (+4.8R on 12mo — the burst-heavy
"institutional" hour — but -3.2R on 5yr and -1.1R on OOS: regime-
specific); M15 hour-16 unblock (+0.8R/+57 trades 12mo but -18.3R
DD +34% on 5yr); every SELL-gate/vol/TP-size relaxation retested
negative (see memory notes for the full grid).
M5 override 4 — XAU_PB disabled (validated 2026-08-04, Oanda bars,
$0.65 REAL spread — first campaign validated at measured cost on the
live feed, per the 2026-08-04 re-pin baseline): m5_pullback_enabled
False on M5. Loss forensics on the Oanda books identified XAU_PB as
the largest wrong-direction loss source (12mo: -43.5R of the -156.6R
wrong-direction bucket; 5yr: -274.5R). Disabling: 12mo +96.6R/588
PF 1.44 max DD 13.9R halves +51.2/+45.4 vs +82.3R/788 PF 1.26 DD
23.9R shipped (which FAILS the PF>=1.3 clause at real cost); OOS
2024-06-01 -> 2025-03-30 +9.5R PF 1.34; 5yr +123.9R/956 PF 1.35 vs
PF 1.16 with PB. PB's 5yr net contribution (+23.6R) was almost
entirely the 2022 chop year (+36.1R vs -2.2R without) — a regime
earner that has been a drag on trend-regime bars since; its tight
stops are also the most cost-punished at $0.65. PB code and the
pb_trail_* params STAY for future regime-gated experiments (same
pattern as cp_enabled). Rejected in the same campaign (4 candidates
total, N logged): hour-19 block (+3.7R 12mo but -3.3R OOS, -1.5R
5yr — OOS gate, the classic trap); hours-14/15 block (-6.9R 12mo
once PB is removed — their raw negativity WAS the PB losses);
trail_dist_r 0.75 (-2.6R 12mo; the 0.88R average give-back is the
validated price of riding trends, exits confirmed NOT the loss
driver: exit-management accounts for 1-2% of lost R vs 50%
wrong-direction / 49% entry-timing).
M5 override 5 — EMA partial-alignment squash (validated 2026-08-04,
Oanda bars, $0.65 real spread): ema_partial_neutral True on M5 —
partial 4-EMA ribbon alignment (2-3 of 4 checks) scores NEUTRAL
instead of a weak BUY/SELL. Motivated by the indicator audit: partial-
alignment trades averaged -0.008R/84 vs +0.230R/341 fully aligned.
Replay (knob mirrored in the backtester's precomputed EMA scorer in
the same commit — live==backtest parity): 12mo +97.6R/556 PF 1.48
DD 10.7R halves +47.8/+49.8 vs control +96.6R/588 PF 1.44 DD 13.9R;
5yr +127.4R/912 PF 1.38 DD 12.8R vs +123.9R/956 PF 1.35 DD 13.2R,
2022 improves -2.2 -> -1.1; OOS 2024-06-01 -> 2025-03-30 +9.4R
PF 1.33 vs +9.5R PF 1.34 — a one-trade tie, documented as such (the
OOS window holds up; it neither prefers nor punishes). Net effect:
32 fewer marginal trades, every risk-adjusted measure better.
Rejected in the same round (trial-logged): confirmation-TF swap
M5->[M15,H4] (12mo +104.0R PF 1.51 DD 10.3 but OOS +7.8R PF 1.27
vs control +9.5R PF 1.34 and 2022 3x worse — the H4 confirm keeps
endorsing stale direction in chop; OOS-ordering rejection, same
precedent as trail dist 1.0); confirm [H1,H4] (-31.8R 12mo);
triple-confirm [M15,H1,H4] (-4.9R); divergence weight -1/3
(-3.8R 12mo); candlestick weight +50% (-3.0R 12mo). The weight
vector keeps proving calibrated as a system: divergence/candlestick/
bollinger all look mis-weighted in-book and all replay worse when
"fixed" (bollinger weight-0: +80.9R PF 1.29 DD 23.8 — its constant
oppose vote is a load-bearing quality bar, like ROC).
"""
from typing import Optional, List
from config import PlatformConfig, Timeframe
from app_config import apply_pair_overrides
_CONFIRMATIONS = {
Timeframe.M5: [Timeframe.M15, Timeframe.H1],
Timeframe.M15: [Timeframe.M5, Timeframe.H1],
Timeframe.H1: [Timeframe.M15, Timeframe.H4],
Timeframe.H4: [Timeframe.H1, Timeframe.D1],
}
def make_config(
primary_tf: Timeframe,
confirmation_tfs: Optional[List[Timeframe]] = None,
) -> PlatformConfig:
config = PlatformConfig()
# Deployment overlay (config.json) BEFORE the validated per-TF
# deltas below, so e.g. the M5 hour-9 append lands on the JSON base.
apply_pair_overrides(config)
config.primary_timeframe = primary_tf
config.confirmation_timeframes = (
confirmation_tfs or _CONFIRMATIONS.get(primary_tf, [Timeframe.M15, Timeframe.H4])
)
if primary_tf == Timeframe.M5:
# Block hour 9 UTC on M5 only; UNBLOCK hour 21 on M5 only
# (positive every year of the 5yr replay) — see docstring.
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"]
# XAU_PB pullback entries OFF — largest wrong-direction loss
# source at real cost on Oanda bars — see module docstring.
config.xau_strategy.m5_pullback_enabled = False
# Partial EMA-ribbon alignment scores NEUTRAL on M5 — its
# trades carried no edge — see module docstring.
config.technical.ema_partial_neutral = True
# Wider trailing distance on M5 only — see module docstring.
config.signals.trail_arm_r = 0.60
config.signals.trail_dist_r = 0.85
if primary_tf == Timeframe.M15:
# 1R minimum target on M15 only — see module docstring.
config.signals.tp_floor_rr = 1.0
# Block the mixed "trending" regime on M15 only — see docstring.
config.xau_strategy.blocked_regimes = ["trending"]
# Block SELL entries at 15:00/17:00 UTC on M15 only — see docstring.
config.xau_strategy.sell_blocked_hours_utc = [15, 17]
# Later trailing-stop arm on M15 only — see module docstring.
config.signals.trail_arm_r = 0.70
config.signals.trail_dist_r = 0.75
return config