#!/usr/bin/env python3
"""Exit-Simulation: bringt ein WEITERER Initial-SL netto mehr? (Befund B)

Replays die echten Signale (M5 + M30-Filter) und simuliert den tatsaechlichen
Exit-Ablauf bar-fuer-bar, originalgetreu zu core/trailing.py:
  - Initial-SL = X×ATR (die getestete Variable)
  - Teil-Exit 50 % bei +1,5×ATR (einmalig)
  - Phasen: Init (<0,3×ATR halte Initial-SL) · Trail (SL = HW∓mult×ATR,
    Breakeven-Floor ab +0,6×ATR, mult=1,5 fuer M5) · Lock (>=3,5×ATR enger)
  - Phasen-Ratsche (nie zurueck)
Pessimistische Intrabar-Annahme: Gegenlauf VOR Mitlauf (zaehlt SL zuerst) —
ueberschaetzt den Nutzen eines weiten SL also NICHT.

PnL in R (= ATR-Vielfache, vergleichbar ueber Trades). Der weite Init-SL wirkt
nur in der Init-Phase: sobald Trailing greift, kappt HW∓1,5×ATR ihn ohnehin.
Hinweis: der feste Init-TP (+3,5×ATR) wird weggelassen — der Runner-Exit laeuft
praktisch ueber den Trailing-SL; das ist die konservative, dominante Mechanik.
"""
from __future__ import annotations
import sys
import MetaTrader5 as mt5
from core.wave_rec import (WaveRecommender, _atr, _ema_last,
                           _EMA_FAST, _EMA_SLOW, _N_BARS, _HTF_DEADBAND)

_MULT      = 1.5    # _MULT_BY_TF[M5]
_BE_ATR    = 0.6    # _BREAKEVEN_ATR
_TRAIL_ON  = 0.3    # _TRAIL_START_ATR
_LOCK_ATR  = 3.5    # _PHASE4_ATR
_LOCK_MULT = max(1.2, _MULT * 0.6)
_PART_ATR  = 1.5    # _PARTIAL_TP_ATR
_PART_FRAC = 0.0    # Teil-Exit AUS (entspricht Live: _PARTIAL_TP_FRAC=0)
_ATR_MIN   = 0.12
_MAXH      = 240    # max. Haltedauer in M5-Bars (~20 h)


class _NeutralTU:
    def snapshot(self): return {"intervals": {}}


def _ema_series(vals, period):
    k = 2.0/(period+1); out=[]; e=vals[0]
    for i,v in enumerate(vals):
        e = v if i==0 else v*k + e*(1-k); out.append(e)
    return out


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


def _htf_sign_at(ts, T, Ef, Es, ATR):
    lo,hi,idx=0,len(T)-1,-1
    while lo<=hi:
        m=(lo+hi)//2
        if T[m]<=ts: idx=m; lo=m+1
        else: hi=m-1
    if idx<_EMA_SLOW or ATR[idx] is None or ATR[idx]<=0: return 0
    d=Ef[idx]-Es[idx]
    return 0 if abs(d)<_HTF_DEADBAND*ATR[idx] else (1 if d>0 else -1)


def simulate(entry, d, atr, X, H, L, C, j0, be=_BE_ATR):
    """Ein Trade. Gibt (R_total, stopped_in_init) zurueck. d=+1 long/-1 short.
    be = Breakeven-Schwelle in xATR (ab welchem Profit der SL auf Entry rueckt)."""
    mult = _MULT
    sl = entry - d * X * atr
    hw = entry
    size = 1.0
    realized = 0.0          # in Preis-Einheiten
    partial = False
    phase_rank = 0          # 0 Init, 1 Trail, 2 Lock
    init_stop = False
    end = min(j0 + _MAXH, len(C) - 1)
    exit_px = C[end]
    for j in range(j0, end + 1):
        hi, lo = H[j], L[j]
        # 1) Gegenlauf zuerst → SL-Treffer?
        hit = (lo <= sl) if d > 0 else (hi >= sl)
        if hit:
            exit_px = sl
            if phase_rank == 0:
                init_stop = True
            break
        # 2) HW mit Mitlauf
        hw = max(hw, hi) if d > 0 else min(hw, lo)
        # 3) Teil-Exit 50 % bei +1,5×ATR (Mitlauf-Extrem)
        fav = ((hi if d > 0 else lo) - entry) * d
        if not partial and fav >= _PART_ATR * atr:
            lvl = entry + d * _PART_ATR * atr
            realized += _PART_FRAC * (lvl - entry) * d
            size -= _PART_FRAC
            partial = True
        # 4) Phase aus Close-Profit + Ratsche
        prof = (C[j] - entry) * d
        rank = 0 if prof < _TRAIL_ON * atr else (1 if prof < _LOCK_ATR * atr else 2)
        phase_rank = max(phase_rank, rank)
        # 5) Trailing-SL nachziehen
        if phase_rank == 1:
            cand = hw - d * mult * atr
            cand = (max(cand, entry - mult * atr) if d > 0
                    else min(cand, entry + mult * atr))
            if prof >= be * atr:
                cand = max(cand, entry) if d > 0 else min(cand, entry)
            sl = max(sl, cand) if d > 0 else min(sl, cand)
        elif phase_rank == 2:
            cand = hw - d * _LOCK_MULT * atr
            cand = max(cand, entry) if d > 0 else min(cand, entry)
            sl = max(sl, cand) if d > 0 else min(sl, cand)
    R = (realized + size * (exit_px - entry) * d) / atr
    return R, init_stop


def main():
    args = sys.argv[1:]
    mode = "be" if (args and args[0] == "be") else "width"
    if mode == "be": args = args[1:]
    n_bars = int(args[0]) if args else 8000
    widths = [1.2, 1.5, 1.8, 2.0, 2.5, 3.0, 4.0]
    if not mt5.initialize(): print("init", mt5.last_error()); sys.exit(1)
    sym=None
    for c in ("SpotCrude","USOIL","WTI","XTIUSD"):
        if mt5.symbol_info(c): sym=c; break
    sym=sym or "SpotCrude"
    bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5,  0, n_bars+_N_BARS+_MAXH+5)
    m30b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M30, 0, n_bars//6+400)
    mt5.shutdown()
    if bars is None or m30b is None: print("Bars fehlen"); sys.exit(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]; T=[int(b["time"]) for b in bars]
    mT=[int(b["time"]) for b in m30b]; mc=[float(b["close"]) for b in m30b]
    mh=[float(b["high"]) for b in m30b]; ml=[float(b["low"]) for b in m30b]
    mEf=_ema_series(mc,_EMA_FAST); mEs=_ema_series(mc,_EMA_SLOW); mATR=_atr_series(mh,ml,mc)

    w=WaveRecommender(_NeutralTU(), mt5.TIMEFRAME_M5)
    # Signale einmal sammeln
    sigs=[]
    for i in range(_N_BARS, len(C)-_MAXH-1):
        wc=C[i-_N_BARS:i]; wh=H[i-_N_BARS:i]; wl=L[i-_N_BARS:i]
        atr=_atr(wh,wl,wc)
        if not atr or atr<=0: continue
        ef=_ema_last(wc,_EMA_FAST); es=_ema_last(wc,_EMA_SLOW)
        m=_htf_sign_at(T[i], mT,mEf,mEs,mATR)
        rec,_=w._build(ef,es,C[i-1],atr,"M5",5,htf_trend=m)
        s=rec["signal"]
        if s=="WARTEN": continue
        sigs.append((i, 1 if s=="LONG" else -1, max(atr,_ATR_MIN)))

    def metrics(Rs):
        n=len(Rs); win=sum(1 for r in Rs if r>0)
        gains=sum(r for r in Rs if r>0); losses=-sum(r for r in Rs if r<0)
        losers=[r for r in Rs if r<0]
        pf=gains/losses if losses>0 else float('inf')
        avg_loss=sum(losers)/len(losers) if losers else 0.0
        return n,win,pf,avg_loss

    if mode == "be":
        X = 2.0
        bes = [0.6, 0.8, 1.0, 1.3, 1.5, 99.0]
        print("="*66)
        print(f"  Breakeven-Test — {sym} M5+M30  SL={X}xATR  Signale={len(sigs)}")
        print("="*66)
        print(f"  {'Breakeven':<11}{'Treffer':>8}{'Oe-R':>8}{'Summe-R':>9}"
              f"{'PF':>6}{'Oe-Verl.':>9}{'Scratch%':>9}")
        for be in bes:
            Rs=[]; scratch=0
            for (i,d,atr) in sigs:
                R,_=simulate(C[i], d, atr, X, H, L, C, i+1, be=be)
                Rs.append(R)
                if -0.15 < R < 0.05: scratch+=1     # ~Breakeven gescratcht
            n,win,pf,avg_loss=metrics(Rs)
            lbl = "aus (nie)" if be>10 else f"{be:.1f}"
            print(f"  {lbl:<11}{100*win/n:>7.0f}%{sum(Rs)/n:>8.3f}{sum(Rs):>9.1f}"
                  f"{pf:>6.2f}{avg_loss:>9.2f}{100*scratch/n:>8.0f}%")
        print("\n  Breakeven = ab wieviel xATR Profit der SL auf Entry rückt (aus=nie)")
        print("  Scratch% = Anteil ~Breakeven-Ausgänge (R zw. −0,15 und +0,05)")
        return

    print("="*70)
    print(f"  Exit-Simulation — {sym} M5+M30  Signale={len(sigs)}  Halt<= {_MAXH} Bars")
    print("  (Teil-Exit AUS · Breakeven@0.6 · Trail HW∓1.5ATR · pessimistisch)")
    print("="*70)
    print(f"  {'Init-SL':<9}{'Treffer':>8}{'Oe-R':>8}{'Summe-R':>9}{'PF':>6}"
          f"{'Oe-Verl.':>9}{'Worst-R':>9}{'Init-Stop':>10}")
    for X in widths:
        Rs=[]; init_stops=0
        for (i,d,atr) in sigs:
            R, istop = simulate(C[i], d, atr, X, H, L, C, i+1)
            Rs.append(R)
            if istop: init_stops+=1
        n,win,pf,avg_loss=metrics(Rs)
        print(f"  {X:<9.1f}{100*win/n:>7.0f}%{sum(Rs)/n:>8.3f}{sum(Rs):>9.1f}"
              f"{pf:>6.2f}{avg_loss:>9.2f}{min(Rs):>9.2f}{100*init_stops/n:>9.0f}%")
    print("\n  Oe-R = Ø/Trade in ATR-Vielfachen · PF = Profit-Faktor")
    print("  Oe-Verl. = Ø verlierender Trade · Worst-R = größter Einzelverlust (Tail)")


if __name__ == "__main__":
    main()
