"""Die DREI ungemessenen Konzepte aus Risueno Gomez, "Praktisches Trading 2.0"
(User-Auftrag 2026-08-19: "nutze das PDF fuer neue Ideen").

TRIAGE ZUERST. Von den ~20 Konzepten des Buches sind 17 in diesem Projekt
bereits gemessen und ueberwiegend verworfen (Doppeltop/-boden, Kopf-Schulter,
Dreieck, Rechteck, Kanal, Marktstruktur HH/HL, Kerzenmuster inkl. Hammer/
Engulfing/Stern, Fibonacci, Order Blocks, FVG, Wyckoff, Pullback, RSI-Richtung,
Liquidity Sweeps). Ungemessen sind genau drei:

  (1) BOLLINGER-SQUEEZE  -- das Wort "Bollinger" kommt in CLAUDE.md NICHT vor.
      ⚠⚠ Das ist die interessanteste Luecke, weil es KEIN neues Signal ist:
      der live laufende Squeeze benutzt eine ATR-Box (Spanne der letzten 10
      M5-Bars <= 2,5x ATR). Das Buch definiert Kompression ueber die
      BANDBREITE (SMA20 +- 2 Sigma). Gleiche Idee, andere Messgroesse --
      also eine Variante des EINZIGEN validierten Setups statt eines 26.
      Signal-Eingriffs. Genau dort sagt die Projekt-Logik, man solle suchen.

  (2) RSI-DIVERGENZ -- gemessen wurde bisher die RSI-RICHTUNG
      (`backtest_confluence.py`: bestaetigender RSI kontraproduktiv, RSI GEGEN
      die Richtung = der Pullback-Effekt). Die DIVERGENZ (Preis neues Extrem,
      RSI nicht) ist eine dritte, andere Aussage und nie geprueft. Das Buch
      nennt sie "eines der wenigen Tools, das den Preis vorwegnehmen kann".

  (3) DREI WEISSE SOLDATEN / SCHWARZE KRAEHEN -- als eigene Klasse ungemessen.
      ⚠ Hohe Beweislast: die Kerzen-Klasse ist 4x gemessen und in der
      Lehrbuch-Lesart INVERTIERT. Wird deshalb in BEIDEN Lesarten getestet.

⚠⚠ EINORDNUNG DER BUCH-ZAHLEN (S. 203). Die Tabelle behauptet Kopf-Schulter
83 %, Engulfing 70-75 %, Doppelboden 70-75 %. In diesem Projekt gemessen:
Muster-Ziel-Trefferquote 13-38 % (`backtest_patterns.py`), Doppeltop in allen
Toleranzen durchgefallen, Kerzen-Lesart beidhaelftig invertiert. Die Tabelle
nennt keine Quelle, kein n und keinen Zeitraum. Sie wird hier NICHT als
Erwartung verwendet -- gemessen wird gegen null.

AUFBAU: 80k M5-Bars, 2 Halbjahre, kanonischer Exit (`exit_model.LIVE`),
Echtkosten aus dem Bar-Spread, Fill bei BERUEHRUNG (die am 06.08. als
entscheidend gemessene Achse), 95-%-Bootstrap je Zelle.

REGEL VORAB FIXIERT -- ein Konzept gilt nur dann als tragfaehig, wenn:
  (a) OeR > 0 UND PF > 1 in BEIDEN Haelften
  (b) das 95-%-KI schliesst in BEIDEN Haelften die Null aus
  (c) die Nachbar-Parameter kippen nicht
  (d) n >= 100 je Haelfte
Fuer (1) zusaetzlich: die Bollinger-Variante muss die ATR-Box-KONTROLLE auf
DERSELBEN Population schlagen -- sonst ist sie nur eine andere Schreibweise.

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

import MetaTrader5 as mt5

from backtest_breakout_gated import _atr_series
from backtest_cost_gate import kennzahlen
from core.exit_model import LIVE, simulate

_ATRMIN = 0.06
_COOL = 3


def sma(C, n):
    out = [None] * len(C)
    s = 0.0
    for i, c in enumerate(C):
        s += c
        if i >= n:
            s -= C[i - n]
        if i >= n - 1:
            out[i] = s / n
    return out


def bb_width(C, n=20, k=2.0):
    """Bandbreite (oben-unten)/Mitte -- die Buch-Definition von Kompression."""
    m = sma(C, n)
    out = [None] * len(C)
    for i in range(n - 1, len(C)):
        mu = m[i]
        var = sum((C[j] - mu) ** 2 for j in range(i - n + 1, i + 1)) / n
        sd = var ** 0.5
        out[i] = (2 * k * sd / mu) if mu else None
    return out, m


def rsi_series(C, p=14):
    out = [None] * len(C)
    if len(C) < p + 1:
        return out
    g = l = 0.0
    for i in range(1, p + 1):
        d = C[i] - C[i - 1]
        g += max(d, 0.0); l += max(-d, 0.0)
    g /= p; l /= p
    out[p] = 100.0 - 100.0 / (1 + g / l) if l else 100.0
    for i in range(p + 1, len(C)):
        d = C[i] - C[i - 1]
        g = (g * (p - 1) + max(d, 0.0)) / p
        l = (l * (p - 1) + max(-d, 0.0)) / p
        out[i] = 100.0 - 100.0 / (1 + g / l) if l else 100.0
    return out


def pivots(V, k):
    """Indizes lokaler Hochs/Tiefs. Kausal nutzbar erst ab j+k."""
    ph, pl = [], []
    for j in range(k, len(V) - k):
        if V[j] is None:
            continue
        w = [V[x] for x in range(j - k, j + k + 1) if V[x] is not None]
        if len(w) < 2 * k + 1:
            continue
        if V[j] == max(w):
            ph.append(j)
        if V[j] == min(w):
            pl.append(j)
    return ph, pl


def trade(i, d, H, L, C, A, SP, ende, puffer):
    """Ruhende Stop-Order am Extrem der Signalbar, Fill bei BERUEHRUNG."""
    atr = A[i]
    if not atr or atr < _ATRMIN or i + 1 >= ende:
        return None
    entry = (H[i] if d > 0 else L[i]) + d * puffer * atr
    j = None
    for x in range(i + 1, min(i + 13, ende)):
        if (H[x] >= entry) if d > 0 else (L[x] <= entry):
            j = x
            break
    if j is None or j + 1 >= ende:
        return None
    r = simulate(entry, d, atr, H, L, C, j + 1, LIVE, timestop=True, use_tp=True)
    return r - ((SP[j] if SP[j] > 0 else 0.0225) / atr)


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 "  ~0"))
        return ("n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f}{}"
                .format(k["n"], k["wr"], k["oer"], k["pf"], m))
    print("    {:<30} H1 {}".format(lbl, f(k1)))
    print("    {:<30} 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]; O = [float(b["open"]) 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; ende = N - LIVE.max_hold - 1

    print("=" * 96)
    print("  BUCH-KONZEPTE (Risueno Gomez) -- {} M5 ({} Bars, 2 Halbjahre)".format(sym, N))
    print("  Exit kanonisch - Echtkosten - Fill bei BERUEHRUNG - 95-%-KI")
    print("=" * 96)

    # ---------------------------------------------------------------- (1)
    print("\n  (1) BOLLINGER-SQUEEZE gegen die ATR-BOX des laufenden Systems")
    print("  " + "-" * 92)
    BW, M20 = bb_width(C)
    for lo_, hi_, tag in ((0, mid, "H1"), (mid, ende, "H2")):
        pass
    for pct, gu in ((0.15, 50), (0.25, 50), (0.15, 100)):
        res = {"bb": [[], []], "atr": [[], []]}
        for h, (lo_, hi_) in enumerate(((0, mid), (mid, ende))):
            i = max(lo_, 120)
            while i < hi_:
                atr = A[i]
                if not atr or atr < _ATRMIN or BW[i] is None:
                    i += 1; continue
                fenster = [x for x in BW[i - gu:i] if x is not None]
                if len(fenster) < gu // 2:
                    i += 1; continue
                schwelle = sorted(fenster)[int(pct * len(fenster))]
                # Buch: Kontraktion = Bandbreite im unteren Perzentil
                bb_komp = BW[i] <= schwelle
                # Projekt: Box-Spanne der letzten 10 Bars <= 2,5x ATR
                box_hi = max(H[i - 10:i]); box_lo = min(L[i - 10:i])
                atr_komp = (box_hi - box_lo) <= 2.5 * atr
                d = 0
                if C[i] > box_hi:
                    d = 1
                elif C[i] < box_lo:
                    d = -1
                if d:
                    if bb_komp:
                        r = trade(i, d, H, L, C, A, SP, ende, 0.0)
                        if r is not None:
                            res["bb"][h].append(r)
                    if atr_komp:
                        r = trade(i, d, H, L, C, A, SP, ende, 0.0)
                        if r is not None:
                            res["atr"][h].append(r)
                    i += _COOL
                else:
                    i += 1
        zeig("BOLLINGER p{:.0f} f{}".format(pct * 100, gu),
             kennzahlen(res["bb"][0]), kennzahlen(res["bb"][1]))
        zeig("  KONTROLLE ATR-Box (live)",
             kennzahlen(res["atr"][0]), kennzahlen(res["atr"][1]))
        print("")

    # ---------------------------------------------------------------- (2)
    print("  (2) RSI-DIVERGENZ (Preis neues Extrem, RSI nicht)")
    print("  " + "-" * 92)
    R = rsi_series(C)
    for k in (3, 5):
        ph, pl = pivots(C, k)
        res = [[], []]
        gegen = [[], []]
        for name, piv, d in (("bull", pl, 1), ("bear", ph, -1)):
            for a, b in zip(piv, piv[1:]):
                if b - a > 60 or b - a < 5 or R[a] is None or R[b] is None:
                    continue
                # bullisch: tieferes Tief im Preis, hoeheres Tief im RSI
                if d > 0:
                    ok = C[b] < C[a] and R[b] > R[a]
                else:
                    ok = C[b] > C[a] and R[b] < R[a]
                if not ok:
                    continue
                i = b + k            # kausal: Pivot erst k Bars spaeter bekannt
                if i >= ende:
                    continue
                h = 0 if i < mid else 1
                r = trade(i, d, H, L, C, A, SP, ende, 0.0)
                if r is not None:
                    res[h].append(r)
                rg = trade(i, -d, H, L, C, A, SP, ende, 0.0)
                if rg is not None:
                    gegen[h].append(rg)
        zeig("Divergenz k={} (Buch-Lesart)".format(k),
             kennzahlen(res[0]), kennzahlen(res[1]))
        zeig("  KONTROLLE Gegenrichtung", kennzahlen(gegen[0]), kennzahlen(gegen[1]))
        print("")

    # ---------------------------------------------------------------- (3)
    print("  (3) DREI WEISSE SOLDATEN / DREI SCHWARZE KRAEHEN")
    print("  " + "-" * 92)
    res = [[], []]; geg = [[], []]
    i = 5
    while i < ende:
        atr = A[i]
        if not atr or atr < _ATRMIN:
            i += 1; continue
        d = 0
        for dd in (1, -1):
            ok = True
            for x in (i - 2, i - 1, i):
                koerper = (C[x] - O[x]) * dd
                spanne = H[x] - L[x]
                if koerper <= 0 or spanne <= 0 or koerper < 0.5 * spanne:
                    ok = False; break
                if x > i - 2:
                    # oeffnet im Koerper der Vorkerze, schliesst nahe Extrem
                    if not (min(O[x - 1], C[x - 1]) <= O[x] <= max(O[x - 1], C[x - 1])):
                        ok = False; break
                    if (C[x] - C[x - 1]) * dd <= 0:
                        ok = False; break
            if ok:
                d = dd; break
        if d:
            h = 0 if i < mid else 1
            r = trade(i, d, H, L, C, A, SP, ende, 0.0)
            if r is not None:
                res[h].append(r)
            rg = trade(i, -d, H, L, C, A, SP, ende, 0.0)
            if rg is not None:
                geg[h].append(rg)
            i += _COOL
        else:
            i += 1
    zeig("Fortsetzung (Buch-Lesart)", kennzahlen(res[0]), kennzahlen(res[1]))
    zeig("  KONTROLLE Gegenrichtung", kennzahlen(geg[0]), kennzahlen(geg[1]))
    print("=" * 96)


if __name__ == "__main__":
    main()
