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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
+37
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user