#!/usr/bin/env python3
"""Fuellt die ruhende Squeeze-Order wirklich so, wie der Backtest unterstellt?

ANLASS (2026-08-06): Bei `backtest_momentum_v2.py` ist aufgefallen, dass ein
"Einstieg am Level" zwei sehr verschiedene Dinge bedeuten kann:
  (a) einsteigen, WO das Signal ausgeloest hat — also dort, wo der Bar-CLOSE das
      Level ueberschritten hat, und zwar zum Level-Preis;
  (b) einsteigen, WANN die liegende Order gefuellt haette — bei der ERSTEN
      Beruehrung des Levels, unabhaengig davon, wo der Bar schliesst.
Live gilt (b). Der Unterschied ist kein Detail: beim Momentum lag er bei **0,45 R**
und hat aus +0,40 ein −0,06 gemacht — die Variante (a) konditioniert auf den
Bar-Schluss, den eine liegende Order nicht kennen kann. Look-ahead.

⚠ DIESELBE FRAGE MUSS FUER DEN SQUEEZE BEANTWORTET WERDEN, denn `squeeze_pending_
entry` laeuft seit 05.08. mit ECHTEM Geld, und die Erwartung dahinter (+0,456/
+0,611 aus `backtest_squeeze_entry.py`) stammt aus Variante (a).

GRUND ZUR HOFFNUNG — aber eben nur Hoffnung, bis gemessen: die Squeeze-Box steht
ueber viele Bars still, das Level ist also ein echter, stabiler Ort fuer eine
Order. Das Momentum-Level wandert dagegen mit `C[i−N]` jede Bar mit. Wenn der
Unterschied klein ist, war die Hoffnung berechtigt; ist er gross, ist die
Live-Erwartung zu hoch angesetzt und gehoert korrigiert.

Aufruf:  python backtest_squeeze_touchfill.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

_ATRMIN = 0.06


def boxen(H, L, C, A):
    """Je Bar die AKTUELLE Box-Grenze + Ausbruchs-Level — auch ohne Ausbruch.

    Das ist der Zustand, in dem die ruhende Order liegt (`armed`): die Box steht,
    die beiden Stop-Level stehen. Ob der Kurs sie beruehrt, entscheidet der Markt.
    """
    out = []
    for i in range(wr._SQ_N + 2, len(C) - 2):
        atr = A[i - 1]                      # am Ende der Vorbar bekannt
        if not atr or atr < _ATRMIN:
            out.append(None); continue
        hb = H[i - wr._SQ_N - 1:i - 1]; lb = L[i - wr._SQ_N - 1:i - 1]
        if not hb or not lb:
            out.append(None); continue
        hi, lo = max(hb), min(lb)
        if (hi - lo) / atr > wr._SQ_MULT:   # keine Kompression → keine Order
            out.append(None); continue
        out.append((i, hi + wr._SQ_K * atr, lo - wr._SQ_K * atr, atr))
    return out


def lauf_close(H, L, C, SP, A, lo, hi):
    """(a) Projekt-Standard: Einstieg dort, wo `_squeeze_one` `active` meldet
    (Bar-CLOSE hat das Level gebrochen), zum Level-Preis."""
    rs, frei_ab, last = [], lo, None
    for i in range(wr._SQ_N + 2, len(C) - 2):
        if not (lo <= i < hi) or i < frei_ab:
            continue
        atr = A[i]
        if not atr or atr < _ATRMIN:
            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
        d = 1 if st["dir"] == "LONG" else -1
        r, xb = simulate(float(st["level"]), d, atr, H, L, C, i + 1, LIVE,
                         timestop=True, use_tp=True, ret_bar=True)
        rs.append(r - (SP[i] if SP[i] > 0 else 0.0225) / atr)
        frei_ab = xb + 1
    return rs


def lauf_touch(H, L, C, SP, A, B, lo, hi):
    """(b) Realitaet: die Order liegt an beiden Box-Grenzen und fuellt bei der
    ERSTEN Beruehrung — auch wenn der Bar wieder in die Box zurueckfaellt."""
    rs, frei_ab = [], lo
    for eintrag in B:
        if eintrag is None:
            continue
        i, lvl_hi, lvl_lo, atr = eintrag
        if not (lo <= i < hi) or i < frei_ab:
            continue
        d = 0
        if H[i] >= lvl_hi:
            d, lvl = 1, lvl_hi
        elif L[i] <= lvl_lo:
            d, lvl = -1, lvl_lo
        if not d:
            continue
        r, xb = simulate(lvl, d, atr, H, L, C, i + 1, LIVE,
                         timestop=True, use_tp=True, ret_bar=True)
        rs.append(r - (SP[i] if SP[i] > 0 else 0.0225) / 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
    sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD")
                if mt5.symbol_info(c)), None)
    b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
    point = mt5.symbol_info(sym).point
    mt5.shutdown()
    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"]) * point for x in b]
    A = _atr_series(H, L, C)
    B = boxen(H, L, C, A)
    m = len(C) // 2

    print("=" * 92)
    print(f"  SQUEEZE — Fill bei BAR-CLOSE-Ausbruch gegen Fill bei BERUEHRUNG ({sym})")
    print("=" * 92)
    print(f"  {len(C)} M5-Bars · kanonischer Exit · Echtkosten · sequentiell EIN Slot")
    print()
    a1 = kennzahlen(lauf_close(H, L, C, SP, A, 0, m))
    a2 = kennzahlen(lauf_close(H, L, C, SP, A, m, len(C)))
    b1 = kennzahlen(lauf_touch(H, L, C, SP, A, B, 0, m))
    b2 = kennzahlen(lauf_touch(H, L, C, SP, A, B, m, len(C)))
    for lbl, x1, x2 in (("(a) Close bricht Level (Backtest-Standard)", a1, a2),
                        ("(b) Order fuellt bei Beruehrung (LIVE)", b1, b2)):
        if not x1 or not x2:
            print(f"  {lbl:<44} — keine Trades"); continue
        print(f"  {lbl:<44} H1 ØR {x1['oer']:+.3f} PF {x1['pf']:.2f} n{x1['n']:<5} | "
              f"H2 ØR {x2['oer']:+.3f} PF {x2['pf']:.2f} n{x2['n']:<5}")
    if all((a1, a2, b1, b2)):
        print()
        print(f"  Look-ahead-Aufschlag: H1 {a1['oer']-b1['oer']:+.3f} · "
              f"H2 {a2['oer']-b2['oer']:+.3f} R je Trade")
        print(f"  Zusaetzliche Fills durch Beruehrung: H1 {b1['n']-a1['n']:+d} · "
              f"H2 {b2['n']-a2['n']:+d}")
        gut = b1["oer"] > 0 and b2["oer"] > 0 and b1["pf"] > 1 and b2["pf"] > 1
        print()
        print(f"  → Der Squeeze haelt der realistischen Fill-Annahme "
              f"{'STAND ✅' if gut else 'NICHT stand ❌'}")
        print("  ⚠ Zum Vergleich Momentum (`backtest_momentum_v2.py`): dort betrug")
        print("    der Aufschlag ~0,45 R und drehte +0,40 auf −0,06.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
