Live news gate: stop capping signal confidence near events (XAU default)

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Harwinder Sekhon
2026-07-11 10:02:27 -04:00
parent 2842351f9b
commit 02bd61f378
4 changed files with 58 additions and 3 deletions
+8
View File
@@ -168,6 +168,14 @@ class NewsConfig:
tracked_currencies: List[str] = field(default_factory=lambda: ["USD", "XAU", "EUR"]) tracked_currencies: List[str] = field(default_factory=lambda: ["USD", "XAU", "EUR"])
pre_event_buffer_minutes: int = 30 pre_event_buffer_minutes: int = 30
post_event_buffer_minutes: int = 15 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 # Wider stay-out window after a high-impact release that surprised
# vs forecast (inline releases use post_event_buffer_minutes). # vs forecast (inline releases use post_event_buffer_minutes).
post_event_surprise_buffer_minutes: int = 45 post_event_surprise_buffer_minutes: int = 45
+5
View File
@@ -114,6 +114,11 @@ def make_config(
# belt-and-braces so no gold logic can ever fire on an EUR config. # belt-and-braces so no gold logic can ever fire on an EUR config.
config.xau_strategy.enabled = False 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_start_hour = 7
config.backtest.session_end_hour = 20 config.backtest.session_end_hour = 20
config.backtest.blocked_hours_utc = [22, 23, 0, 1, 2, 3] config.backtest.blocked_hours_utc = [22, 23, 0, 1, 2, 3]
+8 -3
View File
@@ -1138,10 +1138,14 @@ class SignalAggregator:
confidence += 5 confidence += 5
# ── Event risk (hard cap, separate from penalty system) ─── # ── 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": if report.event_risk == "danger":
report.warnings.append(f"EVENT RISK: {report.event_description}") report.warnings.append(f"EVENT RISK: {report.event_description}")
confidence = min(confidence, 30) if self.config.news.event_risk_caps_signals:
report.action_recommendation = f"WAIT — {report.event_description}" confidence = min(confidence, 30)
report.action_recommendation = f"WAIT — {report.event_description}"
# ── Penalties (diminishing cap system) ──────────────────── # ── Penalties (diminishing cap system) ────────────────────
# Collect all applicable penalties, then apply with exponential # Collect all applicable penalties, then apply with exponential
@@ -1163,7 +1167,8 @@ class SignalAggregator:
if report.event_risk == "caution": if report.event_risk == "caution":
report.warnings.append(f"Upcoming event: {report.event_description}") 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 = ( is_counter_trend = (
(report.market_regime == "trending_up" and score < 0) or (report.market_regime == "trending_up" and score < 0) or
+37
View File
@@ -1203,6 +1203,43 @@ def _():
assert report.action_recommendation != "" 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") @test("SignalAggregator._generate_trade_setup BUY direction")
def _(): def _():
config = PlatformConfig() config = PlatformConfig()