From 183f148bb4ee3ab0702fffd08ae48bd2560b8c2f Mon Sep 17 00:00:00 2001 From: Harwinder Sekhon Date: Sat, 11 Jul 2026 10:02:27 -0400 Subject: [PATCH] Live news gate: stop capping signal confidence near events (XAU default) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaying the live danger/caution gates against the validated 12mo XAU M5 book showed they suppress 25-35 trades worth +4.6..+7.2R/yr — trades entered near USD releases outperform the book average (+0.475R vs +0.256R). New NewsConfig.event_risk_caps_signals (default False = XAU-tuned) keeps EVENT RISK warnings in alerts but no longer guts confidence; EUR factory re-enables the caps (evidence is gold-specific). Pinned XAU M5 regression: 547 trades, +139.94R, trade-identical. Tests: 177 passed. --- config.py | 8 ++++++++ pair_signals/eurusd.py | 5 +++++ signal_aggregator.py | 11 ++++++++--- test_all.py | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index e92dccc..47ab338 100755 --- a/config.py +++ b/config.py @@ -168,6 +168,14 @@ class NewsConfig: tracked_currencies: List[str] = field(default_factory=lambda: ["USD", "XAU", "EUR"]) pre_event_buffer_minutes: int = 30 post_event_buffer_minutes: int = 15 + # Whether event risk gates the SIGNAL (danger: confidence hard-capped + # at 30; caution: 15-point penalty). Warnings are always emitted + # regardless. Default False = XAU-tuned: replaying the live gates + # against the validated 12mo XAU M5 book (547 trades, +139.9R) + # showed they suppress 25-35 trades worth +4.6..+7.2R/yr — trades + # entered near USD releases OUTPERFORM (+0.475R vs +0.256R avg). + # Audit 2026-07-11 via backtest/research/calendar_store. + event_risk_caps_signals: bool = False # Wider stay-out window after a high-impact release that surprised # vs forecast (inline releases use post_event_buffer_minutes). post_event_surprise_buffer_minutes: int = 45 diff --git a/pair_signals/eurusd.py b/pair_signals/eurusd.py index 0bb5d9b..de5e83f 100644 --- a/pair_signals/eurusd.py +++ b/pair_signals/eurusd.py @@ -114,6 +114,11 @@ def make_config( # belt-and-braces so no gold logic can ever fire on an EUR config. config.xau_strategy.enabled = False + # Keep event-risk signal gating ON for EUR (the XAU-tuned default + # turned it off after the 2026-07-11 audit — that evidence is + # gold-specific; EUR event-window trades were flat-to-negative). + config.news.event_risk_caps_signals = True + config.backtest.session_start_hour = 7 config.backtest.session_end_hour = 20 config.backtest.blocked_hours_utc = [22, 23, 0, 1, 2, 3] diff --git a/signal_aggregator.py b/signal_aggregator.py index b7fc481..4156f23 100755 --- a/signal_aggregator.py +++ b/signal_aggregator.py @@ -1138,10 +1138,14 @@ class SignalAggregator: confidence += 5 # ── Event risk (hard cap, separate from penalty system) ─── + # Warning always shown; the confidence cap is config-gated + # (news.event_risk_caps_signals) — see NewsConfig for the audit + # showing the cap costs R on XAU. if report.event_risk == "danger": report.warnings.append(f"EVENT RISK: {report.event_description}") - confidence = min(confidence, 30) - report.action_recommendation = f"WAIT — {report.event_description}" + if self.config.news.event_risk_caps_signals: + confidence = min(confidence, 30) + report.action_recommendation = f"WAIT — {report.event_description}" # ── Penalties (diminishing cap system) ──────────────────── # Collect all applicable penalties, then apply with exponential @@ -1163,7 +1167,8 @@ class SignalAggregator: if report.event_risk == "caution": report.warnings.append(f"Upcoming event: {report.event_description}") - penalties.append(("event_caution", 15)) + if self.config.news.event_risk_caps_signals: + penalties.append(("event_caution", 15)) is_counter_trend = ( (report.market_regime == "trending_up" and score < 0) or diff --git a/test_all.py b/test_all.py index 166bb1f..0718da7 100644 --- a/test_all.py +++ b/test_all.py @@ -1203,6 +1203,43 @@ def _(): assert report.action_recommendation != "" +@test("Event-risk signal gating obeys news.event_risk_caps_signals") +def _(): + config = PlatformConfig() + agg = SignalAggregator(config) + dfs = { + Timeframe.H1: make_ohlcv(300, seed=42), + Timeframe.M15: make_ohlcv(300, seed=43), + Timeframe.H4: make_ohlcv(300, seed=44), + } + report = agg.analyze_from_dataframes("XAU/USD", dfs, use_news=False) + + def recompute(risk, caps): + config.news.event_risk_caps_signals = caps + report.event_risk = risk + report.event_description = "USD Test Event in 10min" + report.warnings = [] + agg._compute_final_signal(report) + return report.final_confidence + + baseline = recompute("clear", True) + # default XAU behavior (caps off): danger warns but does not gut + conf_off = recompute("danger", False) + assert conf_off == baseline, "flag off must not change confidence" + assert any("EVENT RISK" in w for w in report.warnings), "warning must persist" + # caps on: danger hard-caps confidence at 30 + conf_on = recompute("danger", True) + assert conf_on <= 30, f"danger with caps on must cap conf at 30, got {conf_on}" + # caution with caps off is also warning-only + conf_caut_off = recompute("caution", False) + assert conf_caut_off == baseline + assert any("Upcoming event" in w for w in report.warnings) + # PlatformConfig default is off (XAU-tuned); EUR factory re-enables + assert PlatformConfig().news.event_risk_caps_signals is False + from pair_signals.eurusd import make_config as eur_make + assert eur_make(Timeframe.M15).news.event_risk_caps_signals is True + + @test("SignalAggregator._generate_trade_setup BUY direction") def _(): config = PlatformConfig()