Optimize EUR/USD to 78.7% WR on 2-year M15 backtest

Three-layer optimization:

1. Entry filters (signal_aggregator.py section 6e):
   - Block ranging regime
   - Only A+/A/B quality grades
   - min_entry_score=38, min_entry_confidence=60
   - Block RSI > 70 for BUY, RSI < 30 for SELL

2. Trade setup (config.py EurUsdStrategyConfig):
   - RR=0.50 with uniform quality scale
   - SL floor 1.2 ATR, TP 1.0 ATR

3. Breakeven trail (backtest/gpu_backtest.py):
   - When MFE reaches 0.3R, move SL to entry + 0.05R buffer
   - Converts 4 of 14 losses to +0.05R small wins
   - Bar-by-bar simulation integrated into simulate_trades()

Results (EUR/USD M15, 2024-05-18 to 2026-05-18):
  47 trades | 78.7% WR | +1.81R | PF 1.18 | MaxDD 3.35R
XAU/USD unchanged: 77.8% WR on 30-day, 68.7% WR on 2-year.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
harwinder
2026-05-18 14:00:59 -04:00
parent 74b723ba06
commit 08d7c9ade1
4 changed files with 191 additions and 42 deletions
+69 -5
View File
@@ -426,6 +426,72 @@ class SignalAggregator:
report.signal_quality = "D"
report.trade_setup = None
# ── 6e. EUR/USD regime + quality + risk gate ─────────────
if report.display_name == "EUR/USD" and self.config.eur_strategy.enabled:
eur = self.config.eur_strategy
if eur.blocked_regimes and report.market_regime in eur.blocked_regimes:
report.warnings.append(
f"EUR REGIME FILTER: {report.market_regime} blocked"
)
report.signal_quality = "F"
if (eur.min_quality_grades
and report.signal_quality not in eur.min_quality_grades
and report.signal_quality != "F"):
report.warnings.append(
f"EUR QUALITY FILTER: {report.signal_quality} below minimum"
)
report.signal_quality = "F"
if (report.signal_quality != "F"
and getattr(report, 'exhaustion_count', 0) > eur.max_exhaustion_tags):
report.warnings.append(
f"EUR EXHAUSTION FILTER: {report.exhaustion_count} tags > max {eur.max_exhaustion_tags}"
)
report.signal_quality = "F"
if (report.signal_quality != "F"
and eur.block_near_ema200
and any(w.startswith("NEAR EMA200") for w in report.warnings)):
report.warnings.append("EUR EMA200 FILTER: too close to EMA200")
report.signal_quality = "F"
if eur.session_hours and report.signal_quality != "F":
try:
bar_hour = pd.Timestamp(report.timestamp).hour
if bar_hour not in eur.session_hours:
report.warnings.append(
f"EUR SESSION FILTER: {bar_hour:02}:00 UTC outside allowed hours"
)
report.signal_quality = "F"
except Exception:
pass
if report.signal_quality != "F":
abs_s = abs(report.final_score)
if abs_s < eur.min_entry_score:
report.warnings.append(
f"EUR SCORE FILTER: |{abs_s:.1f}| < {eur.min_entry_score}"
)
report.signal_quality = "F"
elif report.final_confidence < eur.min_entry_confidence:
report.warnings.append(
f"EUR CONF FILTER: {report.final_confidence} < {eur.min_entry_confidence}"
)
report.signal_quality = "F"
if (report.signal_quality != "F"
and eur.block_rsi_extended
and report.primary_analysis):
rsi_ind = report.primary_analysis.indicators.get("rsi")
if rsi_ind:
rsi_v = rsi_ind.value
is_buy = report.final_score > 0
if is_buy and rsi_v > 70:
report.warnings.append(
f"EUR RSI FILTER: RSI {rsi_v:.0f} overbought for BUY"
)
report.signal_quality = "F"
elif not is_buy and rsi_v < 30:
report.warnings.append(
f"EUR RSI FILTER: RSI {rsi_v:.0f} oversold for SELL"
)
report.signal_quality = "F"
# ── 7. Trade setup for actionable signals ──────────────────
if report.trade_setup is None and report.signal_quality in ("A+", "A") and report.display_name == "XAU/USD":
atr = (report.primary_analysis.key_levels.get("atr", 0)
@@ -864,12 +930,10 @@ class SignalAggregator:
if _is_eur:
eur = self.config.eur_strategy
q_scale = eur.tp_quality_scale.get(report.signal_quality, 1.0)
min_rr = eur.min_rr_ratio * q_scale
min_rr = eur.min_rr_ratio
mature_move = abs(report.final_score) > eur.mature_move_threshold
base_tp = eur.tp_atr_multiplier if mature_move else eur.tp_atr_multiplier * 1.5
tp_mult = base_tp * q_scale
tp_swing_cap = (2.0 if mature_move else 2.5) * q_scale
tp_mult = eur.tp_atr_multiplier if mature_move else eur.tp_atr_multiplier * 1.5
tp_swing_cap = 2.0 if mature_move else 2.5
sl_atr_floor = eur.sl_atr_floor
else:
min_rr = self.config.signals.min_rr_ratio