#!/usr/bin/env python3
"""Ausführungs-Analyse (Track B, Mess-Kalibrierung — KEINE Strategie-Änderung):
  A) Kosten-Profil: echter Spread je Berlin-Stunde (Bar-Feld `spread`, in Points)
     absolut und als ×ATR — ersetzt die pauschale 0,1×ATR-Kostenannahme.
  B) Slippage: SL-Closes, die SCHLECHTER als der Initial-SL ausgeführt wurden
     (sichere Untergrenze der echten Slippage; getrailte SLs sind nicht rekonstruierbar).
  C) MAE/MFE der Live-Trades: wie weit liefen echte Trades ins Minus (MAE) und
     ins Plus (MFE), normiert auf ATR≈|Entry−InitialSL|/2 — validiert SL-Band,
     Breakeven-Schwelle (1,3) und Trailing an LIVE-Daten statt Simulation.
Aufruf: python analyze_execution.py [m5_bars] [m1_bars]
"""
import sys, sqlite3, datetime as dt
from zoneinfo import ZoneInfo
from collections import defaultdict
import MetaTrader5 as mt5

_BROKER_OFF = 3 * 3600
_BERLIN = ZoneInfo("Europe/Berlin")
DB = "oil_widget_history.db"

def bhour(broker_ts):
    return dt.datetime.fromtimestamp(int(broker_ts) - _BROKER_OFF,
                                     tz=dt.timezone.utc).astimezone(_BERLIN).hour

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])))
    out = []
    for i in range(len(C)):
        w = t[max(1, i-p+1):i+1]
        out.append(sum(w)/len(w) if w else None)
    return out

def main():
    n5 = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
    n1 = int(sys.argv[2]) if len(sys.argv) > 2 else 100000
    mt5.initialize()
    sym = None
    for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
        if mt5.symbol_info(c):
            sym = c; break
    si = mt5.symbol_info(sym)
    point = si.point
    bars5 = None
    for req in (n5, 80000, 60000, 40000):
        bars5 = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, req)
        if bars5 is not None and len(bars5) > 2000: break
    bars1 = None
    for req in (n1, 60000, 30000, 15000):
        bars1 = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M1, 0, req)
        if bars1 is not None and len(bars1) > 1000: break
    mt5.shutdown()

    # ── A) Kosten-Profil: Spread je Berlin-Stunde ────────────────────────────
    print("=" * 84)
    print(f"  A) KOSTEN-PROFIL — {sym}, {len(bars5)} M5-Bars, Spread aus Bar-Feld (Points×{point})")
    print("=" * 84)
    H = [float(b["high"]) for b in bars5]; L = [float(b["low"]) for b in bars5]
    C = [float(b["close"]) for b in bars5]
    AT = _atr_series(H, L, C)
    byh = defaultdict(list)
    for i, b in enumerate(bars5):
        atr = AT[i]
        if not atr or atr <= 0: continue
        sp = float(b["spread"]) * point
        if sp <= 0: continue
        byh[bhour(b["time"])].append((sp, sp / atr))
    print(f"  {'Std':>3} {'Ø-Spread':>9} {'×ATR':>6}   (Kostenannahme bisher pauschal 0,10×ATR)")
    tot = []
    for h in range(24):
        v = byh.get(h)
        if not v: continue
        sp = sum(x[0] for x in v)/len(v); rel = sum(x[1] for x in v)/len(v)
        tot += v
        mark = "  ⚠ teuer" if rel > 0.15 else ("  günstig" if rel < 0.07 else "")
        print(f"  {h:>3} {sp:>9.4f} {rel:>6.3f}{mark}")
    if tot:
        print(f"  ALLE Ø-Spread {sum(x[0] for x in tot)/len(tot):.4f} · {sum(x[1] for x in tot)/len(tot):.3f}×ATR")

    # ── Trades laden (B + C) ─────────────────────────────────────────────────
    con = sqlite3.connect(DB); con.row_factory = sqlite3.Row
    rows = [dict(r) for r in con.execute(
        "SELECT * FROM trades WHERE exit_time IS NOT NULL AND entry_price IS NOT NULL "
        "ORDER BY entry_time")]

    # ── B) Slippage bei SL-Closes ────────────────────────────────────────────
    print("\n" + "=" * 84)
    print("  B) SLIPPAGE — SL-Closes schlechter als der Initial-SL (sichere Untergrenze)")
    print("=" * 84)
    slips = []
    n_sl = 0
    for r in rows:
        if r["closed_by"] != "sl" or not r["sl_at_entry"]: continue
        n_sl += 1
        d = 1 if r["direction"] in ("BUY", "LONG", "buy") else -1
        gap = (r["sl_at_entry"] - r["exit_price"]) * d   # >0 = schlechter als Initial-SL
        if gap > 0:
            slips.append((gap, r))
    print(f"  SL-Closes gesamt: {n_sl} · davon SCHLECHTER als Initial-SL: {len(slips)}")
    if slips:
        gaps = [g for g, _ in slips]
        gaps.sort()
        print(f"  Slippage: Ø {sum(gaps)/len(gaps):.3f} · Median {gaps[len(gaps)//2]:.3f} "
              f"· Max {max(gaps):.3f} (Preis-Punkte)")
        worst = sorted(slips, key=lambda x: -x[0])[:5]
        for g, r in worst:
            t = dt.datetime.fromtimestamp(r["entry_time"]).strftime("%d.%m %H:%M")
            print(f"    {t}  {r['direction']:<4} SL {r['sl_at_entry']:.3f} → Exit "
                  f"{r['exit_price']:.3f}  Slippage {g:.3f}  (netto {((r['pnl'] or 0)+(r['commission'] or 0)):+.2f})")

    # ── C) MAE/MFE der Live-Trades (M1-Fenster) ─────────────────────────────
    print("\n" + "=" * 84)
    print(f"  C) MAE/MFE live — M1-Abdeckung {len(bars1)} Bars "
          f"(~{len(bars1)/60/24*1.0:.0f} Handelstage), ATR≈|Entry−InitSL|/2")
    print("=" * 84)
    T1 = [int(b["time"]) for b in bars1]
    H1 = [float(b["high"]) for b in bars1]; L1 = [float(b["low"]) for b in bars1]
    t0 = T1[0]
    import bisect
    cov = 0
    mae_w, mae_l, mfe_w, mfe_l = [], [], [], []
    for r in rows:
        eb = int(r["entry_time"]) + _BROKER_OFF   # lokale Epoch → Broker-Epoch
        xb = int(r["exit_time"]) + _BROKER_OFF
        if eb < t0 or not r["sl_at_entry"]: continue
        i0 = bisect.bisect_left(T1, eb); i1 = bisect.bisect_right(T1, xb)
        if i1 - i0 < 1: continue
        d = 1 if r["direction"] in ("BUY", "LONG", "buy") else -1
        entry = float(r["entry_price"])
        atr_est = abs(entry - float(r["sl_at_entry"])) / 2.0
        if atr_est <= 0: continue
        seg_h = H1[i0:i1]; seg_l = L1[i0:i1]
        # adverse/favorable Exkursion je Richtung
        if d > 0:
            mae = max(0.0, entry - min(seg_l)); mfe = max(0.0, max(seg_h) - entry)
        else:
            mae = max(0.0, max(seg_h) - entry); mfe = max(0.0, entry - min(seg_l))
        net = (r["pnl"] or 0) + (r["commission"] or 0)
        cov += 1
        (mae_w if net > 0 else mae_l).append(mae / atr_est)
        (mfe_w if net > 0 else mfe_l).append(mfe / atr_est)
    def st(v):
        if not v: return "n=0"
        v = sorted(v)
        return (f"n={len(v):>3}  Ø={sum(v)/len(v):.2f}  Median={v[len(v)//2]:.2f}  "
                f"P90={v[int(len(v)*0.9)]:.2f}")
    print(f"  Abgedeckte Trades: {cov}")
    print(f"  MAE Gewinner  {st(mae_w)}   (wie tief liefen GEWINNER ins Minus)")
    print(f"  MAE Verlierer {st(mae_l)}")
    print(f"  MFE Gewinner  {st(mfe_w)}")
    print(f"  MFE Verlierer {st(mfe_l)}   (wie viel Plus gaben VERLIERER wieder her)")
    if mfe_l:
        gave = sum(1 for x in mfe_l if x >= 1.3) / len(mfe_l)
        print(f"  Verlierer, die ≥1,3×ATR im Plus waren (Breakeven hätte greifen müssen): {100*gave:.0f}%")
    if mae_w:
        deep = sum(1 for x in mae_w if x >= 1.8) / len(mae_w)
        print(f"  Gewinner, die ≥1,8×ATR im Minus waren (SL-Band-Nähe überlebt): {100*deep:.0f}%")

if __name__ == "__main__":
    main()
