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:
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -1138,8 +1138,12 @@ 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}")
|
||||||
|
if self.config.news.event_risk_caps_signals:
|
||||||
confidence = min(confidence, 30)
|
confidence = min(confidence, 30)
|
||||||
report.action_recommendation = f"WAIT — {report.event_description}"
|
report.action_recommendation = f"WAIT — {report.event_description}"
|
||||||
|
|
||||||
@@ -1163,6 +1167,7 @@ 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}")
|
||||||
|
if self.config.news.event_risk_caps_signals:
|
||||||
penalties.append(("event_caution", 15))
|
penalties.append(("event_caution", 15))
|
||||||
|
|
||||||
is_counter_trend = (
|
is_counter_trend = (
|
||||||
|
|||||||
+37
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user