#!/usr/bin/env python3
"""Haette der Auto-Squeeze in der letzten Woche Gewinn gemacht?

ANLASS (2026-08-11, User: „analysiere den Breakout-Auto-Trader, ob er zuverlaessig
funktioniert; simuliere ueber die letzte Woche, ob er Gewinn erzielt haette").

⚠⚠ WAS DIESE SIMULATION IST UND WAS NICHT. **Eine Woche ist KEIN Edge-Nachweis.**
Der Squeeze feuert 2–3×/Woche; das sind Stichproben von n≈2–6, und im Projekt sind
Einzelzeitraeume schon dreimal faelschlich zum Beleg erhoben worden (15-Minuten-
Regel 31.07., Nacht-Guard 05.08., das Veto im Wochentest 08.08.). Gemessen wird
hier die **MECHANIK**: feuert die Regel ueberhaupt, zu welchen Preisen, und was
haette der kanonische Exit daraus gemacht.

MODELLIERT WIRD DER LIVE-PFAD (Stand seit 05.08.):
  · echte Live-Funktion `WaveRecommender._squeeze_one` — kein Nachbau
  · **ruhende Stop-Order** an der Box-Grenze, solange `armed`; Fuellung bei
    BERUEHRUNG (nicht bei Bar-Schluss). Das ist der teurere und richtige Fall:
    (a) „Close bricht Level" ist Look-ahead und rund +0,32 R zu optimistisch
    (`backtest_squeeze_touchfill.py`).
  · Nacht-Sperre 3–6 Uhr Berlin (`auto_squeeze_night_hours`)
  · ATR-Floor, EIN Positions-Slot (sequentiell), Echtkosten aus dem Bar-Spread
  · **kanonischer Exit** `core.exit_model.LIVE` — ausdruecklich NICHT
    `squeeze_scan.exit_legacy` (Trail 1,5 · kein Lock · kein Time-Stop · kein TP),
    gegen den der B4-Monitor bis heute misst.

Aufruf:  python backtest_squeeze_woche.py [tage]        (Default 7)
"""
from __future__ import annotations

import sys
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo

import MetaTrader5 as mt5

from core.exit_model import LIVE, simulate
from core.wave_rec import WaveRecommender

_BERLIN = ZoneInfo("Europe/Berlin")
_BROKER = timezone(timedelta(hours=3))
_NACHT = {3, 4, 5, 6}
_ATR_MIN = 0.06
_SLIPPAGE = (0.0, 0.05, 0.10, 0.20)     # ×ATR Aufschlag auf den Fill


def _berlin(ts):
    return datetime.fromtimestamp(ts, timezone.utc).replace(
        tzinfo=_BROKER).astimezone(_BERLIN)


def atr_series(H, L, C, p=14):
    tr = [0.0]
    for i in range(1, len(C)):
        tr.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
    out = [None]
    for i in range(1, len(C)):
        seg = tr[max(1, i - p + 1):i + 1]
        out.append(sum(seg) / len(seg))
    return out


def lauf(T, H, L, C, SP, A, lo_i, slip=0.0, nacht=True):
    """Sequentiell EIN Slot, ruhende Stop-Order, Fuellung bei Beruehrung."""
    trades = []
    i = lo_i
    n = len(C)
    while i < n - LIVE.max_hold - 2:
        atr = A[i]
        if not atr or atr < _ATR_MIN:
            i += 1; continue
        if nacht and _berlin(T[i]).hour in _NACHT:
            i += 1; continue
        sq = WaveRecommender._squeeze_one(H[:i + 1], L[:i + 1], C[:i + 1], atr)
        if not sq or sq.get("box_hi") is None or sq.get("state") is None:
            i += 1; continue
        # ⚠ Die Box stammt aus den N bereits GESCHLOSSENEN Bars vor i — eine
        #   ruhende Order darf waehrend Bar i also legitim gefuellt werden.
        up = sq["box_hi"] + 0.1 * atr
        dn = sq["box_lo"] - 0.1 * atr
        traf_up = H[i] >= up
        traf_dn = L[i] <= dn
        if not (traf_up or traf_dn):
            i += 1; continue
        if traf_up and traf_dn:
            # Beide Seiten in EINEM Bar — aus OHLC nicht aufloesbar. Die naeher
            # am Open liegende Seite wird zuerst beruehrt worden sein.
            traf_up = abs(up - C[i - 1]) <= abs(C[i - 1] - dn)
            traf_dn = not traf_up
        d = 1 if traf_up else -1
        fill = (up + slip * atr) if traf_up else (dn - slip * atr)
        kost = (SP[i] if SP[i] > 0 else 0.0225) / atr
        R, xb = simulate(fill, d, atr, H, L, C, i + 1, LIVE, ret_bar=True)
        trades.append({"i": i, "t": T[i], "d": d, "fill": fill, "atr": atr,
                       "R": R - kost, "bars": xb - i,
                       "box_atr": sq.get("box_atr")})
        i = xb + 1                      # Slot war belegt
    return trades


def kz(tr, lbl):
    if not tr:
        return f"  {lbl:<34} keine Ausbrueche"
    n = len(tr); Rs = [x["R"] for x in tr]
    w = sum(1 for x in Rs if x > 0)
    g = sum(x for x in Rs if x > 0); v = -sum(x for x in Rs if x < 0)
    ow = g / w if w else 0.0
    ol = v / (n - w) if n - w else 0.0
    return (f"  {lbl:<34} n={n:>3}  Treffer {100*w/n:>3.0f} %  OeR {sum(Rs)/n:>+6.3f}  "
            f"PF {(g/v if v else 9.99):>4.2f}  Verh {(ow/ol if ol else 9.99):>4.2f}  "
            f"SumR {sum(Rs):>+6.2f}")


def main():
    tage = int(sys.argv[1]) if len(sys.argv) > 1 else 7
    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, 20000)
    point = mt5.symbol_info(sym).point
    mt5.shutdown()
    T = [int(b["time"]) for b in bars]
    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)

    grenze = T[-1] - tage * 86400
    lo_i = next(i for i, t in enumerate(T) if t >= grenze)
    lo_i = max(lo_i, 60)

    print("=" * 100)
    print(f"  AUTO-SQUEEZE — Simulation der letzten {tage} Tage ({sym})")
    print(f"  {_berlin(T[lo_i]):%d.%m %H:%M} bis {_berlin(T[-1]):%d.%m %H:%M}   "
          f"{len(C)-lo_i} M5-Bars")
    print("  Live-Pfad: ruhende Stop-Order an der Box, Fuellung bei BERUEHRUNG,")
    print("  Nacht 3-6 gesperrt, EIN Slot, kanonischer Exit, Echtkosten.")
    print("=" * 100)

    basis = lauf(T, H, L, C, SP, A, lo_i)
    print("\n  ── Fill-Slippage (eine Stop-Order fuellt zum Markt, nicht zum Ideal) ──")
    for s in _SLIPPAGE:
        print(kz(lauf(T, H, L, C, SP, A, lo_i, slip=s), f"Slippage +{s:.2f}xATR"))

    print("\n  ── Gegenprobe: ohne Nacht-Sperre ──")
    print(kz(lauf(T, H, L, C, SP, A, lo_i, nacht=False), "alle Stunden"))

    print(f"\n  ── die einzelnen Ausbrueche (Slippage 0) ──")
    if not basis:
        print("    KEINER — die Regel hat in diesem Fenster nicht gefeuert.")
    for x in basis:
        print(f'    {_berlin(x["t"]):%d.%m %H:%M}  {"LONG " if x["d"]>0 else "SHORT"} '
              f'@ {x["fill"]:.3f}  Box {x["box_atr"]:.2f}xATR  '
              f'{x["bars"]*5:>4} min  R {x["R"]:>+6.2f}')

    if basis:
        # EUR-Naeherung: R x ATR x Lots x ~87,6 EUR/$/Lot (robust geschaetzt, s. CLAUDE.md)
        lots = 2.5
        eur = sum(x["R"] * x["atr"] for x in basis) * lots * 87.6
        print(f"\n  grob in EUR bei {lots} Lots: {eur:>+8.0f} EUR")
        print("  ⚠ Naeherung — die echte Lot-Groesse schwankt mit dem Konto.")

    print("\n  " + "-" * 96)
    print("  ⚠⚠ EINE WOCHE IST KEIN EDGE-NACHWEIS. Der Squeeze feuert 2-3x/Woche;")
    print("     n dieser Groesse ist Rauschen. Die 2-Stichproben-Regel gilt weiter.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
