#!/usr/bin/env python3
"""Flip-Close auf dem HÖHEREN TREND (M30 / H1) — trägt er als EXIT?

ANLASS (2026-08-07, User: „oder doch lieber ein Flip-Close bei entgegen-
gerichtetem Trend?"). Zu unterscheiden sind ZWEI Dinge:
  · Flip auf das EMPFEHLUNGS-Signal — gemessen (`backtest_flipclose2.py`), in
    JEDER Variante und BEIDEN Hälften schlechter, am 05.08. abgeschaltet.
    Mechanik: die nachlaufende EMA dreht oft mitten im Pullback → Gewinner-Kappen.
  · Flip auf den HTF-TREND (M30/H1) — **war nie gemessen**. Der HTF ist träger,
    dreht viel seltener, und als FILTER ist er im Projekt belegt (Edge ×2). Ob er
    auch als EXIT trägt, ist damit keineswegs entschieden.

⚠⚠ ENTSCHEIDUNGSREGEL, VORAB FESTGELEGT:
Eine Variante gilt nur dann als besser, wenn sie
  (1) den ΣR in BEIDEN Halbjahren erhöht, UND
  (2) den Worst-Case nicht verschlechtert, UND
  (3) bei den Nachbar-Einstellungen (minR) nicht das Vorzeichen kippt.
Sonst: kein Einbau. Punkt (3), weil in diesem Projekt schon mehrfach eine
einzelne Zelle gut aussah, deren Nachbarn kippten (ORB, SMC-Sweeps, und heute
der Notfall-Stop bei 8 % gegen −292/−106 bei 5 %/15 %).

⚠ FESTE, GETEILTE ENTRY-LISTE. `backtest_flipclose2.py` lässt die sequentielle
Sim nach jedem Exit bei `xb+1` weiterlaufen — dadurch bekommt JEDE Variante eine
andere Trade-Folge (steigt eine früher aus, verschiebt sich alles Nachfolgende)
und die Zahlen sind nicht vergleichbar. Genau dieser Fehler wurde am 31.07. bei
`backtest_exit_combo.py` schon einmal korrigiert; hier wird er von vornherein
vermieden: alle Varianten laufen auf DENSELBEN Einstiegen.

Exit-Kern = `core/exit_model.simulate()` mit LIVE-Parametern (geteilte Quelle,
kein Nachbau) + `stop_when`-Hook für den Flip. Kosten = echter Bar-Spread/ATR.

Aufruf:  python backtest_flipclose_htf.py [n_bars]     (Default 80000)
"""
from __future__ import annotations

import sys

import MetaTrader5 as mt5

from backtest_auto_signal import _atr_series, _ema_series, _htf_signs
# ⚠ `_ATRMIN` aus `backtest_flipclose2` uebernehmen, NICHT neu setzen — der
# Vergleich mit dem dortigen Befund gilt nur bei identischem ATR-Floor.
from backtest_flipclose2 import _ATRMIN, signal_series
from core.exit_model import LIVE, simulate
from core.wave_rec import _EMA_FAST, _EMA_SLOW, _N_BARS

_M30_STEP = 6      # 6 M5-Bars = M30
_H1_STEP = 12      # 12 M5-Bars = H1


def entries(sig, lo, hi, H, L, C, AT, SP):
    """EINE feste Einstiegsliste, sequentiell mit dem BASIS-Exit ermittelt.

    ⚠ Bewusst mit der Basis (ohne Flip) bestimmt: nur so handeln alle Varianten
    dieselben Trades und der Vergleich misst den EXIT, nicht eine verschobene
    Trade-Folge."""
    out = []
    i = lo
    while i < hi:
        d = sig[i]
        if d == 0:
            i += 1
            continue
        atr = max(AT[i] or 0, _ATRMIN)
        kosten = (SP[i] if SP[i] > 0 else 0.0225) / atr
        _, xb = simulate(C[i], d, atr, H, L, C, i + 1, LIVE, ret_bar=True)
        out.append((i, d, atr, kosten))
        i = xb + 1
    return out


def lauf(ein, H, L, C, htf, h1, *, flip=None, min_r=0.0, auch_im_minus=False):
    """`flip`: None = Basis · "m30" · "m30+h1" (beide müssen dagegen stehen)."""
    Rs = []
    for i, d, atr, kosten in ein:
        haken = None
        if flip:
            def haken(j, prof_atr, _d=d):
                gegen = htf[j] == -_d
                if flip == "m30+h1":
                    gegen = gegen and h1[j] == -_d
                if not gegen:
                    return False
                return auch_im_minus or prof_atr > min_r
        R = simulate(C[i], d, atr, H, L, C, i + 1, LIVE, stop_when=haken)
        Rs.append(R - kosten)
    return Rs


def kz(Rs):
    if not Rs:
        return "—"
    n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs)
    g = sum(x for x in Rs if x > 0); v = -sum(x for x in Rs if x < 0)
    return (f"n={n:>4} WR={100*w/n:>3.0f}% ØR={s/n:+.3f} "
            f"PF={(g/v if v > 0 else 9.99):>4.2f} ΣR={s:>+6.0f} Worst={min(Rs):+.2f}")


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)
    si = mt5.symbol_info(sym); point = si.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]
    EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW)
    AT = _atr_series(H, L, C)
    sig = signal_series(H, L, C, EF, ES, AT)
    htf = _htf_signs(H, L, C, _M30_STEP)
    h1 = _htf_signs(H, L, C, _H1_STEP)

    mid = len(C) // 2
    print("=" * 96)
    print(f"  FLIP-CLOSE auf dem HTF-TREND — {sym} M5, {len(C)} Bars")
    print("  Regel vorab: ΣR in BEIDEN Hälften besser · Worst nicht schlechter · "
          "Nachbarn kippen nicht")
    print("=" * 96)

    for name, (lo, hi) in (("H1", (_N_BARS, mid)), ("H2", (mid, len(C) - LIVE.max_hold - 2))):
        ein = entries(sig, lo, hi, H, L, C, AT, SP)
        print(f"\n  ── {name} ── {len(ein)} feste Einstiege, alle Varianten identisch")
        basis = lauf(ein, H, L, C, htf, h1)
        b_sum = sum(basis)
        print(f"    {'BASIS (kein Flip-Close)':<34}{kz(basis)}")
        for etikett, kw in (
                ("M30 dagegen, ab +0,0R",      dict(flip="m30", min_r=0.0)),
                ("M30 dagegen, ab +0,5R",      dict(flip="m30", min_r=0.5)),
                ("M30 dagegen, ab +1,0R",      dict(flip="m30", min_r=1.0)),
                ("M30 dagegen, AUCH im Minus", dict(flip="m30", auch_im_minus=True)),
                ("M30+H1 dagegen, ab +0,5R",   dict(flip="m30+h1", min_r=0.5)),
                ("M30+H1, AUCH im Minus",      dict(flip="m30+h1", auch_im_minus=True))):
            Rs = lauf(ein, H, L, C, htf, h1, **kw)
            d = sum(Rs) - b_sum
            print(f"    {etikett:<34}{kz(Rs)}   Δ{d:>+6.0f}")


if __name__ == "__main__":
    main()
