#!/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())
