#!/usr/bin/env python3
"""Reversal-Trigger: IST-Verhalten gegen vorzeichenkorrigierte Variante (2026-08-04).

Anlass: Beim Nachgehen der User-Frage „warum SHORT im starken M5-Aufwärtstrend?"
kam heraus, dass die Reversal-Bedingung in `wave_rec._build` GEGEN ihre eigene
Dokumentation läuft.

  `calc_trend_angle` liefert laut Docstring   0° = starker AUFWÄRTStrend
                                             90° = seitwärts
                                            180° = starker ABWÄRTStrend
  → `ad = angle − 90`:  ad < 0 = STEIGEND,  ad > 0 = FALLEND.

  Der Code macht:
      stretch >= +3.0 und ad <= -2   → SHORT   (Kommentar: „Winkel ab")
      stretch <= -3.0 und ad >= +2   → LONG    (Kommentar: „Winkel auf")
  Mit obiger Konvention heißt das: SHORT bei überkauft UND WEITER STEIGENDEM
  Winkel; LONG bei überverkauft UND WEITER FALLENDEM. CLAUDE.md beschreibt aber
  „Einstieg IN Winkel-Richtung" — also genau umgekehrt.

⚠ Warum das nicht einfach umgedreht wurde: sämtliche Messungen (`backtest_bounce.py`,
der Legacy-Recheck, `backtest_bounce_reliability.py`) rufen dasselbe `_build` — sie
beschreiben also das IST-Verhalten. Ein Vorzeichenwechsel machte aus einem gemessenen
Setup ein ungemessenes. Deshalb hier beide Varianten gegeneinander, plus eine
Kontrolle ohne Winkelbedingung (trägt der Winkel überhaupt etwas bei?).

Aufbau: M5 (Basis-TF), kanonischer Live-Exit (`core/exit_model.LIVE`), Echtkosten
(Bar-Spread/ATR), sequentielle 1-Positions-Sim, 2 Halbjahre.
Nur der TRIGGER wird verglichen — ohne Konfidenz-Gate/Raum-Gate, sonst mischen sich
Effekte. Maßstab: beidhälftig ØR>0 und PF>1.

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

import MetaTrader5 as mt5

from core.analysis import calc_trend_angle
from core.exit_model import LIVE, simulate
from core.wave_rec import _ANGLE_DEAD, _ANGLE_LR, _EMA_SLOW, _REVERSAL_STRETCH

_ATRMIN = 0.06
_COOL = 3


def _ema(vals, p):
    k = 2.0 / (p + 1)
    out = [vals[0]]
    for v in vals[1:]:
        out.append(out[-1] + k * (v - out[-1]))
    return out


def _atr_series(H, L, C, p=14):
    t = [0.0]
    for i in range(1, len(C)):
        t.append(max(H[i] - L[i], abs(H[i] - C[i - 1]), abs(L[i] - C[i - 1])))
    return [(sum(t[max(1, i - p + 1):i + 1]) / max(1, len(t[max(1, i - p + 1):i + 1])))
            if i else None for i in range(len(C))]


def st(Rs):
    if not Rs:
        return None
    n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs)
    up = sum(x for x in Rs if x > 0); dn = -sum(x for x in Rs if x < 0)
    return dict(n=n, wr=100 * w / n, oR=s / n, pf=(up / dn if dn > 0 else 9.99), sum=s)


def line(lbl, s):
    if not s:
        return f"    {lbl:<22} — (keine Trades)"
    return (f"    {lbl:<22} n={s['n']:>5} WR={s['wr']:>3.0f}% ØR={s['oR']:+.3f} "
            f"PF={s['pf']:>4.2f} ΣR={s['sum']:>+7.0f}")


def run(H, L, C, A, SP, E50, AD, lo, hi, variant):
    def cost(i, atr):
        return (SP[i] if SP[i] > 0 else 0.0225) / atr

    Rs = []
    i = max(lo, _EMA_SLOW + _ANGLE_LR + 5)
    while i < hi:
        atr = A[i]
        if not atr or atr < _ATRMIN:
            i += 1; continue
        stretch = (C[i] - E50[i]) / atr
        ad = AD[i]
        d = 0
        if variant == "IST":            # wie der Live-Code heute
            if stretch <= -_REVERSAL_STRETCH and ad >= _ANGLE_DEAD:
                d = 1
            elif stretch >= _REVERSAL_STRETCH and ad <= -_ANGLE_DEAD:
                d = -1
        elif variant == "KORR":         # vorzeichenkorrigiert = dokumentierte Absicht
            if stretch <= -_REVERSAL_STRETCH and ad <= -_ANGLE_DEAD:
                d = 1                   # überverkauft + Winkel STEIGT = gedreht
            elif stretch >= _REVERSAL_STRETCH and ad >= _ANGLE_DEAD:
                d = -1                  # überkauft + Winkel FÄLLT = gedreht
        else:                           # KONTROLLE: nur Überdehnung, kein Winkel
            if stretch <= -_REVERSAL_STRETCH:
                d = 1
            elif stretch >= _REVERSAL_STRETCH:
                d = -1
        if d == 0:
            i += 1; continue
        a = max(atr, _ATRMIN)
        r, xj = simulate(C[i], d, a, H, L, C, i + 1, LIVE,
                         timestop=True, use_tp=True, ret_bar=True)
        Rs.append(r - cost(i, a))
        i = xj + _COOL
    return Rs


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)
    E50 = _ema(C, _EMA_SLOW)
    # Winkel EINMAL je Bar (variantenunabhängig), exakt wie im Live-Code aufgerufen
    AD = [0.0] * len(C)
    for i in range(_ANGLE_LR + 2, len(C)):
        AD[i] = calc_trend_angle(C[i - (_ANGLE_LR + 1):i + 1], _ANGLE_LR) - 90.0
    N = len(C); mid = N // 2

    print("=" * 88)
    print(f"  Reversal-Trigger: IST vs. vorzeichenkorrigiert — {sym} M5 ({N} Bars)")
    print(f"  Kanonischer Exit (SL {LIVE.sl_atr}× · Trail {LIVE.mult}× · BE {LIVE.be}×"
          f" · Time-Stop), Echtkosten, 2 Halbjahre")
    print(f"  Trigger: |stretch| ≥ {_REVERSAL_STRETCH}×ATR, Winkel-Totband ±{_ANGLE_DEAD}°")
    print("=" * 88)
    beschreibung = {
        "IST":  "IST (Live heute): überkauft + Winkel STEIGT → SHORT",
        "KORR": "KORRIGIERT (Doku):  überkauft + Winkel FÄLLT → SHORT",
        "KTRL": "KONTROLLE: nur Überdehnung, Winkel ignoriert",
    }
    for v in ("IST", "KORR", "KTRL"):
        s1 = st(run(H, L, C, A, SP, E50, AD, 0, mid, v))
        s2 = st(run(H, L, C, A, SP, E50, AD, mid, N - LIVE.max_hold - 1, v))
        print(f"\n  {beschreibung[v]}")
        print(line("H1 (alt)", s1))
        print(line("H2 (neu)", s2))
        ok = (s1 and s2 and s1["oR"] > 0 and s2["oR"] > 0
              and s1["pf"] > 1 and s2["pf"] > 1)
        print(f"    → {'ROBUST (beide Hälften positiv)' if ok else 'fällt durch'}")
    print()


if __name__ == "__main__":
    main()
