#!/usr/bin/env python3 """Flip-Close auf dem HÖHEREN TREND (M30 / H1) — trägt er als EXIT? ANLASS (2026-08-07, User: „oder doch lieber ein Flip-Close bei entgegen- gerichtetem Trend?"). Zu unterscheiden sind ZWEI Dinge: · Flip auf das EMPFEHLUNGS-Signal — gemessen (`backtest_flipclose2.py`), in JEDER Variante und BEIDEN Hälften schlechter, am 05.08. abgeschaltet. Mechanik: die nachlaufende EMA dreht oft mitten im Pullback → Gewinner-Kappen. · Flip auf den HTF-TREND (M30/H1) — **war nie gemessen**. Der HTF ist träger, dreht viel seltener, und als FILTER ist er im Projekt belegt (Edge ×2). Ob er auch als EXIT trägt, ist damit keineswegs entschieden. ⚠⚠ ENTSCHEIDUNGSREGEL, VORAB FESTGELEGT: Eine Variante gilt nur dann als besser, wenn sie (1) den ΣR in BEIDEN Halbjahren erhöht, UND (2) den Worst-Case nicht verschlechtert, UND (3) bei den Nachbar-Einstellungen (minR) nicht das Vorzeichen kippt. Sonst: kein Einbau. Punkt (3), weil in diesem Projekt schon mehrfach eine einzelne Zelle gut aussah, deren Nachbarn kippten (ORB, SMC-Sweeps, und heute der Notfall-Stop bei 8 % gegen −292/−106 bei 5 %/15 %). ⚠ FESTE, GETEILTE ENTRY-LISTE. `backtest_flipclose2.py` lässt die sequentielle Sim nach jedem Exit bei `xb+1` weiterlaufen — dadurch bekommt JEDE Variante eine andere Trade-Folge (steigt eine früher aus, verschiebt sich alles Nachfolgende) und die Zahlen sind nicht vergleichbar. Genau dieser Fehler wurde am 31.07. bei `backtest_exit_combo.py` schon einmal korrigiert; hier wird er von vornherein vermieden: alle Varianten laufen auf DENSELBEN Einstiegen. Exit-Kern = `core/exit_model.simulate()` mit LIVE-Parametern (geteilte Quelle, kein Nachbau) + `stop_when`-Hook für den Flip. Kosten = echter Bar-Spread/ATR. Aufruf: python backtest_flipclose_htf.py [n_bars] (Default 80000) """ from __future__ import annotations import sys import MetaTrader5 as mt5 from backtest_auto_signal import _atr_series, _ema_series, _htf_signs # ⚠ `_ATRMIN` aus `backtest_flipclose2` uebernehmen, NICHT neu setzen — der # Vergleich mit dem dortigen Befund gilt nur bei identischem ATR-Floor. from backtest_flipclose2 import _ATRMIN, signal_series from core.exit_model import LIVE, simulate from core.wave_rec import _EMA_FAST, _EMA_SLOW, _N_BARS _M30_STEP = 6 # 6 M5-Bars = M30 _H1_STEP = 12 # 12 M5-Bars = H1 def entries(sig, lo, hi, H, L, C, AT, SP): """EINE feste Einstiegsliste, sequentiell mit dem BASIS-Exit ermittelt. ⚠ Bewusst mit der Basis (ohne Flip) bestimmt: nur so handeln alle Varianten dieselben Trades und der Vergleich misst den EXIT, nicht eine verschobene Trade-Folge.""" out = [] i = lo while i < hi: d = sig[i] if d == 0: i += 1 continue atr = max(AT[i] or 0, _ATRMIN) kosten = (SP[i] if SP[i] > 0 else 0.0225) / atr _, xb = simulate(C[i], d, atr, H, L, C, i + 1, LIVE, ret_bar=True) out.append((i, d, atr, kosten)) i = xb + 1 return out def lauf(ein, H, L, C, htf, h1, *, flip=None, min_r=0.0, auch_im_minus=False): """`flip`: None = Basis · "m30" · "m30+h1" (beide müssen dagegen stehen).""" Rs = [] for i, d, atr, kosten in ein: haken = None if flip: def haken(j, prof_atr, _d=d): gegen = htf[j] == -_d if flip == "m30+h1": gegen = gegen and h1[j] == -_d if not gegen: return False return auch_im_minus or prof_atr > min_r R = simulate(C[i], d, atr, H, L, C, i + 1, LIVE, stop_when=haken) Rs.append(R - kosten) return Rs def kz(Rs): if not Rs: return "—" n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs) g = sum(x for x in Rs if x > 0); v = -sum(x for x in Rs if x < 0) return (f"n={n:>4} WR={100*w/n:>3.0f}% ØR={s/n:+.3f} " f"PF={(g/v if v > 0 else 9.99):>4.2f} ΣR={s:>+6.0f} Worst={min(Rs):+.2f}") def main(): n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 mt5.initialize() sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") if mt5.symbol_info(c)), None) bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n) si = mt5.symbol_info(sym); point = si.point 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) sig = signal_series(H, L, C, EF, ES, AT) htf = _htf_signs(H, L, C, _M30_STEP) h1 = _htf_signs(H, L, C, _H1_STEP) mid = len(C) // 2 print("=" * 96) print(f" FLIP-CLOSE auf dem HTF-TREND — {sym} M5, {len(C)} Bars") print(" Regel vorab: ΣR in BEIDEN Hälften besser · Worst nicht schlechter · " "Nachbarn kippen nicht") print("=" * 96) for name, (lo, hi) in (("H1", (_N_BARS, mid)), ("H2", (mid, len(C) - LIVE.max_hold - 2))): ein = entries(sig, lo, hi, H, L, C, AT, SP) print(f"\n ── {name} ── {len(ein)} feste Einstiege, alle Varianten identisch") basis = lauf(ein, H, L, C, htf, h1) b_sum = sum(basis) print(f" {'BASIS (kein Flip-Close)':<34}{kz(basis)}") for etikett, kw in ( ("M30 dagegen, ab +0,0R", dict(flip="m30", min_r=0.0)), ("M30 dagegen, ab +0,5R", dict(flip="m30", min_r=0.5)), ("M30 dagegen, ab +1,0R", dict(flip="m30", min_r=1.0)), ("M30 dagegen, AUCH im Minus", dict(flip="m30", auch_im_minus=True)), ("M30+H1 dagegen, ab +0,5R", dict(flip="m30+h1", min_r=0.5)), ("M30+H1, AUCH im Minus", dict(flip="m30+h1", auch_im_minus=True))): Rs = lauf(ein, H, L, C, htf, h1, **kw) d = sum(Rs) - b_sum print(f" {etikett:<34}{kz(Rs)} Δ{d:>+6.0f}") if __name__ == "__main__": main()