#!/usr/bin/env python3 """Traegt der validierte Squeeze auch auf ANDEREN Instrumenten? ANLASS (2026-08-06): `analyze_signal_live.py` hat gezeigt, dass die Richtungs- aussage der Empfehlung ein Muenzwurf ist (48–51 % ueber 1.071 Episoden). Der Ertrag kommt aus Einstiegspreis und Exit-Asymmetrie. Damit ist das Grundproblem nicht die QUALITAET des Squeeze, sondern seine **Frequenz** — 2–3×/Woche auf einem Instrument. Darauf hat das Projekt bisher zweimal reagiert: mehr Signale auf demselben Instrument suchen (**23 verworfene Eingriffe**) oder auf eine feinere Zeitebene gehen (`backtest_squeeze_m1.py` — die Kosten erledigen es). Die dritte Achse ist unberuehrt: **dasselbe Signal, mehr Instrumente.** Praezedenzfall: `breakout_k=0,3` wurde 2026-07-09 auf Brent nachvalidiert (ØR +0,305) — die Mechanik Kompression→Ausbruch ist nicht oelspezifisch. ⚠⚠ ZWEI FALLEN, DIE HIER EINGEBAUT SIND — beide sind im Projekt schon einmal teuer gewesen: 1) **Der ATR-Floor 0,06 ist ein WTI-DOLLAR-Wert.** Auf Gold (ATR ~3) waere er wirkungslos, auf NatGas (~0,05) wuerde er fast alles wegwerfen — und ein Filter, der nach einer Eigenschaft aussortiert, die mit der untersuchten Achse korreliert, ERZEUGT den Befund (Lehre aus `analyze_tageszeit.py` und dem ORB-Selektions-Artefakt). Der Floor wird deshalb **relativ** gesetzt: derselbe Anteil am Median-ATR wie bei WTI. 2) **MEHRFACHVERGLEICH.** Acht Instrumente = acht Chancen auf einen Zufalls- treffer. Bei 5 % Fehlalarmquote ist ~0,4 davon zu erwarten. Deshalb gilt die Regel unten UND es wird ausgewiesen, wie viele Kandidaten bestanden haben — bestehen 1 von 8, ist das etwas anderes als 6 von 8. VORAB FIXIERTE ENTSCHEIDUNGSREGEL (vor dem ersten Lauf festgelegt): Ein Instrument gilt nur dann als Kandidat, wenn (1) ØR > 0 UND PF > 1 in BEIDEN Haelften, (2) mindestens 40 Ausbrueche je Haelfte (sonst Rauschen), (3) die Nachbar-Parameter (Box 10/15 × k 0,1/0,2) das Vorzeichen nicht kippen, (4) SpotCrude als KONTROLLE seine bekannten Werte reproduziert — sonst ist die Pipeline defekt und nichts davon zaehlt. Aufruf: python backtest_squeeze_multi.py [bars] """ import sys import MetaTrader5 as mt5 import core.wave_rec as wr from backtest_cost_gate import _atr_series, kennzahlen from core.exit_model import LIVE, simulate from core.wave_rec import WaveRecommender _SYMBOLE = ["SpotCrude", # Kontrolle — muss reproduzieren "SpotBrent", "NatGas", "Gasoline", "Copper", "XAUUSD", "XAGUSD", "NAS100", "GER40", "USDX"] _MIN_N = 40 def hole(sym, n): if not mt5.symbol_select(sym, True): return None si = mt5.symbol_info(sym) b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n) if b is None or len(b) < 5000 or si is None: return None return {"H": [float(x["high"]) for x in b], "L": [float(x["low"]) for x in b], "C": [float(x["close"]) for x in b], "SP": [float(x["spread"]) * si.point for x in b], "digits": si.digits} def kandidaten(d, A, floor, box=None, k=None, mult=None): """`mult` = Kompressions-Schwelle. Sehr hoch gesetzt (99) entfaellt die Kompressions-Bedingung → Ausbruch aus einer BELIEBIGEN Box = die Kontrolle, die den Squeeze auf WTI ueberhaupt erst validiert hat (dort schlug er die beliebige Box in BEIDEN Haelften: +0,227/+0,141 gegen −0,045/+0,161).""" alt_n, alt_k, alt_m = wr._SQ_N, wr._SQ_K, wr._SQ_MULT if box is not None: wr._SQ_N = box if k is not None: wr._SQ_K = k if mult is not None: wr._SQ_MULT = mult H, L, C, SP = d["H"], d["L"], d["C"], d["SP"] out, last = [], None try: for i in range(wr._SQ_N + 2, len(C) - 2): atr = A[i] if not atr or atr < floor: continue w0 = i - wr._SQ_N - 2 st = WaveRecommender._squeeze_one(H[w0:i + 1], L[w0:i + 1], C[w0:i + 1], atr) if st.get("state") != "active": last = None continue sig = (st["dir"], st["level"]) if sig == last: continue last = sig # ⚠ Kosten: kein WTI-Ersatzwert (0,0225) fuer fremde Instrumente — # fehlt der Bar-Spread, wird der Median dieses Symbols genommen. out.append({"i": i, "d": 1 if st["dir"] == "LONG" else -1, "lvl": float(st["level"]), "atr": atr, "sp": SP[i]}) finally: wr._SQ_N, wr._SQ_K, wr._SQ_MULT = alt_n, alt_k, alt_m return out def lauf(K, d, lo, hi, med_sp, slip=0.0): H, L, C = d["H"], d["L"], d["C"] rs, frei_ab = [], lo for kd in K: i = kd["i"] if not (lo <= i < hi) or i < frei_ab: continue sp = kd["sp"] if kd["sp"] > 0 else med_sp entry = kd["lvl"] + kd["d"] * slip * kd["atr"] r, xb = simulate(entry, kd["d"], kd["atr"], H, L, C, i + 1, LIVE, timestop=True, use_tp=True, ret_bar=True) rs.append(r - sp / kd["atr"]) frei_ab = xb + 1 return rs def main(): n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 if not mt5.initialize(): print("MT5 nicht erreichbar"); return 1 daten, atr_med = {}, {} for s in _SYMBOLE: d = hole(s, n) if not d: print(f" ⚠ {s}: keine Daten / nicht handelbar — uebersprungen") continue d["A"] = _atr_series(d["H"], d["L"], d["C"]) gute = sorted(a for a in d["A"][20:] if a > 0) if not gute: continue atr_med[s] = gute[len(gute) // 2] sps = sorted(x for x in d["SP"][20:] if x > 0) d["med_sp"] = sps[len(sps) // 2] if sps else 0.0 daten[s] = d mt5.shutdown() if "SpotCrude" not in daten: print("SpotCrude fehlt — ohne Kontrolle keine Aussage"); return 1 # Relativer Floor: derselbe Anteil am Median-ATR wie live bei WTI rel = 0.06 / atr_med["SpotCrude"] print("=" * 100) print(" SQUEEZE AUF MEHREREN INSTRUMENTEN — dasselbe Signal, mehr Maerkte") print("=" * 100) print(f" Bars je Symbol: {n} (M5) · Einstieg AM Level (ruhende Stop-Order) ·") print(f" kanonischer Exit · Echtkosten aus dem Bar-Spread · sequentiell EIN Slot") print(f" ATR-Floor relativ: {rel:.3f} × Median-ATR " f"(= 0,06 $ bei WTI, massstabsgerecht uebertragen)") print() print(f" {'Symbol':<12} {'Med ATR':>9} {'Med Spr':>9} {'Spr/ATR':>8} " f"{'H1 ØR':>8} {'PF':>5} {'n':>5} | {'H2 ØR':>8} {'PF':>5} {'n':>5} Urteil") print(" " + "-" * 96) besteht = [] for s, d in daten.items(): floor = rel * atr_med[s] K = kandidaten(d, d["A"], floor) m = len(d["C"]) // 2 r1 = lauf(K, d, 0, m, d["med_sp"]) r2 = lauf(K, d, m, len(d["C"]), d["med_sp"]) k1, k2 = kennzahlen(r1), kennzahlen(r2) if not k1 or not k2: print(f" {s:<12} — keine Ausbrueche") continue ok = (k1["oer"] > 0 and k2["oer"] > 0 and k1["pf"] > 1 and k2["pf"] > 1 and k1["n"] >= _MIN_N and k2["n"] >= _MIN_N) duenn = "" if (k1["n"] >= _MIN_N and k2["n"] >= _MIN_N) else " (zu duenn)" print(f" {s:<12} {atr_med[s]:>9.4f} {d['med_sp']:>9.4f} " f"{d['med_sp']/atr_med[s]:>8.3f} " f"{k1['oer']:>+8.3f} {k1['pf']:>5.2f} {k1['n']:>5} | " f"{k2['oer']:>+8.3f} {k2['pf']:>5.2f} {k2['n']:>5} " f"{'✅' if ok else '❌'}{duenn}") if ok and s != "SpotCrude": besteht.append(s) print() print("=" * 100) print(" NACHBAR-PARAMETER der bestandenen Kandidaten (Box × k)") print("=" * 100) if not besteht: print(" keiner bestanden — nichts zu pruefen") fest = [] for s in besteht: d = daten[s] floor = rel * atr_med[s] m = len(d["C"]) // 2 alle_ok, zeilen = True, [] for box, kk in ((10, 0.1), (15, 0.1), (10, 0.2), (15, 0.2)): K = kandidaten(d, d["A"], floor, box=box, k=kk) a, b = (kennzahlen(lauf(K, d, 0, m, d["med_sp"])), kennzahlen(lauf(K, d, m, len(d["C"]), d["med_sp"]))) if not a or not b: alle_ok = False zeilen.append(f" Box {box} k {kk}: keine Ausbrueche") continue gut = a["oer"] > 0 and b["oer"] > 0 alle_ok &= gut zeilen.append(f" Box {box} k {kk}: H1 {a['oer']:+.3f} (n{a['n']}) | " f"H2 {b['oer']:+.3f} (n{b['n']}) {'✅' if gut else '❌'}") print(f" {s}:") for z in zeilen: print(z) print(f" → {'ROBUST' if alle_ok else 'KIPPT — verworfen'}") if alle_ok: fest.append(s) print() print("=" * 100) print(" KONTROLLE — schlaegt die KOMPRESSION den Ausbruch aus BELIEBIGER Box?") print("=" * 100) print(" ⚠ Ohne diese Zeile waere der Befund nicht interpretierbar: 'Breakout +") print(" Trailing' hat generisch Edge. Traegt die Kompression nichts bei, misst") print(" man nur den Exit — und dann braucht es kein Squeeze-Modul.") print(f" {'Symbol':<12} {'Squeeze H1/H2':>22} {'beliebige Box H1/H2':>24} Beitrag") for s in fest: d = daten[s]; floor = rel * atr_med[s]; m = len(d["C"]) // 2 Ks = kandidaten(d, d["A"], floor) Kb = kandidaten(d, d["A"], floor, mult=99.0) a1, a2 = (kennzahlen(lauf(Ks, d, 0, m, d["med_sp"])), kennzahlen(lauf(Ks, d, m, len(d["C"]), d["med_sp"]))) b1, b2 = (kennzahlen(lauf(Kb, d, 0, m, d["med_sp"])), kennzahlen(lauf(Kb, d, m, len(d["C"]), d["med_sp"]))) if not all((a1, a2, b1, b2)): continue gut = a1["oer"] > b1["oer"] and a2["oer"] > b2["oer"] print(f" {s:<12} {a1['oer']:>+10.3f}/{a2['oer']:>+10.3f} " f"{b1['oer']:>+12.3f}/{b2['oer']:>+10.3f} " f"{'✅ Kompression traegt' if gut else '❌ kein Beitrag'}") print() print("=" * 100) print(" SLIPPAGE-HAERTETEST — die Stop-Order loest aus, fuellt aber zum Markt") print("=" * 100) for s in fest: d = daten[s]; floor = rel * atr_med[s]; m = len(d["C"]) // 2 K = kandidaten(d, d["A"], floor) zeile = f" {s:<12}" for sl in (0.0, 0.10, 0.20): x1 = kennzahlen(lauf(K, d, 0, m, d["med_sp"], slip=sl)) x2 = kennzahlen(lauf(K, d, m, len(d["C"]), d["med_sp"], slip=sl)) if x1 and x2: zeile += f" +{sl:.2f}: {x1['oer']:+.3f}/{x2['oer']:+.3f}" print(zeile) print() print("=" * 100) print(f" ERGEBNIS: {len(fest)} von {len(daten)-1} geprueften Fremd-Instrumenten " f"halten alle vier Bedingungen") if fest: print(f" Kandidaten: {', '.join(fest)}") print() print(" ⚠ MEHRFACHVERGLEICH mitlesen: bei 5 % Fehlalarmquote und " f"{len(daten)-1} Tests") print(" waere ~0,4 Zufallstreffer zu erwarten. 1 von 9 ist deshalb schwach,") print(" mehrere sind ein Muster. Die Nachbar-Pruefung senkt die Rate zusaetzlich.") print(" ⚠ NICHT gemessen: Korrelation der Instrumente untereinander (mehrere") print(" Slots gleichzeitig = evtl. dasselbe Risiko doppelt), Swap/Kommission") print(" je Symbol, Handelszeiten und Mindest-Lots. Das gehoert VOR einen Bau.") return 0 if __name__ == "__main__": sys.exit(main())