#!/usr/bin/env python3
"""Wie teuer ist der SPÄTE Squeeze-Einstieg — und hilft eine Abstands-Bremse?

ANLASS (2026-08-05, User): „Der Auto-Trade wurde am höchsten Punkt einer
aufsteigenden Kerze geschlossen und ein neuer Long eröffnet. Aus meiner Sicht ist
das nicht klug." Realfall 08:33:20 — Ausbruchs-Level **75,757**, Einstieg
**75,822** = **0,28×ATR darüber**, oberhalb des Hochs (75,808) der laufenden
M5-Kerze; die Kerze schloss dann bei 75,693.

⚠ DER VERDACHT IST EIN DEPLOYMENT-DRIFT-KANDIDAT, kein Bauchgefühl:
`backtest_breakout_squeeze.py` hat den Squeeze mit Einstieg **AM LEVEL** validiert
(+0,14…+0,23 ØR). Live läuft `_check_auto_squeeze` alle ~5 s und schickt eine
MARKET-Order — der Kurs ist dann schon ein Stück über dem Level. Gemessen unter X,
betrieben unter Y: dieselbe Klasse wie die fünf dokumentierten Fälle.

GEMESSEN WIRD DESHALB
  A) Einstieg AM LEVEL          (die Backtest-Annahme)
  B) Einstieg am BAR-CLOSE      (realistisch: so weit ist der Kurs gelaufen, wenn
                                 `_squeeze_one` den Ausbruch das erste Mal sieht)
  C) B + Abstands-Bremse: Ausbruch überspringen, wenn der Kurs bereits mehr als
     X×ATR über/unter dem Level steht („zu spät, der Zug ist weg")
  D) B + Bremse, aber die übersprungenen Ausbrüche NACHTRÄGLICH prüfen — steigt
     man nur später ein oder gar nicht? (Hier: gar nicht, wie live.)

VORAB FIXIERTE ENTSCHEIDUNGSREGEL:
  Eine Bremse X wird nur empfohlen, wenn sie B in **ΣR und ØR in BEIDEN Hälften**
  schlägt UND die beiden Nachbar-Schwellen ebenfalls besser als B sind.

Sequentiell mit EINEM Positions-Slot (wie live), kanonischer Exit, Echtkosten.

Aufruf:  python backtest_squeeze_entry.py [bars]
"""
import sys

import MetaTrader5 as mt5

from backtest_cost_gate import _atr_series, _berlin, kandidaten, kennzahlen
from core.exit_model import LIVE, simulate

_SCHWELLEN = (0.10, 0.15, 0.20, 0.30, 0.40)


def lauf(K, H, L, C, lo, hi, am_level: bool, max_abstand: float | None):
    """Sequentiell, EIN Slot. `am_level`=True → Backtest-Annahme, sonst Bar-Close.
    `max_abstand` in ×ATR → Ausbruch überspringen, wenn der Kurs schon weiter ist."""
    rs, abst = [], []
    frei_ab = lo
    for k in K:
        i = k["i"]
        if not (lo <= i < hi) or i < frei_ab:
            continue
        d, lvl, atr = k["d"], k["lvl"], k["atr"]
        weg = (C[i] - lvl) * d / atr          # wie weit ist der Kurs schon drüber?
        if max_abstand is not None and weg > max_abstand:
            continue                          # zu spät → gar nicht einsteigen
        entry = lvl if am_level else C[i]
        r, xb = simulate(entry, d, atr, H, L, C, i + 1, LIVE,
                         timestop=True, use_tp=True, ret_bar=True)
        # Kosten immer auf den ATR bezogen; beim Bar-Close kommt der bereits
        # gelaufene Weg als echter Nachteil über den schlechteren `entry` rein.
        rs.append(r - k["kosten"])
        abst.append(weg)
        frei_ab = xb + 1
    return rs, abst


def lauf_limit(K, H, L, C, lo, hi, warte: int, puffer: float = 0.0):
    """Variante D — LIMIT am Ausbruchs-Level statt Market.

    Die Order liegt `warte` Bars lang am Level (+`puffer`×ATR in Trade-Richtung
    zurückversetzt = etwas günstiger). Kommt der Kurs zurück → Fill zum Limit-Preis;
    kommt er nicht zurück → **kein Trade** (der Zug ist weg, das ist der Preis).
    Damit wird Variante A überhaupt erst erreichbar: dort wird der Level-Preis
    einfach unterstellt, obwohl der Kurs ihn schon verlassen hat."""
    rs = []
    frei_ab = lo
    verpasst = 0
    for k in K:
        i = k["i"]
        if not (lo <= i < hi) or i < frei_ab:
            continue
        d, atr = k["d"], k["atr"]
        limit = k["lvl"] - d * puffer * atr
        fill = None
        for j in range(i + 1, min(i + 1 + warte, len(C) - 1)):
            # LONG: Limit unter dem Kurs → Fill, wenn das Bar-Tief es erreicht
            if (L[j] <= limit) if d > 0 else (H[j] >= limit):
                fill = j
                break
        if fill is None:
            verpasst += 1
            continue
        r, xb = simulate(limit, d, atr, H, L, C, fill + 1, LIVE,
                         timestop=True, use_tp=True, ret_bar=True)
        rs.append(r - k["kosten"])
        frei_ab = xb + 1
    return rs, verpasst


def lauf_stop(K, H, L, C, lo, hi, slip: float):
    """Variante E — ruhende STOP-Order am Ausbruchs-Level.

    Sie liegt bereits, während der Squeeze `armed` ist, und wird vom Ausbruch selbst
    ausgelöst → Fill am Level + `slip`×ATR (Auslösung erfolgt zum Markt, etwas
    Slippage bleibt). Kein Warten auf einen Rücklauf wie bei der Limit-Order, also
    auch KEINE verpassten Ausbrüche."""
    rs = []
    frei_ab = lo
    for k in K:
        i = k["i"]
        if not (lo <= i < hi) or i < frei_ab:
            continue
        d, atr = k["d"], k["atr"]
        entry = k["lvl"] + d * slip * atr
        r, xb = simulate(entry, d, atr, H, L, C, i + 1, LIVE,
                         timestop=True, use_tp=True, ret_bar=True)
        rs.append(r - k["kosten"])
        frei_ab = xb + 1
    return rs, 0


def zeile(lbl, k1, k2, extra=""):
    def f(k):
        if not k:
            return f"{'—':>34}"
        return (f"{k['n']:>5} {k['wr']:>5.1f}% {k['oer']:>+7.3f} "
                f"{k['pf']:>5.2f} {k['sr']:>+8.1f}")
    print(f"  {lbl:<30}{f(k1)}  |{f(k2)}  {extra}")


def main():
    n_bars = 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_bars)
    point = mt5.symbol_info(sym).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]; T = [int(b["time"]) 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("=" * 104)
    print(f"  SQUEEZE-EINSTIEG: am Level vs. hinterhergelaufen — {sym} M5, {N} Bars")
    print(f"  {_berlin(T[0]):%d.%m.%Y} bis {_berlin(T[-1]):%d.%m.%Y} · 2 Halbjahre · "
          f"sequentiell (EIN Slot) · kanonischer Exit · Echtkosten")
    print("=" * 104)
    K = kandidaten(H, L, C, T, SP, A)

    # Wie weit läuft der Kurs typischerweise, bis der Ausbruch sichtbar ist?
    weg = sorted((C[k["i"]] - k["lvl"]) * k["d"] / k["atr"] for k in K)
    print(f"  Abstand Kurs↔Level beim ERSTEN Sichtbarwerden des Ausbruchs (×ATR):")
    print(f"    Median {weg[len(weg)//2]:.2f} · 75 % {weg[3*len(weg)//4]:.2f} · "
          f"90 % {weg[int(0.9*len(weg))]:.2f} · Max {weg[-1]:.2f}   (n={len(weg)})")
    print(f"    Der Realfall vom 05.08. lag bei 0,28×ATR — "
          f"{100.0*sum(1 for w in weg if w <= 0.28)/len(weg):.0f} % der Fälle sind näher dran.")
    print()

    print(f"  {'Variante':<30}{'H1  n    WR      OeR    PF     SigmaR':>34}"
          f"  |{'H2  n    WR      OeR    PF     SigmaR':>34}")
    print("  " + "-" * 100)
    erg = {}
    for lbl, am_level, mx in (("A  Einstieg AM LEVEL", True, None),
                              ("B  Einstieg am Bar-Close", False, None)):
        r1, _ = lauf(K, H, L, C, 0, mid, am_level, mx)
        r2, _ = lauf(K, H, L, C, mid, N, am_level, mx)
        erg[lbl] = (kennzahlen(r1), kennzahlen(r2))
        zeile(lbl, *erg[lbl])
    print("  " + "-" * 100)
    for x in _SCHWELLEN:
        lbl = f"C  Bremse: max {x:.2f}xATR"
        r1, _ = lauf(K, H, L, C, 0, mid, False, x)
        r2, _ = lauf(K, H, L, C, mid, N, False, x)
        erg[lbl] = (kennzahlen(r1), kennzahlen(r2))
        zeile(lbl, *erg[lbl])

    print("  " + "-" * 100)
    for warte, puf in ((3, 0.0), (6, 0.0), (12, 0.0), (6, 0.1), (12, 0.1)):
        lbl = f"D  Limit am Level {warte}B" + (f" +{puf:.1f}" if puf else "")
        r1, v1 = lauf_limit(K, H, L, C, 0, mid, warte, puf)
        r2, v2 = lauf_limit(K, H, L, C, mid, N, warte, puf)
        erg[lbl] = (kennzahlen(r1), kennzahlen(r2))
        zeile(lbl, *erg[lbl], extra=f"verpasst {v1}/{v2}")

    # ── Variante E: RUHENDE STOP-ORDER am Level ──────────────────────────
    # Aus der Web-Recherche (2026-08-05): „don't chase breakouts with market orders;
    # place orders just beyond the breakout level." Genau das ist hier der Hebel —
    # eine PENDING BUY_STOP/SELL_STOP, die schon liegt, WÄHREND der Squeeze `armed`
    # ist, füllt am Level statt 0,31×ATR später. Anders als die Limit-Order (D)
    # wartet sie NICHT auf einen Rücklauf: sie wird vom Ausbruch selbst ausgelöst.
    # ⚠ Eine ausgelöste Stop-Order füllt zum Markt → etwas Slippage bleibt. Deshalb
    # mit mehreren realistischen Aufschlägen gerechnet, nicht mit dem Idealpreis.
    print("  " + "-" * 100)
    for slip in (0.00, 0.05, 0.10, 0.20):
        lbl = f"E  Stop-Order Level +{slip:.2f}"
        r1, _ = lauf_stop(K, H, L, C, 0, mid, slip)
        r2, _ = lauf_stop(K, H, L, C, mid, N, slip)
        erg[lbl] = (kennzahlen(r1), kennzahlen(r2))
        zeile(lbl, *erg[lbl], extra="(= Variante A)" if slip == 0 else "")

    a1, a2 = erg["A  Einstieg AM LEVEL"]
    b1, b2 = erg["B  Einstieg am Bar-Close"]
    print()
    print("=" * 104)
    print("  BEFUND 1 — was kostet das Hinterherlaufen? (A gegen B)")
    print("=" * 104)
    print(f"    OeR   H1 {a1['oer']:+.3f} -> {b1['oer']:+.3f} ({b1['oer']-a1['oer']:+.3f})"
          f"      H2 {a2['oer']:+.3f} -> {b2['oer']:+.3f} ({b2['oer']-a2['oer']:+.3f})")
    print(f"    SigmaR H1 {a1['sr']:+.1f} -> {b1['sr']:+.1f} ({b1['sr']-a1['sr']:+.1f})"
          f"      H2 {a2['sr']:+.1f} -> {b2['sr']:+.1f} ({b2['sr']-a2['sr']:+.1f})")
    print("    ⚠ B ist der LIVE-Fall. Weicht es stark ab, gilt die validierte")
    print("      Squeeze-Kennzahl (+0,14…+0,23 OeR) fuer den Live-Betrieb NICHT.")

    print()
    print("=" * 104)
    print("  BEFUND 2 — hilft eine Abstands-Bremse? (Regel VOR dem Lauf fixiert:")
    print("             SigmaR UND OeR in BEIDEN Haelften > B, plus beide Nachbarn)")
    print("=" * 104)

    def schlaegt(x):
        k1, k2 = erg[f"C  Bremse: max {x:.2f}xATR"]
        return (k1 and k2 and k1["sr"] > b1["sr"] and k2["sr"] > b2["sr"]
                and k1["oer"] > b1["oer"] and k2["oer"] > b2["oer"])

    treffer = []
    for idx, x in enumerate(_SCHWELLEN):
        nb = [_SCHWELLEN[j] for j in (idx - 1, idx + 1) if 0 <= j < len(_SCHWELLEN)]
        selbst, nb_ok = schlaegt(x), all(schlaegt(m) for m in nb)
        if selbst and nb_ok:
            treffer.append(x)
        print(f"    X = {x:.2f}xATR   "
              + ("BESTEHT" if (selbst and nb_ok) else
                 "schlaegt B, Nachbarn NICHT" if selbst else "faellt durch"))
    print()
    if treffer:
        print(f"    => Kandidat(en): {', '.join(f'{x:.2f}' for x in treffer)}")
    else:
        print("    => KEINE Schwelle besteht. Eine Abstands-Bremse ist nicht belegt —")
        print("       uebersprungene Ausbrueche waeren im Schnitt NICHT schlechter.")

    print()
    print("=" * 104)
    print("  BEFUND 3 — schlaegt eine LIMIT-Order am Level den Market-Einstieg?")
    print("             (Regel: SigmaR UND OeR in BEIDEN Haelften > B, Nachbarn ebenso)")
    print("=" * 104)
    d_treffer = []
    d_keys = [k for k in erg if k.startswith("D ")]
    for k in d_keys:
        k1, k2 = erg[k]
        gut = (k1 and k2 and k1["sr"] > b1["sr"] and k2["sr"] > b2["sr"]
               and k1["oer"] > b1["oer"] and k2["oer"] > b2["oer"])
        if gut:
            d_treffer.append(k)
        print(f"    {k:<30} {'BESSER als Market' if gut else 'faellt durch'}")
    print()
    if d_treffer:
        print(f"    => {len(d_treffer)} von {len(d_keys)} Limit-Varianten schlagen den")
        print("       Market-Einstieg in beiden Haelften.")
    else:
        print("    => Auch die Limit-Order rettet es nicht.")
    print()


if __name__ == "__main__":
    main()
