"""Traegt der ENTRY-RAUM-Filter noch, wenn man am Level einsteigt? (2026-08-19)

FRAGE (User: "hast Du Ideen wie ich die Empfehlung noch verbessern koennte?").
`entry_room` ist mit 60,6 % der mit ABSTAND groesste Blocker der Empfehlung
(gerichtet sind nur 2,7 %). Kalibriert wurde er am 2026-07-16 mit
`backtest_entryroom.py` -- dort steigt die Sim am BAR-CLOSE ein, also per MARKT.
Die Begruendung lautet folgerichtig: Gegenlevel nah => Ertrag gedeckelt =>
nicht handeln (PF 0,42/0,66, sauber gemessen).

Am 05./06.08. hat sich die Ausfuehrung geaendert und das Gate wurde NICHT
nachgezogen. Seither ist belegt, dass eine RUHENDE ORDER AM LEVEL etwas
grundlegend anderes ist als ein Markt-Einstieg mit dem Level als Hindernis
voraus (Squeeze: -0,158 => +0,124/+0,292).

  Markt-Einstieg unter einem Widerstand  = in die Wand hineinkaufen
  BUY_STOP am Widerstand                 = Ausbruch aus der Wand heraus

Das sind GEGENTEILIGE Trades, und das Gate kann sie nicht unterscheiden --
es blockt beide.

GEMESSEN wird deshalb die von `entry_room` GEBLOCKTE Gruppe getrennt, genau
wie `backtest_htf_vola.py` es fuer den M30-Filter gemacht hat:
  A) Markt am Bar-Close   (was die Sim von 2026-07-16 unterstellt)
  B) ruhende Stop-Order AM blockierenden Level, Fill bei BERUEHRUNG

Das Level ist ein M5-Pivot aus der Vergangenheit, es STEHT also -- das ist der
Unterschied zu `_pend` (wandert, `backtest_signal_touchfill.py` faellt durch)
und dieselbe Eigenschaft wie die Squeeze-Box (steht, haelt).

REGEL VORAB FIXIERT -- gebaut wird nur, wenn ALLES zutrifft:
  (1) geblockte Gruppe mit Level-Einstieg: OeR > 0 UND PF > 1 in BEIDEN Haelften
  (2) 95-%-KI schliesst in BEIDEN Haelften die Null aus
  (3) die Nachbarn (Puffer, Gueltigkeitsdauer) kippen nicht
  (4) n >= 100 je Haelfte

GRENZEN, vorab benannt:
  - Kein sequentieller EIN-Slot-Lauf. Verglichen werden zwei POPULATIONEN
    (geblockt/durchgelassen), nicht zwei Kontostaende; dafuer ist der OeR je
    Trade mit KI die richtige Statistik. Ein Slot-Lauf braeuchte eine
    Vorrang-Entscheidung und misst dann Selektion statt Gate.
  - Exit-Slippage ist NICHT modelliert (senkt alle Varianten, die haeufiger
    handelnden staerker).
  - Der ATR fuer die R-Normierung ist in A und B derselbe (Signal-Bar), sonst
    waeren die R-Einheiten nicht vergleichbar.

Aufruf:  python backtest_entryroom_level.py [bars]
"""
import sys
from bisect import bisect_left, bisect_right

import MetaTrader5 as mt5

from backtest_breakout_gated import (_ATRMIN, _ATR_FLOOR_PB, _COOL, _PIV_K,
                                     _PIV_LOOK, _TIMEOUT_BARS, _TU,
                                     _atr_series, _htf_sign)
from backtest_cost_gate import kennzahlen
from core.analysis import calc_trend_angle
from core.exit_model import LIVE, simulate
from core.wave_rec import (WaveRecommender, _ANGLE_LR, _EMA_FAST, _EMA_SLOW,
                           _ema_series)

_K = 0.3            # breakout_k, Live-Wert
_GATE = 0.6         # entry_room_atr, Live-Wert


def sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, lo, hi):
    """Alle Signale NACH _build + _confirm_breakout, VOR dem Raum-Gate.

    Geliefert wird je Signal die Rohlage; das Gate wird hier bewusst NICHT
    angewandt -- seine Entscheidung ist der Gegenstand der Messung.
    """
    out = []
    pend = None
    i = max(lo, _PIV_LOOK + _EMA_SLOW + 5)
    while i < hi:
        atr = A[i]
        if not atr or atr < _ATRMIN:
            i += 1
            continue
        w._pb_feats = {"atr": max(atr, _ATR_FLOOR_PB)}
        _a1 = bisect_left(PH, i - _PIV_LOOK); _b1 = bisect_right(PH, i - _PIV_K)
        _a2 = bisect_left(PL, i - _PIV_LOOK); _b2 = bisect_right(PL, i - _PIV_K)
        w._pb_levels = {"ph": [C[j] for j in PH[_a1:_b1]],
                        "pl": [C[j] for j in PL[_a2:_b2]]}
        rec, _ = w._build(EF[i], ES[i], C[i], atr, "M5", 5,
                          htf_trend=HTF[i], h1_trend=H1[i], angle=AD[i])
        sig = rec.get("signal")

        if _K > 0 and sig in ("LONG", "SHORT"):
            d = 1 if sig == "LONG" else -1
            if pend is None or pend["dir"] != d:
                pend = {"dir": d, "level": C[i] + d * _K * atr,
                        "invalid": C[i] - d * _K * atr, "confirmed": False, "bar": i}
            if not pend["confirmed"]:
                broke = (C[i] >= pend["level"]) if d > 0 else (C[i] <= pend["level"])
                gegen = (C[i] <= pend["invalid"]) if d > 0 else (C[i] >= pend["invalid"])
                if broke:
                    pend["confirmed"] = True
                else:
                    if gegen or (i - pend["bar"]) > _TIMEOUT_BARS:
                        pend = {"dir": d, "level": C[i] + d * _K * atr,
                                "invalid": C[i] - d * _K * atr,
                                "confirmed": False, "bar": i}
                    i += 1
                    continue
        elif sig == "WARTEN":
            pend = None

        if sig not in ("LONG", "SHORT"):
            i += 1
            continue

        d = 1 if sig == "LONG" else -1
        # ECHTE Methode, nicht nachgebaut -- dieselbe Zahl, die das Gate benutzt.
        ri = w.room_info(C[i])[("long" if d > 0 else "short")] or {}
        out.append({"i": i, "d": d, "atr": max(atr, _ATRMIN), "px": C[i],
                    "conf": rec.get("conf_pct"),   # additiv, s. backtest_conf_gated.py
                    "lvl": ri.get("level"), "dist": ri.get("dist_atr"),
                    "frei": bool(ri.get("frei"))})
        pend = None
        i += _COOL
    return out


def handeln(sig, H, L, C, SP, ende, modus, puffer, gueltig):
    """R eines Signals. modus 'markt' = Bar-Close, 'level' = ruhende Stop-Order."""
    i, d, atr = sig["i"], sig["d"], sig["atr"]

    def cost(j):
        return (SP[j] if SP[j] > 0 else 0.0225) / atr

    if modus == "markt":
        entry, j = C[i], i
    else:
        lvl = sig["lvl"]
        if lvl is None:
            return None                      # freie Bahn: kein Level zum Liegen
        entry = lvl + d * puffer * atr
        # Stop-Order MUSS auf der richtigen Marktseite liegen (Lehre 06.08.:
        # ohne diese Pruefung fuellt die Sim zu laengst passierten Preisen).
        if (d > 0 and entry <= C[i]) or (d < 0 and entry >= C[i]):
            return None
        j = None
        for x in range(i + 1, min(i + 1 + gueltig, ende)):
            if (H[x] >= entry) if d > 0 else (L[x] <= entry):
                j = x
                break
        if j is None:
            return None                      # nicht gefuellt = kein Trade
    if j + 1 >= ende:
        return None
    r = simulate(entry, d, atr, H, L, C, j + 1, LIVE, timestop=True, use_tp=True)
    return r - cost(j)


def gruppen(sigs, H, L, C, SP, ende, puffer, gueltig):
    g = {"blk_markt": [], "blk_level": [], "frei_markt": [],
         "durch_markt": [], "durch_level": []}
    for s in sigs:
        rm = handeln(s, H, L, C, SP, ende, "markt", puffer, gueltig)
        rl = handeln(s, H, L, C, SP, ende, "level", puffer, gueltig)
        if s["frei"] or s["lvl"] is None:
            if rm is not None:
                g["frei_markt"].append(rm)
            continue
        blk = (s["dist"] is not None and s["dist"] < _GATE)
        if rm is not None:
            g["blk_markt" if blk else "durch_markt"].append(rm)
        if rl is not None:
            g["blk_level" if blk else "durch_level"].append(rl)
    return g


def zeig(lbl, k1, k2):
    def f(k):
        if not k:
            return "  --"
        ci = k.get("ci")
        m = ""
        if ci:
            m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1])
                 + ("" if ci[0] * ci[1] > 0 else "  ~KI enthaelt 0"))
        return ("n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f}{}"
                .format(k["n"], k["wr"], k["oer"], k["pf"], m))
    print("    {:<28} H1 {}".format(lbl, f(k1)))
    print("    {:<28} H2 {}".format("", f(k2)))


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()
    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)
    EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW)
    N = len(C); mid = N // 2; ende = N - LIVE.max_hold - 1

    print("Vorberechnung (Winkel, HTF-Trends, Pivots) ...", flush=True)
    AD = [90.0] * N
    for i in range(_ANGLE_LR + 2, N):
        AD[i] = calc_trend_angle(C[i - (_ANGLE_LR + 1):i + 1], _ANGLE_LR)
    HTF = [0] * N; H1 = [0] * N
    for i in range(N):
        if A[i]:
            HTF[i] = _htf_sign(C, i, 6, A[i])
            H1[i] = _htf_sign(C, i, 12, A[i])
    PH = [j for j in range(_PIV_K, N - _PIV_K)
          if H[j] == max(H[j - _PIV_K:j + _PIV_K + 1])]
    PL = [j for j in range(_PIV_K, N - _PIV_K)
          if L[j] == min(L[j - _PIV_K:j + _PIV_K + 1])]

    w = WaveRecommender(_TU(), mt5.TIMEFRAME_M5)
    w.set_reversal_enabled(False)
    w.set_entry_room(_GATE)                 # Live-Konfiguration, nicht Default
    w.set_dead_hours([])
    w.set_breakout_k(_K)

    print("=" * 96)
    print("  ENTRY-RAUM-GATE gegen AUSFUEHRUNG -- {} M5 ({} Bars, 2 Halbjahre)"
          .format(sym, N))
    print("  Gate {}x ATR_M5 - breakout_k {} - Reversal AUS - Exit kanonisch"
          .format(_GATE, _K))
    print("=" * 96)

    print("Signale sammeln (voller Gate-Stack, VOR dem Raum-Gate) ...", flush=True)
    s1 = sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, 0, mid)
    s2 = sammle(w, H, L, C, A, EF, ES, AD, HTF, H1, PH, PL, mid, ende)
    print("  H1 {} - H2 {} Signale vor dem Raum-Gate".format(len(s1), len(s2)))

    PU, GU = 0.0, 12
    g1 = gruppen(s1, H, L, C, SP, ende, PU, GU)
    g2 = gruppen(s2, H, L, C, SP, ende, PU, GU)

    print("")
    print("  HAUPTLAUF  (Puffer {}x ATR - Order gueltig {} Bars = 1 h)".format(PU, GU))
    print("  " + "-" * 92)
    for key, lbl in (("blk_markt",   "GEBLOCKT   - Markt"),
                     ("blk_level",   "GEBLOCKT   - Level (ruhend)"),
                     ("durch_markt", "durchgel.  - Markt (=heute)"),
                     ("durch_level", "durchgel.  - Level (ruhend)"),
                     ("frei_markt",  "freie Bahn - Markt")):
        zeig(lbl, kennzahlen(g1[key]), kennzahlen(g2[key]))
        print("")

    print("  NACHBARN (Regel 3) -- nur die geblockte Gruppe am Level")
    print("  " + "-" * 92)
    for pu, gu in ((0.05, 12), (0.10, 12), (0.0, 6), (0.0, 24)):
        a = gruppen(s1, H, L, C, SP, ende, pu, gu)["blk_level"]
        b = gruppen(s2, H, L, C, SP, ende, pu, gu)["blk_level"]
        zeig("Puffer {:.2f} - gueltig {}".format(pu, gu),
             kennzahlen(a), kennzahlen(b))
        print("")

    k1, k2 = kennzahlen(g1["blk_level"]), kennzahlen(g2["blk_level"])
    ok = bool(k1 and k2 and k1["n"] >= 100 and k2["n"] >= 100
              and k1["oer"] > 0 and k2["oer"] > 0
              and k1["pf"] > 1 and k2["pf"] > 1
              and k1["ci"] and k2["ci"]
              and k1["ci"][0] > 0 and k2["ci"][0] > 0)
    print("=" * 96)
    print("  URTEIL nach der vorab fixierten Regel: {}"
          .format("ERFUELLT -- weiterverfolgen" if ok
                  else "NICHT erfuellt -- nichts bauen"))
    print("=" * 96)


if __name__ == "__main__":
    main()
