"""EUR/USD signal producer config. Re-tuned 2026-07-10 against the realistic backtester (1-pip round-trip cost, next-open fills, raw-path resolution) on 2025-07-08 → 2026-07-08 M5 with M15/H1 confirmations. RESULT: no configuration passed the acceptance rule (positive in BOTH train and val halves, PF >= 1.3). The M5 defaults below are the best found — roughly breakeven (-1.1R / 53 trades / PF 0.93 / 62% WR on 12mo) versus the donor branch's -6.7R. Findings that shaped them: - transitioning regime bled -27R/88 trades wide-open → blocked - A-grade entries LOSE (-26R) while B-grade wins — max conviction fires at move extremes (same pathology as gold M5) - Fridays (-17R) and hours 8, 12, 13, 18, 19 UTC negative - score 30-35 band is where the train-half losses live → floor 35 - trailing exits beat static TP1 (arm 0.45R / dist 0.5R best) M15 + H1 investigated 2026-07-11, verdict NO VALIDATED EDGE either (all at 1-pip round-trip cost, real fills): - M15 wide-open (all gates off): -2.5R/270 trades 2025-26; -54.8R/308 trades on 2024-25 out-of-sample. PF 0.61-0.98. - Best in-sample slice (transitioning-regime-only: +15.4R, PF 1.59, positive both halves, month-spread) FAILED 2024-25 OOS at PF 0.55. Within-year train/val splits are not sufficient for EUR — always re-test on the 2024 window before believing a config. - Raw-parquet forward-return screens (M15 and M5, ~14 entry conditions x 4-5 horizons: momentum, mean-reversion, breakout, streak, session families): nothing clears +1 pip in both years. - H1 wide-open: +9.8R PF 1.32 in 2025-26 (both halves positive) but -5.7R PF 0.82 on 2024-25. Positive from Jan 2025 onward; H2-2024 regime negative. Closest to workable, still unvalidated. Root cause is cost-to-volatility: EUR M15 ATR ~= 10 pips, so the 1-pip spread taxes every trade ~10% of ATR (M5 worse). The XAU signal engine survives because gold's ATR/spread ratio is several times larger. EUR/USD therefore ships FUNCTIONAL BUT OFF by default: it is not in PlatformConfig.scan_pairs. Scan it on demand (main.py --pair EURUSD, or add to scan_pairs) but treat all EUR M5/M15 signals as informational only. Open avenues needing new data/infra: calendar-anchored entries (requires a historical Forex Factory archive), limit-entry cost reduction (needs tick data to model fills honestly), H4/D1 swing. """ from typing import Optional, List from config import PlatformConfig, Timeframe, EURUSD _CONFIRMATIONS = { Timeframe.M5: [Timeframe.M15, Timeframe.H1], Timeframe.M15: [Timeframe.M5, Timeframe.H1], Timeframe.H1: [Timeframe.M15, Timeframe.H4], Timeframe.H4: [Timeframe.H1, Timeframe.D1], } _TRENDING = { "ema_trend": 2.0, "rsi": 1.0, "macd": 2.0, "bollinger": 0.5, "keltner": 1.0, "roc": 1.5, "candlestick": 0.5, "divergence": 2.0, "adx": 1.0, } EUR_REGIME_WEIGHTS = { "trending_up": _TRENDING, "trending_down": _TRENDING, "trending": _TRENDING, "ranging": { "ema_trend": 0.3, "rsi": 2.0, "macd": 1.0, "bollinger": 2.0, "keltner": 1.5, "roc": 0.5, "candlestick": 0.8, "divergence": 2.5, "adx": 0.5, }, "transitioning": { "ema_trend": 1.5, "rsi": 1.5, "macd": 2.0, "bollinger": 1.0, "keltner": 1.5, "roc": 2.0, "candlestick": 0.8, "divergence": 2.0, "adx": 1.0, }, } _M5_TRENDING = { "ema_trend": 1.5, "rsi": 1.0, "macd": 2.5, "bollinger": 0.5, "keltner": 2.0, "roc": 2.0, "candlestick": 0.3, "divergence": 1.0, "adx": 1.0, } M5_REGIME_WEIGHTS = { "trending_up": _M5_TRENDING, "trending_down": _M5_TRENDING, "trending": _M5_TRENDING, "ranging": { "ema_trend": 0.3, "rsi": 1.5, "macd": 1.5, "bollinger": 1.5, "keltner": 2.0, "roc": 1.0, "candlestick": 0.3, "divergence": 1.5, "adx": 0.5, }, "transitioning": { "ema_trend": 1.0, "rsi": 1.0, "macd": 2.5, "bollinger": 0.5, "keltner": 2.5, "roc": 2.0, "candlestick": 0.3, "divergence": 1.0, "adx": 1.0, }, } def make_config( primary_tf: Timeframe, confirmation_tfs: Optional[List[Timeframe]] = None, ) -> PlatformConfig: config = PlatformConfig() config.symbol = EURUSD config.display_name = "EUR/USD" config.pip_value = 0.0001 config.primary_timeframe = primary_tf config.confirmation_timeframes = ( confirmation_tfs or _CONFIRMATIONS.get(primary_tf, [Timeframe.M15, Timeframe.H4]) ) # XAU gates are display-gated in the aggregator; disabling here is # 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] config.backtest.allowed_weekdays = [0, 1, 2, 3, 4] if primary_tf == Timeframe.M5: config.technical.regime_weights_override = M5_REGIME_WEIGHTS config.technical.swing_sr_lookback = 20 config.eur_strategy.sl_atr_floor = 0.7 config.eur_strategy.sl_atr_multiplier = 0.3 config.eur_strategy.min_quality_grades = ["A+", "A", "B"] # Best-found 2026-07-10 (breakeven, see module docstring): config.eur_strategy.min_entry_score = 35.0 config.eur_strategy.min_entry_confidence = 45.0 config.eur_strategy.blocked_vol_regimes = [] config.eur_strategy.min_indicator_agreement = 0 config.eur_strategy.allowed_weekdays = [0, 1, 2, 3] # Fridays bled -17R config.backtest.allowed_weekdays = [0, 1, 2, 3] config.backtest.blocked_hours_utc = [22, 23, 0, 1, 2, 3, 8, 12, 13, 18, 19] # Trailing exit beat static TP1; earlier/tighter than gold's 0.6/0.75. config.signals.trail_arm_r = 0.45 config.signals.trail_dist_r = 0.5 else: config.technical.regime_weights_override = EUR_REGIME_WEIGHTS return config