Remove EUR/USD support — XAU/USD-only codebase

EUR/USD never had a validated edge (2026-07-11/12 verdict: no config
passed validation on M5/M15/H1 at 1-pip cost; cost-to-volatility root
cause). Removed: EurUsdStrategyConfig, pair_signals/eurusd.py, EUR
gates 6h + weekend/day filters + strategy marker + trade geometry in
signal_aggregator, EUR pair registry/aliases in main.py and
gpu_backtest.py, EUR/USD news pair mappings, test_all.py Module 10.
Generic multi-currency news keyword infra kept (GBP/JPY/EUR keyword
table is not pair code). Full EUR history remains at 7d3fd95.

Suite: 162/162 green (was 187; 25 EUR-specific tests removed).

Claude-Session: https://claude.ai/code/session_015kby7M9JwpYi8NQT2yoxAi
This commit is contained in:
Harwinder Sekhon
2026-07-16 14:06:27 -04:00
parent 665ffdda48
commit 14448c10e8
13 changed files with 75 additions and 919 deletions
+10 -464
View File
@@ -930,9 +930,9 @@ def _():
def _():
ne = NewsEngine(NewsConfig())
context = FundamentalContext()
context.news_sentiment = {"EUR": 0.5, "USD": -0.1}
context.news_sentiment = {"XAU": 0.5, "USD": -0.1}
score, desc = ne.get_pair_sentiment("XAU/USD", context)
assert score > 0, "EUR bullish + USD bearish should be net bullish for EUR/USD"
assert score > 0, "XAU bullish + USD bearish should be net bullish for XAU/USD"
assert "bullish" in desc.lower()
@@ -940,7 +940,7 @@ def _():
def _():
ne = NewsEngine(NewsConfig())
context = FundamentalContext()
context.news_sentiment = {"EUR": -0.5, "USD": 0.3}
context.news_sentiment = {"XAU": -0.5, "USD": 0.3}
score, desc = ne.get_pair_sentiment("XAU/USD", context)
assert score < 0
@@ -949,7 +949,7 @@ def _():
def _():
ne = NewsEngine(NewsConfig())
context = FundamentalContext()
context.news_sentiment = {"EUR": 0.0, "USD": 0.0}
context.news_sentiment = {"XAU": 0.0, "USD": 0.0}
score, desc = ne.get_pair_sentiment("XAU/USD", context)
assert score == 0.0
assert "neutral" in desc.lower()
@@ -1236,10 +1236,8 @@ def _():
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
# PlatformConfig default is off (XAU-tuned)
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")
@@ -1472,17 +1470,6 @@ def _():
assert config.primary_timeframe is not None
@test("build_config resolves EURUSD pair to EUR factory config")
def _():
class FakeArgs:
pair = "EURUSD"
timeframe = "M15"
config = build_config(FakeArgs())
assert config.symbol == "EUR/USD"
assert config.pip_value == 0.0001
assert config.primary_timeframe == Timeframe.M15
# ══════════════════════════════════════════════════════════════════════
# MODULE 7: telegram_bot.py
# ══════════════════════════════════════════════════════════════════════
@@ -1821,12 +1808,12 @@ def _():
print("\n═══ Module 9: pair_signals registry ═══")
@test("pair_signals registry includes XAU/USD and EUR/USD")
@test("pair_signals registry includes XAU/USD only")
def _():
import pair_signals
pairs = pair_signals.get_supported_pairs()
assert XAUUSD in pairs, f"XAU missing from registry: {pairs}"
assert "EUR/USD" in pairs, f"EUR missing from registry: {pairs}"
assert pairs == [XAUUSD], f"registry must be XAU-only: {pairs}"
@test("xauusd.make_config equals inline PlatformConfig construction")
@@ -1863,18 +1850,6 @@ def _():
f"XAU factory config differs from inline construction for {tf}"
@test("eurusd.make_config produces EUR config with XAU strategy disabled")
def _():
from pair_signals import PAIR_PRODUCERS
cfg = PAIR_PRODUCERS["EUR/USD"](Timeframe.M5, None)
assert cfg.symbol == "EUR/USD"
assert cfg.pip_value == 0.0001
assert cfg.xau_strategy.enabled is False
assert cfg.eur_strategy.enabled is True
assert cfg.technical.regime_weights_override, "EUR regime weights not applied"
assert cfg.confirmation_timeframes == [Timeframe.M15, Timeframe.H1]
@test("produce_signal raises for unregistered pair")
def _():
from pair_signals import produce_signal
@@ -1943,7 +1918,7 @@ def _():
df = make_ohlcv(n=100, seed=42)
df.iloc[-11, df.columns.get_loc("Close")] = 0.0
engine = TechnicalEngine(PlatformConfig().technical)
result = engine.analyze(df, "EUR/USD", Timeframe.H1)
result = engine.analyze(df, "XAU/USD", Timeframe.H1)
if "roc" in result.indicators:
assert np.isfinite(result.indicators["roc"].value), "ROC should not be inf"
@@ -1959,440 +1934,11 @@ def _():
"Close": [100.0]*n, "Volume": [1000.0]*n,
}, index=dates)
engine = TechnicalEngine(PlatformConfig().technical)
result = engine.analyze(df, "EUR/USD", Timeframe.H1)
result = engine.analyze(df, "XAU/USD", Timeframe.H1)
if "adx" in result.indicators:
assert np.isfinite(result.indicators["adx"].value), "ADX should be finite on flat data"
print("\n═══ Module 10: EUR/USD Support ═══")
from config import EURUSD, EurUsdStrategyConfig
# ── 10a. Config & registry ──────────────────────────────────────────
@test("PAIR_INFO includes EUR/USD with correct pip_value")
def _():
assert EURUSD in PAIR_INFO
assert PAIR_INFO[EURUSD]["pip_value"] == 0.0001
assert PAIR_INFO[EURUSD]["is_jpy_pair"] is False
assert PAIR_INFO[EURUSD]["display"] == "EUR/USD"
assert PAIR_INFO[EURUSD]["price_precision"] == 5
assert PAIR_INFO[EURUSD]["default_spread"] == 0.0001
@test("EurUsdStrategyConfig defaults are sensible")
def _():
cfg = EurUsdStrategyConfig()
assert cfg.enabled is True
assert 0 in cfg.blocked_hours_utc
assert 5 not in cfg.allowed_weekdays # No Saturday
assert 6 not in cfg.allowed_weekdays # No Sunday
assert cfg.min_atr_threshold == 0.0003
assert cfg.min_rr_ratio == 1.0
assert cfg.mature_move_threshold == 35.0
assert cfg.blocked_regimes == ["ranging", "transitioning"]
assert "A" in cfg.min_quality_grades
# EUR-local cost knobs must be pip-scale, not gold-dollar-scale
assert cfg.round_trip_cost == 0.0001
assert cfg.round_trip_cost * cfg.min_tp_cost_multiple < 0.01
assert cfg.min_sl_pips == 8.0
@test("PlatformConfig has eur_strategy field")
def _():
config = PlatformConfig()
assert hasattr(config, "eur_strategy")
assert isinstance(config.eur_strategy, EurUsdStrategyConfig)
@test("EUR/USD config factory produces correct config")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
assert config.symbol == EURUSD
assert config.display_name == "EUR/USD"
assert config.pip_value == 0.0001
assert config.xau_strategy.enabled is False
assert config.technical.regime_weights_override
assert "trending" in config.technical.regime_weights_override
assert "ranging" in config.technical.regime_weights_override
@test("XAU/USD config factory keeps XAU strategy enabled")
def _():
from pair_signals.xauusd import make_config
config = make_config(Timeframe.H1)
assert config.symbol == XAUUSD
assert config.display_name == "XAU/USD"
assert config.pip_value == 0.10
assert config.xau_strategy.enabled is True
@test("Mixed 'trending' regime: RSI mid-zone is neutral, extremes only")
def _():
from engine import TechnicalEngine, TechnicalAnalysis, IndicatorSignal
from config import TechnicalConfig
eng = TechnicalEngine(TechnicalConfig())
# Pre-fix, mixed "trending" used the uptrend playbook: RSI 45 became
# STRONG_BUY conf 75 in a regime whose EMAs disagree on direction.
for rsi_v, expected in ((45.0, SignalStrength.NEUTRAL),
(75.0, SignalStrength.SELL),
(25.0, SignalStrength.BUY)):
res = TechnicalAnalysis(symbol="XAU/USD", timeframe="1h", timestamp="t")
res.market_regime = "trending"
res.indicators["rsi"] = IndicatorSignal(
name="RSI", value=rsi_v, signal=SignalStrength.NEUTRAL,
confidence=30, description="", weight=1.5)
eng._reinterpret_rsi_for_regime(res)
got = res.indicators["rsi"].signal
assert got == expected, f"RSI {rsi_v} in mixed regime → {got}, want {expected}"
@test("XAU regime filter: blocked regime never yields actionable quality")
def _():
from pair_signals.xauusd import make_config
config = make_config(Timeframe.M15)
config.backtest.session_filter = False
config.xau_strategy.blocked_regimes = [
"trending", "trending_up", "trending_down", "ranging", "transitioning"]
agg = SignalAggregator(config)
df = make_ohlcv(n=300, start_price=3300.0, trend=0.5, seed=7)
dfs = {Timeframe.M15: df, Timeframe.M5: df, Timeframe.H1: df}
report = agg.analyze_from_dataframes(symbol=XAUUSD, dfs=dfs, use_news=False)
assert report.signal_quality not in ("A+", "A", "B", "C"), \
f"blocked regime must not stay actionable, got {report.signal_quality}"
# The M15 factory opts into the mixed-"trending" block; M5 must not.
assert make_config(Timeframe.M15).xau_strategy.blocked_regimes == ["trending"]
assert make_config(Timeframe.M5).xau_strategy.blocked_regimes == []
@test("XAU SELL hour gate: blocks 15/17 UTC bar-close entries, spares others")
def _():
from pair_signals.xauusd import make_config
def xau_15min_df(end):
np.random.seed(9)
dates = pd.date_range(end=pd.Timestamp(end, tz="UTC"),
periods=300, freq="15min")
close = 3300.0 + np.cumsum(np.random.randn(300) * 2.0 - 1.0)
return pd.DataFrame({
"Open": close + np.random.randn(300) * 0.5,
"High": close + np.abs(np.random.randn(300)),
"Low": close - np.abs(np.random.randn(300)),
"Close": close,
"Volume": np.random.randint(100, 10000, 300).astype(float),
}, index=pd.DatetimeIndex(dates, name="Datetime"))
def run(end, hours):
cfg = make_config(Timeframe.M15)
cfg.backtest.session_filter = False
# Neutralize the other SELL gates so 6g3 is the one under test.
cfg.xau_strategy.sell_min_quality = ["A+", "A", "B", "C"]
cfg.xau_strategy.sell_vol_regimes = ["low", "normal", "high", "extreme"]
cfg.xau_strategy.blocked_regimes = []
cfg.xau_strategy.sell_blocked_hours_utc = hours
df = xau_15min_df(end)
dfs = {Timeframe.M15: df, Timeframe.M5: df, Timeframe.H1: df}
return SignalAggregator(cfg).analyze_from_dataframes(
symbol=XAUUSD, dfs=dfs, use_news=False)
# Same synthetic downtrend; only the index end (and thus the bar-close
# hour) differs. Label 14:45 closes 15:00 (blocked); 19:45 closes
# 20:00 (allowed). The gate reads bar-CLOSE time, not the label hour.
control = run("2026-07-13 14:45", [])
assert control.signal_quality in ("A+", "A", "B", "C") and control.final_score < 0, \
f"probe data must yield an actionable sell, got {control.signal_quality}"
blocked = run("2026-07-13 14:45", [15, 17])
assert blocked.signal_quality == "D" and blocked.trade_setup is None, \
f"15:00 UTC close must be gated to D, got {blocked.signal_quality}"
assert any("blocked for shorts" in w for w in blocked.warnings), \
f"missing SELL hour warning: {blocked.warnings}"
open_hour = run("2026-07-13 19:45", [15, 17])
assert open_hour.signal_quality == control.signal_quality, \
f"20:00 UTC close must pass the gate, got {open_hour.signal_quality}"
# Factory scoping: M15 opts in, M5 does not (its book must not move).
assert make_config(Timeframe.M15).xau_strategy.sell_blocked_hours_utc == [15, 17]
assert make_config(Timeframe.M5).xau_strategy.sell_blocked_hours_utc == []
@test("NewsEngine._get_affected_pairs includes EUR/USD for EUR and USD events")
def _():
from news_engine import NewsEngine
assert "EUR/USD" in NewsEngine._get_affected_pairs("EUR")
assert "EUR/USD" in NewsEngine._get_affected_pairs("USD")
assert "XAU/USD" in NewsEngine._get_affected_pairs("USD")
assert "EUR/USD" not in NewsEngine._get_affected_pairs("GBP")
# ── 10b. EUR filter pipeline ────────────────────────────────────────
@test("EUR/USD signal analysis produces valid report (mocked data)")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
config.backtest.session_filter = False
agg = SignalAggregator(config)
df = make_ohlcv(n=300, start_price=1.0800, trend=0.00005)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
assert report.display_name == "EUR/USD"
assert report.signal_quality in ("A+", "A", "B", "C", "D", "F")
assert not any("SELL FILTER" in w for w in report.warnings)
@test("EUR filter: blocked regime sets quality F")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
config.eur_strategy.blocked_regimes = ["ranging"]
agg = SignalAggregator(config)
df = make_ohlcv(n=300, seed=42)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
if report.market_regime == "ranging":
assert report.signal_quality == "F", \
f"Ranging should be blocked, got {report.signal_quality}"
@test("EUR filter: quality not in min_quality_grades sets F")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
config.eur_strategy.min_quality_grades = ["A+"]
agg = SignalAggregator(config)
df = make_ohlcv(n=300, seed=42, trend=0.001)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
if report.signal_quality not in ("A+", "F"):
assert False, f"Non-A+ grade should be blocked, got {report.signal_quality}"
@test("EUR filter: score below min sets F")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
config.eur_strategy.min_entry_score = 99.0
agg = SignalAggregator(config)
df = make_ohlcv(n=300, seed=42, trend=0.001)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
assert report.signal_quality == "F", \
f"Score 99 threshold should block all, got {report.signal_quality}"
@test("EUR filter: confidence below min sets F")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
config.eur_strategy.min_entry_confidence = 99.0
agg = SignalAggregator(config)
df = make_ohlcv(n=300, seed=42, trend=0.001)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
assert report.signal_quality == "F", \
f"Confidence 99 threshold should block all, got {report.signal_quality}"
@test("EUR filter: RSI extended buy (RSI>70) blocked")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
config.eur_strategy.block_rsi_extended = True
config.eur_strategy.min_entry_score = 0.0
config.eur_strategy.min_entry_confidence = 0.0
config.eur_strategy.min_quality_grades = ["A+", "A", "B", "C", "D"]
agg = SignalAggregator(config)
df = make_ohlcv(n=300, trend=0.005, seed=42)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
if report.primary_analysis and "rsi" in report.primary_analysis.indicators:
rsi_v = report.primary_analysis.indicators["rsi"].value
if report.final_score > 0 and rsi_v > 70:
assert report.signal_quality == "F", \
f"Buy with RSI {rsi_v:.0f} should be blocked"
@test("EUR weekend filter returns F report on Saturday")
def _():
import signal_aggregator as sa_mod
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
agg = SignalAggregator(config)
saturday = datetime(2026, 7, 11, 12, 0, tzinfo=timezone.utc)
assert saturday.weekday() == 5
with patch.object(sa_mod, "datetime", wraps=datetime) as mock_dt:
mock_dt.now.return_value = saturday
report = agg.analyze_pair(symbol=EURUSD)
assert report.signal_quality == "F"
assert any("WEEKEND" in w for w in report.warnings)
assert report.trade_setup is None
# ── 10c. EUR trade geometry ─────────────────────────────────────────
def _mk_eur_report(score=45.0, quality="A", atr=0.0006, price=1.0800):
from signal_aggregator import SignalReport
r = SignalReport(symbol=EURUSD, display_name="EUR/USD", timestamp="2026-07-08 14:00")
r.final_signal = SignalStrength.BUY
r.final_score = score
r.final_confidence = 70.0
r.signal_quality = quality
class _Analysis:
key_levels = {
"atr": atr,
"swing_low_1": price - 0.0015,
"swing_low_2": price - 0.0030,
"swing_high_1": price + 0.0030,
"swing_high_2": price + 0.0050,
}
r.primary_analysis = _Analysis()
r.current_price = price
return r
@test("EUR trade setup: swing SL with EUR buffers, RR >= min_rr")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.M5)
agg = SignalAggregator(config)
r = _mk_eur_report()
ts = agg._generate_trade_setup(r, 0.0001)
assert ts is not None, f"no setup, warnings: {r.warnings}"
assert ts.pip_value == 0.0001
assert ts.stop_loss < ts.entry_price < ts.take_profit_1
assert ts.risk_pips < 60, f"EUR risk pips insane: {ts.risk_pips}"
assert ts.risk_reward >= config.eur_strategy.min_rr_ratio - 0.01
# levels must NOT be snapped to gold's 2-decimal grid
assert abs(ts.stop_loss * 100 - round(ts.stop_loss * 100)) > 1e-6 or \
abs(ts.take_profit_1 * 100 - round(ts.take_profit_1 * 100)) > 1e-6
@test("EUR spread gate rejects sub-minimum pip targets")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.M5)
config.eur_strategy.min_rr_ratio = 0.1
agg = SignalAggregator(config)
r = _mk_eur_report(atr=0.0002)
r.primary_analysis.key_levels = {"atr": 0.0002}
ts = agg._generate_trade_setup(r, 0.0001)
assert ts is None
assert any("SPREAD GATE" in w and "pip" in w for w in r.warnings), r.warnings
@test("Per-pair spread gates: 40-pip EUR target passes, $4 XAU target rejected")
def _():
from pair_signals.eurusd import make_config
# EUR: 40-pip TP distance clears the 10-pip minimum
config = make_config(Timeframe.M5)
agg = SignalAggregator(config)
r = _mk_eur_report(atr=0.0020)
ts = agg._generate_trade_setup(r, 0.0001)
assert ts is not None and abs(ts.take_profit_1 - ts.entry_price) / 0.0001 >= 10
# XAU: a $4 target must fail gold's $9 minimum (round_trip 0.30 × 30)
xau_cfg = PlatformConfig()
xau_agg = SignalAggregator(xau_cfg)
from signal_aggregator import SignalReport
xr = SignalReport(symbol=XAUUSD, display_name="XAU/USD", timestamp="2026-07-08 14:00")
xr.final_signal = SignalStrength.BUY
xr.final_score = 45.0
xr.final_confidence = 70.0
xr.signal_quality = "B"
class _XA:
key_levels = {"atr": 2.0}
xr.primary_analysis = _XA()
xr.current_price = 4000.0
xau_cfg.signals.min_rr_ratio = 0.1
xau_cfg.signals.tp_floor_rr = 0.1
ts2 = xau_agg._generate_trade_setup(xr, 0.10)
# ATR 2.0 → TP distance ~3, below the $9 gate
assert ts2 is None
assert any("SPREAD GATE" in w and "$" in w for w in xr.warnings), xr.warnings
@test("Trade setup: pip_value zero returns None")
def _():
config = PlatformConfig()
agg = SignalAggregator(config)
df = make_ohlcv(n=300, trend=0.002, seed=42)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
report = agg.analyze_from_dataframes(symbol=XAUUSD, dfs=dfs, use_news=False)
if report.primary_analysis:
setup = agg._generate_trade_setup(report, pip_value=0.0)
assert setup is None, "pip_value=0 should produce no trade setup"
# ── 10d. Cross-pair isolation ───────────────────────────────────────
@test("XAU sell gate does not apply to EUR/USD")
def _():
from pair_signals.eurusd import make_config
config = make_config(Timeframe.H1)
config.backtest.session_filter = False
agg = SignalAggregator(config)
df = make_ohlcv(n=300, start_price=1.0800, trend=-0.0005, seed=99)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
assert not any("SELL FILTER" in w for w in report.warnings)
@test("EUR report never carries XAU gate warnings across seeds")
def _():
from pair_signals.eurusd import make_config
for seed in (7, 21, 42, 77, 99):
config = make_config(Timeframe.M5)
agg = SignalAggregator(config)
df = make_ohlcv(n=400, start_price=1.0800, trend=0.0004, seed=seed)
dfs = {Timeframe.M5: df, Timeframe.M15: df, Timeframe.H1: df}
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
leaked = [w for w in report.warnings
if "SELL FILTER" in w or "M5 FILTER" in w or "XAU PULLBACK" in w
or "LOW ATR FILTER" in w]
assert not leaked, f"XAU gates leaked into EUR (seed={seed}): {leaked}"
if report.trade_setup:
assert report.trade_setup.pip_value == 0.0001
@test("XAU report identical whether eur_strategy is enabled or disabled")
def _():
df = make_ohlcv(n=300, seed=42, trend=0.001)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
results = []
for eur_enabled in (True, False):
config = PlatformConfig()
config.eur_strategy.enabled = eur_enabled
agg = SignalAggregator(config)
report = agg.analyze_from_dataframes(symbol=XAUUSD, dfs=dfs, use_news=False)
results.append((report.final_score, report.final_confidence,
report.signal_quality, str(report.trade_setup),
tuple(report.warnings)))
assert results[0] == results[1], \
f"XAU output changed with eur_strategy.enabled flip: {results[0]} vs {results[1]}"
# and no EUR warnings on an XAU report
assert not any("EUR" in w for w in results[0][4])
@test("Integration: XAU and EUR produce independent results")
def _():
from pair_signals.eurusd import make_config as eur_make_config
df = make_ohlcv(n=300, seed=42, trend=0.001)
xau_config = PlatformConfig()
xau_agg = SignalAggregator(xau_config)
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
xau_report = xau_agg.analyze_from_dataframes(symbol=XAUUSD, dfs=dfs, use_news=False)
eur_config = eur_make_config(Timeframe.H1)
eur_agg = SignalAggregator(eur_config)
eur_report = eur_agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
assert xau_report.display_name == "XAU/USD"
assert eur_report.display_name == "EUR/USD"
# ══════════════════════════════════════════════════════════════════════
# MODULE 11: 2026-07-16 audit-fix regressions
# ══════════════════════════════════════════════════════════════════════
@@ -2514,7 +2060,7 @@ def _():
spike_ts, after_ts = df.index[k], df.index[k + 1]
spike_val = df["Close"].iloc[k - 1] * 1.05 # ~+5% on a ~0.1%-σ series
df.loc[spike_ts, ["Open", "High", "Low", "Close"]] = spike_val
cleaned = provider._validate_and_clean(df, "EUR/USD", Timeframe.H1)
cleaned = provider._validate_and_clean(df, "XAU/USD", Timeframe.H1)
assert spike_ts not in cleaned.index, "spike bar must be removed"
assert after_ts in cleaned.index, \
"snap-back bar after the spike must be kept (two-bar hole regression)"