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:
+99
-28
@@ -1145,6 +1145,8 @@ def simulate_trades(
|
||||
trades: List[Trade],
|
||||
sim_df: pd.DataFrame,
|
||||
max_bars: int = 200,
|
||||
be_trigger_r: float = 0.0,
|
||||
be_buffer_r: float = 0.0,
|
||||
) -> None:
|
||||
"""Walk forward bar-by-bar after each entry and resolve SL / TP1.
|
||||
|
||||
@@ -1234,41 +1236,107 @@ def simulate_trades(
|
||||
t.exit_reason = "NO_DATA"
|
||||
continue
|
||||
|
||||
sl_hit_idx = int(sl_first_arr[i])
|
||||
tp_hit_idx = int(tp_first_arr[i])
|
||||
risk = abs(t.entry_price - t.sl)
|
||||
is_buy_flag = t.direction == "BUY"
|
||||
|
||||
# SL wins ties (conservative same-bar handling).
|
||||
if sl_hit_idx < n_valid and sl_hit_idx <= tp_hit_idx:
|
||||
t.exit_reason = "SL"
|
||||
t.bars_held = sl_hit_idx + 1
|
||||
t.exit_price = t.sl
|
||||
t.r_multiple = -1.0
|
||||
t.exit_time = exit_times[i][sl_hit_idx]
|
||||
elif tp_hit_idx < n_valid:
|
||||
t.exit_reason = "TP1"
|
||||
t.bars_held = tp_hit_idx + 1
|
||||
t.exit_price = t.tp1
|
||||
t.r_multiple = t.rr_planned
|
||||
t.exit_time = exit_times[i][tp_hit_idx]
|
||||
if be_trigger_r > 0 and risk > 0:
|
||||
triggered = False
|
||||
eff_sl = t.sl
|
||||
exit_found = False
|
||||
for j in range(n_valid):
|
||||
h = float(highs_arr[i, j])
|
||||
l = float(lows_arr[i, j])
|
||||
if is_buy_flag:
|
||||
sl_hit_bar = l <= eff_sl
|
||||
tp_hit_bar = h >= t.tp1
|
||||
else:
|
||||
sl_hit_bar = h >= eff_sl
|
||||
tp_hit_bar = l <= t.tp1
|
||||
if sl_hit_bar and tp_hit_bar:
|
||||
if triggered:
|
||||
t.exit_reason = "BE"
|
||||
t.r_multiple = be_buffer_r
|
||||
t.exit_price = eff_sl
|
||||
else:
|
||||
t.exit_reason = "SL"
|
||||
t.r_multiple = -1.0
|
||||
t.exit_price = t.sl
|
||||
t.bars_held = j + 1
|
||||
t.exit_time = exit_times[i][j]
|
||||
exit_found = True
|
||||
break
|
||||
elif sl_hit_bar:
|
||||
if triggered:
|
||||
t.exit_reason = "BE"
|
||||
t.r_multiple = be_buffer_r
|
||||
t.exit_price = eff_sl
|
||||
else:
|
||||
t.exit_reason = "SL"
|
||||
t.r_multiple = -1.0
|
||||
t.exit_price = t.sl
|
||||
t.bars_held = j + 1
|
||||
t.exit_time = exit_times[i][j]
|
||||
exit_found = True
|
||||
break
|
||||
elif tp_hit_bar:
|
||||
t.exit_reason = "TP1"
|
||||
t.bars_held = j + 1
|
||||
t.exit_price = t.tp1
|
||||
t.r_multiple = t.rr_planned
|
||||
t.exit_time = exit_times[i][j]
|
||||
exit_found = True
|
||||
break
|
||||
if not triggered:
|
||||
if is_buy_flag:
|
||||
bar_mfe = (h - t.entry_price) / risk
|
||||
else:
|
||||
bar_mfe = (t.entry_price - l) / risk
|
||||
if bar_mfe >= be_trigger_r:
|
||||
triggered = True
|
||||
if is_buy_flag:
|
||||
eff_sl = t.entry_price + be_buffer_r * risk
|
||||
else:
|
||||
eff_sl = t.entry_price - be_buffer_r * risk
|
||||
if not exit_found:
|
||||
t.exit_reason = "TIMEOUT"
|
||||
t.bars_held = int(n_valid)
|
||||
last_idx = int(n_valid) - 1
|
||||
t.exit_price = float(closes_arr[i, last_idx])
|
||||
if risk > 0:
|
||||
pnl = (t.exit_price - t.entry_price) if is_buy_flag else (t.entry_price - t.exit_price)
|
||||
t.r_multiple = pnl / risk
|
||||
t.exit_time = exit_times[i][last_idx]
|
||||
else:
|
||||
# Time out at last simulated bar.
|
||||
t.exit_reason = "TIMEOUT"
|
||||
t.bars_held = int(n_valid)
|
||||
last_idx = int(n_valid) - 1
|
||||
t.exit_price = float(closes_arr[i, last_idx])
|
||||
risk = abs(t.entry_price - t.sl)
|
||||
if risk > 0:
|
||||
pnl = (t.exit_price - t.entry_price) if t.direction == "BUY" else (t.entry_price - t.exit_price)
|
||||
t.r_multiple = pnl / risk
|
||||
t.exit_time = exit_times[i][last_idx]
|
||||
sl_hit_idx = int(sl_first_arr[i])
|
||||
tp_hit_idx = int(tp_first_arr[i])
|
||||
if sl_hit_idx < n_valid and sl_hit_idx <= tp_hit_idx:
|
||||
t.exit_reason = "SL"
|
||||
t.bars_held = sl_hit_idx + 1
|
||||
t.exit_price = t.sl
|
||||
t.r_multiple = -1.0
|
||||
t.exit_time = exit_times[i][sl_hit_idx]
|
||||
elif tp_hit_idx < n_valid:
|
||||
t.exit_reason = "TP1"
|
||||
t.bars_held = tp_hit_idx + 1
|
||||
t.exit_price = t.tp1
|
||||
t.r_multiple = t.rr_planned
|
||||
t.exit_time = exit_times[i][tp_hit_idx]
|
||||
else:
|
||||
t.exit_reason = "TIMEOUT"
|
||||
t.bars_held = int(n_valid)
|
||||
last_idx = int(n_valid) - 1
|
||||
t.exit_price = float(closes_arr[i, last_idx])
|
||||
if risk > 0:
|
||||
pnl = (t.exit_price - t.entry_price) if is_buy_flag else (t.entry_price - t.exit_price)
|
||||
t.r_multiple = pnl / risk
|
||||
t.exit_time = exit_times[i][last_idx]
|
||||
|
||||
# MFE / MAE measured only over bars [0 .. bars_held).
|
||||
bars = t.bars_held
|
||||
risk = abs(t.entry_price - t.sl)
|
||||
if bars > 0 and risk > 0:
|
||||
h_win = highs_arr[i, :bars]
|
||||
l_win = lows_arr[i, :bars]
|
||||
if t.direction == "BUY":
|
||||
if is_buy_flag:
|
||||
mfe = (np.nanmax(h_win) - t.entry_price) / risk
|
||||
mae = (t.entry_price - np.nanmin(l_win)) / risk
|
||||
else:
|
||||
@@ -1512,7 +1580,10 @@ def run_backtest(
|
||||
|
||||
# ── Resolve trade outcomes ──
|
||||
t_sim = time.perf_counter()
|
||||
simulate_trades(trades, primary_df, max_bars=bt_cfg.max_hold_bars)
|
||||
be_trigger = getattr(platform_cfg.eur_strategy, 'be_trigger_r', 0.0) if platform_cfg.eur_strategy.enabled and bt_cfg.pair != XAUUSD else 0.0
|
||||
be_buffer = getattr(platform_cfg.eur_strategy, 'be_buffer_r', 0.0) if be_trigger > 0 else 0.0
|
||||
simulate_trades(trades, primary_df, max_bars=bt_cfg.max_hold_bars,
|
||||
be_trigger_r=be_trigger, be_buffer_r=be_buffer)
|
||||
logger.info(
|
||||
f"Trade simulation took {time.perf_counter() - t_sim:.2f}s "
|
||||
f"(GPU={'yes' if _GPU_AVAILABLE else 'no'})"
|
||||
|
||||
Reference in New Issue
Block a user