#!/usr/bin/env python3 """Volatilitäts-Squeeze-Breakout (B2, User-Idee 'Breakouterkennung'): Struktureller Ausbruch aus einer KOMPRESSION — NICHT aus dem EMA-Trend. Setup: die Spanne der letzten N Bars (Box) ist eng relativ zum ATR (Squeeze); danach bricht der Kurs k×ATR über/unter die Box → Einstieg in Ausbruchsrichtung. Exit = live-Modell (SL 2,0×ATR + Trailing 1,5, Breakeven 1,3), pessimistisch. Kosten = echter Bar-Spread/ATR (Ø ~0,265). 2 Halbjahre. R = Profit/ATR. Kernfrage (B3): Trägt der SQUEEZE-Filter netto in BEIDEN Hälften — schlägt er den Ausbruch aus einer BELIEBIGEN Box (ohne Kompression)? Sonst raus. """ import sys import MetaTrader5 as mt5 _MAXH = 288; _ATRMIN = 0.12 _N = 12 # Box-Länge (Bars) = 1 h auf M5 _W = 24 # Fenster nach der Box, in dem der Breakout erfolgen muss (2 h) _COOL = 12 # Cooldown nach einem Trade (Bars) gegen Überlappung _K = 0.1 # Ausbruch k×ATR über/unter die Box-Grenze # ⚠⚠ DIE MECHANIK LIEGT SEIT 2026-08-07 IN `core/squeeze_scan.py`. # Grund: `core/engine.py` und `weekly_review.py` haben sie von HIER importiert — # LIVE-Code, der an einem Backtest-Skript haengt, und die Live-Parameter wurden # ueber MUTIERTE Modul-Globale (`BQ._N = ...`) eingeschleust. Beides ist behoben. # Hier bleiben nur duenne Weiterleitungen, damit es EINE Implementierung gibt und # nicht zwei, die auseinanderdriften — der Fehler, den dieses Projekt bei # `sim_run`/`_ema_series` (Kopien in >=6 Skripten) schon einmal gemacht hat. # ⚠ Die Zahlen dieses Backtests bleiben unveraendert: bitgenau geprueft in # `tests/test_squeeze_scan.py` (500 synthetische Kursreihen). from core.squeeze_scan import atr_series as _core_atr from core.squeeze_scan import exit_legacy as _core_exit from core.squeeze_scan import scan as _core_scan def _atr_series(H, L, C, p=14): return _core_atr(H, L, C, p) def sim(entry, d, atr, H, L, C, j0, sl_atr=2.0, trail=1.5, trail_on=0.3, be_on=1.3): return _core_exit(entry, d, atr, H, L, C, j0, sl_atr=sl_atr, trail=trail, trail_on=trail_on, be_on=be_on, max_hold=_MAXH) def rep(name, Rs): if not Rs: print(f" {name:<32} -"); return n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs) up = sum(x for x in Rs if x > 0); dn = -sum(x for x in Rs if x < 0) pf = up/dn if dn > 0 else 9.99 print(f" {name:<32} Trades={n:>4} Treffer={100*w/n:>3.0f}% " f"ØR={s/n:+.3f} PF={pf:.2f} ΣR={s:+.0f}") def scan(H, L, C, A, SP, point, lo_i, hi_i, squeeze_mult): """Weiterleitung auf `core.squeeze_scan.scan` mit den Modul-Globalen dieses Skripts als Parameter (`main()` variiert `_N`/`_K` beim Sweep).""" return _core_scan(H, L, C, A, SP, point, lo_i, hi_i, squeeze_mult, box_n=_N, k=_K, atr_min=_ATRMIN, fenster=_W, cooldown=_COOL, max_hold=_MAXH) def main(): n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 mt5.initialize() sym = None for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"): if mt5.symbol_info(c): sym = c; break bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n+_MAXH+30) 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] A = _atr_series(H, L, C) N = len(C); mid = N//2 print("="*88) print(f" Volatilitäts-Squeeze-Breakout — {sym} M5 ({N} Bars · Box={_N} · Ausbruch={_K}×ATR)") print(f" Exit=live(SL2,0/Trail1,5/BE1,3) · Kosten=Bar-Spread/ATR · 2 Halbjahre") print("="*88) for label, lo, hi in (("H1 (alt)", 0, mid), ("H2 (neu)", mid, N)): print(f"\n{label}:") _, any_R = scan(H, L, C, A, SP, point, lo, hi, 99.0) rep("Ausbruch OHNE Squeeze-Filter", any_R) for sm in (2.5, 2.0, 1.5): sq_R, _ = scan(H, L, C, A, SP, point, lo, hi, sm) rep(f"Squeeze Box<={sm}×ATR", sq_R) print(f"\n Squeeze trägt nur, wenn ØR & PF in BEIDEN Hälften > 'ohne Filter' UND ØR netto > 0.") if __name__ == "__main__": main()