#!/usr/bin/env python3
"""ENGER Initial-Stop fuer die ersten N Bars, danach der normale — traegt das?

USER-IDEE (2026-08-13): „beim Eroeffnen den SL sehr eng setzen, so dass Trades
in die falsche Richtung schnell geschlossen werden. Laeuft der Trade richtig,
den SL etappenweise nachziehen."

⚠ Der zweite Teil ist bereits gebaut (die Phasen-Ratsche in `core/trailing.py`).
Neu und ungemessen ist allein der erste: ein **zeitlich befristeter** enger Stop.
Nicht zu verwechseln mit einem durchgehend engen Stop — DER ist zweimal gemessen
(`backtest_sl_width.py` 04.08., `backtest_sl_basis.py` 05.08.) und schlaegt den
Live-Stop in KEINER Variante beidhaelftig.

⚠⚠ WIE ES OHNE EXIT-NACHBAU GEHT (die Kopiererei hat 4 divergierende Exit-
Modelle erzeugt — das ist die dokumentierte Falle):
  Lauf A = `simulate` mit sl_atr = ENG  ·  Lauf B = `simulate` mit sl_atr = WEIT
  Beide laufen auf DEMSELBEN Kurspfad. Innerhalb der ersten N Bars ist der
  EINZIGE Unterschied zwischen A und B der SL-Abstand; jeder Nicht-SL-Ausstieg
  (TP, Time-Stop) faellt in beiden identisch aus. Also gilt exakt:
        R_gestaffelt = R_A  wenn A vor/bei Bar N aussteigt, sonst R_B
  Kein Nachbau, keine Naeherung.

⚠⚠ DIE BINDENDE GRENZE — bitte vor der Interpretation lesen:
  `analyze_trail_resolution.py` (05.08.) hat gemessen, dass eine BAR-Simulation
  **enge Stops systematisch UEBERSCHAETZT** (Kipp-Punkt ~0,3xATR): das Modell
  nimmt den Bar-Hochpunkt mit und steigt erst im naechsten Bar aus. Ein
  POSITIVES Ergebnis fuer die enge Variante ist deshalb nach oben verzerrt und
  muesste abgeschlagen werden. Ein NEGATIVES ist belastbar.
  Dazu: die Exit-Slippage ist ABSOLUT (Median 0,017 $, Ø 0,234 $). In R gerechnet
  trifft sie alle Varianten gleich stark — sie verschiebt das Niveau, nicht die
  Rangfolge; die Spalte steht trotzdem dabei.

Aufruf:  python backtest_sl_staged.py [bars]
"""
from __future__ import annotations

import random
import sys
from dataclasses import replace

import MetaTrader5 as mt5

from core.exit_model import LIVE, simulate
from core.squeeze_scan import atr_series, scan

_SQ_N, _SQ_K, _SQ_MULT = 10, 0.1, 2.5
_ATRMIN = 0.06          # ⚠ LIVE-Wert; der Modul-Default 0,12 waere eine andere Population
_WARTE = 8
# Live-Stop: 2,0xATR_M15 ~= 3,4xATR_M5 (Median ueber 121 echte Trades, 13.08.)
_WEIT_LIVE = 3.4


def entries(H, L, C, A, SP, point, lo, hi):
    got = []
    scan(H, L, C, A, SP, point, lo, hi, _SQ_MULT, box_n=_SQ_N, k=_SQ_K,
         atr_min=_ATRMIN, exit_fn=lambda e, d, a, h, l, c, j: 0.0,
         on_entry=lambda j, d, lvl, a, komp: (
             got.append({"j": j, "d": d, "lvl": lvl, "atr": a}) if komp else None))
    return got


def lauf(ein, H, L, C, SP, eng, nbars, weit, slip=0.0):
    """Sequentiell EIN Slot. `eng=None` → durchgehend `weit` (die Basis)."""
    pW = replace(LIVE, sl_atr=weit)
    pE = None if eng is None else replace(LIVE, sl_atr=eng)
    Rs, n_frueh = [], 0
    frei_ab, grenze = -1, len(C) - LIVE.max_hold - 2
    for e in ein:
        i, d, lvl, a = e["j"], e["d"], e["lvl"], e["atr"]
        if i < frei_ab or i >= grenze or not a:
            continue
        fill = None
        for k in range(i + 1, min(i + 1 + _WARTE, grenze)):
            if L[k] <= lvl <= H[k]:
                fill = k; break
        if fill is None:
            continue
        RB, xbB = simulate(lvl, d, a, H, L, C, fill + 1, pW, ret_bar=True)
        R, xb = RB, xbB
        if pE is not None:
            RA, xbA = simulate(lvl, d, a, H, L, C, fill + 1, pE, ret_bar=True)
            if xbA <= fill + nbars:          # in Phase 1 ausgestiegen → A gilt
                R, xb = RA, xbA
                n_frueh += 1
        Rs.append(R - SP[i] / a - (slip / a if slip else 0.0))
        frei_ab = xb + 1
    return Rs, n_frueh


def ci(Rs, n=2500, seed=42):
    if len(Rs) < 20:
        return None, None
    r = random.Random(seed); m = len(Rs)
    b = sorted(sum(Rs[r.randrange(m)] for _ in range(m)) / m for _ in range(n))
    return b[int(.025 * n)], b[int(.975 * n)]


def kenn(Rs, frueh=0):
    if not Rs:
        return None
    g = sum(x for x in Rs if x > 0); v = -sum(x for x in Rs if x < 0)
    u, o = ci(Rs)
    return {"n": len(Rs), "oer": sum(Rs) / len(Rs), "wr": 100 * sum(1 for x in Rs if x > 0) / len(Rs),
            "pf": (g / v if v else float("inf")), "worst": min(Rs), "u": u, "o": o,
            "fr": frueh}


def zeig(name, k, basis=None):
    if not k:
        print(f"  {name:<32} —"); return
    kib = "      —      " if k["u"] is None else f"[{k['u']:+.3f} … {k['o']:+.3f}]"
    dl = "" if basis is None else f" Δ{k['oer']-basis['oer']:+.3f}"
    print(f"  {name:<32} n={k['n']:>4} WR {k['wr']:>4.1f}% ØR {k['oer']:>+.3f} "
          f"PF {k['pf']:>5.2f} worst {k['worst']:>+6.2f} früh {k['fr']:>4} {kib}{dl}")


def main():
    n = 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)
    point = mt5.symbol_info(sym).point
    mt5.shutdown()
    if bars is None:
        print("  keine Bars"); return 1
    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)
    mid = len(C) // 2
    e1 = entries(H, L, C, A, SP, point, 60, mid)
    e2 = entries(H, L, C, A, SP, point, mid, len(C))
    print(f"\n  {sym} · {len(C)} M5-Bars · Squeeze-Entries H1 {len(e1)} · H2 {len(e2)}")
    print(f"  Einstieg ruhend am Level · kanonischer Exit · Echtkosten · EIN Slot")
    print(f"  Basis = durchgehend {_WEIT_LIVE}xATR_M5 (= der Live-Stop 2,0xATR_M15)")
    print(f"  ⚠ Bar-Sim UEBERSCHAETZT enge Stops (Kipp ~0,3xATR) — ein ✅ waere "
          f"nach oben verzerrt\n")

    b1 = kenn(*lauf(e1, H, L, C, SP, None, 0, _WEIT_LIVE))
    b2 = kenn(*lauf(e2, H, L, C, SP, None, 0, _WEIT_LIVE))
    print("  ── BASIS (heute) ──────────────────────────────────────────────")
    zeig("durchgehend 3,4xATR  H1", b1); zeig("durchgehend 3,4xATR  H2", b2)

    print("\n  ── GESTAFFELT: eng fuer die ersten N Bars, dann 3,4xATR ───────")
    besser = []
    for nb in (3, 6, 12):
        print(f"    ── N = {nb} Bars ({nb*5} min) ──")
        for eng in (0.5, 0.8, 1.0, 1.5):
            k1 = kenn(*lauf(e1, H, L, C, SP, eng, nb, _WEIT_LIVE))
            k2 = kenn(*lauf(e2, H, L, C, SP, eng, nb, _WEIT_LIVE))
            zeig(f"eng {eng:.1f}xATR  H1", k1, b1)
            zeig(f"eng {eng:.1f}xATR  H2", k2, b2)
            if k1 and k2 and k1["oer"] > b1["oer"] and k2["oer"] > b2["oer"]:
                besser.append((nb, eng, k1["oer"] - b1["oer"], k2["oer"] - b2["oer"]))

    print("\n  ── KONTROLLE: durchgehend eng (= der 2x gemessene Fall) ───────")
    for eng in (0.8, 1.5, 2.0):
        k1 = kenn(*lauf(e1, H, L, C, SP, None, 0, eng))
        k2 = kenn(*lauf(e2, H, L, C, SP, None, 0, eng))
        zeig(f"durchgehend {eng:.1f}xATR  H1", k1, b1)
        zeig(f"durchgehend {eng:.1f}xATR  H2", k2, b2)

    print("\n  ── URTEIL ─────────────────────────────────────────────────────")
    if besser:
        print(f"    {len(besser)} Kombination(en) schlagen die Basis in BEIDEN Haelften:")
        for nb, eng, d1, d2 in besser:
            print(f"      N={nb}, eng={eng}xATR   Δ H1 {d1:+.3f} · H2 {d2:+.3f}")
        print("    ⚠ VOR einer Umsetzung: das Auflösungs-Artefakt verzerrt genau "
              "diese Richtung\n      nach oben. Belastbar waere erst eine Tick-Simulation.")
    else:
        print("    KEINE Kombination schlaegt die Basis in beiden Haelften.")
        print("    ⚠ Dieses Ergebnis ist belastbar: die Verzerrung der Bar-Sim geht "
              "ZUGUNSTEN\n      der engen Stops — sie faellt trotzdem durch.")
    return 0


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