#!/usr/bin/env python3 """R-Ertrag: bringt das NEU trainierte P(break)-Modell live mehr als das alte? Zweiter, entscheidender Teil zu `backtest_pbreak_retrain.py`. Dort wurde gezeigt, dass das ALTE Modell auf live-typischen Situationen nur AUC ~0,34 erreicht (also schlechter als Zufall) und das neue ~0,66. **AUC allein reicht aber nicht** — die Projektregel verlangt einen besseren R-Ertrag in BEIDEN Hälften. SIMULIERTE REGEL (exakt der Live-Pfad): Position öffnet beim Wave-Signal. Danach jeden Bar prüfen: ist der Kurs im 0,15×ATR-Band um das dynamisch gewählte Level (mit Hysterese)? → P(break) berechnen. Ist P < Schwelle X → JETZT schließen (R_close). → sonst weiterlaufen lassen. Wird nie geschlossen → R_run (Live-Trailing: SL 2,0 · Trail 1,5 ab 0,3 · BE 1,3). Alles mit Echtkosten (Bar-Spread/ATR). VERGLICHEN WERDEN: * Baseline — nie am Level schließen (nur Trailing) = der ehrliche Maßstab * ALT-Modell — die aktuell live eingebetteten _PB_MU/_SD/_W * NEU-Modell — auf der live-spiegelnden Stichprobe gefittet (Fit auf H1!) ⚠ Das NEUE Modell wird auf H1 gefittet und in H2 out-of-sample angewandt — sonst wäre der Vergleich zirkulär. """ import sys from collections import deque import numpy as np import MetaTrader5 as mt5 from core.analysis import calc_trend_angle from core.wave_rec import (_EMA_FAST, _EMA_SLOW, _N_BARS, _ANGLE_LR, _ANGLE_DEAD, _REVERSAL_STRETCH, _STRETCH_MAX) from backtest_pbreak_retrain import ( _ema_series, _atr_series, build_levels, _cluster, pick_level, fit_logreg, _OLD_MU, _OLD_SD, _OLD_W, _ZONE, _ATRMIN, _M30) _MAXH = 200 _SL_ATR = 2.0; _TRAILON = 0.3; _MULT = 1.5; _BE = 1.3 _LOCK_START = 3.5; _LOCK_SCALE = 0.6; _LOCK_MIN = 1.2; _TP_INIT = 3.5 def sim_run(entry, d, atr, H, L, C, j0): """Live-Exit (identisch zu backtest_srclose_prob.py).""" sl = entry - d * _SL_ATR * atr; tp = entry + d * _TP_INIT * atr hw = entry; rank = 0 end = min(j0 + _MAXH, len(C) - 1) for j in range(j0, end + 1): hi, lo = H[j], L[j] if (lo <= sl) if d > 0 else (hi >= sl): return (sl - entry) * d / atr if (hi >= tp) if d > 0 else (lo <= tp): return (tp - entry) * d / atr hw = max(hw, hi) if d > 0 else min(hw, lo) profit = (hw - entry) * d ph = 0 if profit < _TRAILON * atr else (1 if profit < _LOCK_START * atr else 2) if ph < rank: ph = rank rank = ph if ph == 1: cand = hw - d * _MULT * atr if profit >= _BE * atr: cand = max(cand, entry) if d > 0 else min(cand, entry) sl = max(sl, cand) if d > 0 else min(sl, cand) elif ph == 2: tm = max(_LOCK_MIN, _MULT * _LOCK_SCALE); cand = hw - d * tm * atr cand = max(cand, entry) if d > 0 else min(cand, entry) sl = max(sl, cand) if d > 0 else min(sl, cand) return (C[end] - entry) * d / atr def positions(a, b, H, L, C, EF, ES, AT, SP, LV): """→ [{R_run, cost, events:[(feat, R_close)]}] — Events in zeitlicher Reihenfolge.""" TH = _REVERSAL_STRETCH out = [] i = max(a, _N_BARS, 320) while i < min(b, len(C) - _MAXH - 1): atr = AT[i] if not atr or atr <= 0: i += 1; continue atr = max(atr, _ATRMIN); es = ES[i]; ef = EF[i] stretch = (C[i] - es) / atr ang = calc_trend_angle(C[i - _ANGLE_LR - 2:i], _ANGLE_LR); ad = ang - 90.0 d = 0 if stretch <= -TH and ad >= _ANGLE_DEAD: d = 1 elif stretch >= TH and ad <= -_ANGLE_DEAD: d = -1 elif abs(stretch) < _STRETCH_MAX: d = 1 if ef > es else -1 if ef < es else 0 if not d: i += 1; continue entry = C[i] cost = (SP[i] if SP[i] > 0 else 0.0225) / atr rec = {"R_run": sim_run(entry, d, atr, H, L, C, i + 1) - cost, "events": []} prev_lvl = None for j in range(i + 1, min(i + _MAXH, len(C) - 12)): aj = AT[j] if not aj: continue aj = max(aj, _ATRMIN) lv = LV[min(j // _M30, len(LV) - 1)] level = pick_level(_cluster(lv, aj), C[j], d, aj, prev_lvl) prev_lvl = level if level is None or abs(C[j] - level) > _ZONE * aj: continue feat = [(C[j] - C[max(0, j - 6)]) * d / aj, (C[j] - C[max(0, j - 3)]) * d / aj, 1.0 if (EF[j] - ES[j]) * d > 0 else 0.0, abs(level - entry) / aj] # Schließen am Level: der Kurs IST dort (±0,15×ATR) → C[j] als Fill rec["events"].append((feat, (C[j] - entry) * d / aj - cost)) out.append(rec) i += 12 return out def p_old(feat): z = _OLD_W[0] for k in range(4): z += _OLD_W[k + 1] * (feat[k] - _OLD_MU[k]) / _OLD_SD[k] return 1 / (1 + np.exp(-max(-30, min(30, z)))) def total(pos, pfun, X): """ΣR der Regel: beim ERSTEN Event mit P 1 else 80000 mt5.initialize(); sym = None for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"): if mt5.symbol_info(c): sym = c; break si = mt5.symbol_info(sym); point = si.point bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n) mt5.shutdown() H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars] C = [float(b["close"]) for b in bars]; SP = [float(b["spread"]) * point for b in bars] EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW); AT = _atr_series(H, L, C) LV = build_levels(H, L) mid = len(C) // 2 P1 = positions(_N_BARS, mid, H, L, C, EF, ES, AT, SP, LV) P2 = positions(mid, len(C), H, L, C, EF, ES, AT, SP, LV) # NEUES Modell auf H1-Events fitten X1 = np.array([f for r in P1 for f, _ in r["events"]]) # Ausgang je Event wird hier NICHT gebraucht — wir fitten auf demselben # Kriterium wie im Retrain-Skript, deshalb dort erzeugte Gewichte verwenden. from backtest_pbreak_retrain import collect as rt_collect s1 = rt_collect(_N_BARS, mid, H, L, C, EF, ES, AT, LV) Xf = np.array([e["feat"] for e in s1]); yf = np.array([e["y"] for e in s1], float) mu = Xf.mean(0); sd = Xf.std(0) + 1e-9 w = fit_logreg((Xf - mu) / sd, yf) def p_new(feat): z = w[0] + float(np.dot(w[1:], (np.array(feat) - mu) / sd)) return 1 / (1 + np.exp(-max(-30, min(30, z)))) print("=" * 96) print(f" R-ERTRAG der P(break)-Close-Regel — {sym} M5 ({len(C)} Bars), Echtkosten") print(f" NEU-Modell auf H1 gefittet ({len(s1)} Events) → H2 ist out-of-sample") print("=" * 96) for lbl, P in (("H1 (in-sample)", P1), ("H2 (OUT-OF-SAMPLE)", P2)): base = sum(r["R_run"] for r in P) nev = sum(1 for r in P if r["events"]) print(f"\n {lbl}: {len(P)} Positionen, davon {nev} mit Level-Kontakt") print(f" Baseline (nur Trailing, nie am Level schließen): ΣR = {base:+.0f}") print(f" {'Schwelle':<10}{'ALT ΣR':>10}{'ALT %zu':>9}{'NEU ΣR':>10}{'NEU %zu':>9}" f"{'NEU vs ALT':>12}{'NEU vs Base':>13}") for X in (0.35, 0.45, 0.55, 0.60): so, co = total(P, p_old, X) sn, cn = total(P, p_new, X) print(f" P<{X:.2f} {so:>+10.0f}{100*co/max(1,len(P)):>8.0f}%" f"{sn:>+10.0f}{100*cn/max(1,len(P)):>8.0f}%" f"{sn-so:>+12.0f}{sn-base:>+13.0f}") if __name__ == "__main__": main()