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:
+69
-5
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user