"""
evz_analysis_v3.py
====================
Corrected EVZ analysis addressing two critical issues:

1. FIXED RISK: SL is 0.25% from entry — risk is already defined and capped.
   A trade that passes 0.20% TP but misses a wider TP does NOT lose more
   than the original SL. The worst case for a TP miss is simply NOT getting
   the extra profit — you hold longer and exit via z-reversal, hold expiry,
   or the existing SL. You cannot lose more than you already risked.

2. FULL MFE RANGE: Previous test only went to 0.40%. Stress p90 MFE = 2.4%.
   This test sweeps TP from 0.20% to 2.0% to find where the optimal
   capture point actually is across all EVZ regimes.

Run: python src/research/evz_analysis_v3.py
"""

import pandas as pd
import numpy as np
from pathlib import Path
import sys

BASE_PATH  = Path(__file__).resolve().parents[2]
PROC_PATH  = BASE_PATH / "data" / "processed"
RATES_PATH = BASE_PATH / "data" / "raw" / "rates"

NOTIONAL     = 300_000
TP_BASE      = 0.0020
STOP_PCT     = 0.0025
SPREAD       = 0.0001
ZSCORE_BANDS = [(2.75,3.50,1.0),(3.50,4.50,1.5),(4.50,99.,2.0)]

def get_mult(z):
    for lo,hi,m in ZSCORE_BANDS:
        if lo<=z<hi: return m
    return 2.0

def evz_regime(v):
    if v < 8:  return "1_Calm   (<8)"
    if v < 11: return "2_Normal (8-11)"
    if v < 15: return "3_Elevated(11-15)"
    return             "4_Stress (15+)"


def main():
    print("="*70)
    print("EVZ ANALYSIS v3 — Fixed risk, full MFE range")
    print("="*70)

    # ── Load data ─────────────────────────────────────────────────────────
    evz_path = next((p for p in [
        RATES_PATH/"EVZCLS.csv", RATES_PATH/"evz.csv"
    ] if p.exists()), None)
    evz_raw  = pd.read_csv(evz_path)
    date_col = [c for c in evz_raw.columns if 'date' in c.lower()][0]
    val_col  = [c for c in evz_raw.columns if c != date_col][0]
    evz_raw[date_col] = pd.to_datetime(evz_raw[date_col])
    evz_s    = evz_raw.set_index(date_col)[val_col].rename("evz").sort_index()
    full_idx = pd.date_range(evz_s.index.min(), evz_s.index.max(), freq='D')
    evz_ff   = evz_s.reindex(full_idx).ffill()

    trades_path = next((p for p in [
        PROC_PATH/"trades_real_costs.csv",
        PROC_PATH/"trades"/"trades_real_costs.csv",
    ] if p.exists()), None)
    df = pd.read_csv(trades_path)
    df["entry_time"] = pd.to_datetime(df["entry_time"])
    df["entry_date"] = df["entry_time"].dt.normalize()
    df["evz"]        = df["entry_date"].map(evz_ff)
    df               = df.dropna(subset=["evz"])
    df["evz_regime"] = df["evz"].apply(evz_regime)
    df["mult"]       = df["zscore_abs"].apply(get_mult)
    years = (df["entry_time"].max()-df["entry_time"].min()).days/365.25
    n     = len(df)
    per_yr = n / years

    print(f"\nTrades: {n:,}  Years: {years:.1f}  Per year: {per_yr:.0f}")

    # ── Baseline ──────────────────────────────────────────────────────────
    pnl_base = df["dollar_pnl_real"]
    tot_base = pnl_base.sum()
    wr_base  = (pnl_base > 0).mean()*100
    exc      = pnl_base/NOTIONAL - 0.04/per_yr
    sh_base  = exc.mean()/exc.std()*np.sqrt(per_yr) if exc.std()>0 else 0
    print(f"Baseline: WR={wr_base:.1f}%  Sharpe={sh_base:.2f}  "
          f"Total=${tot_base:,.0f}")

    # ── Full MFE distribution — cast wide net ─────────────────────────────
    print(f"\n{'='*70}")
    print("FULL MFE DISTRIBUTION — Where do winning trades actually go?")
    print(f"{'='*70}")

    tp_levels = [0.20, 0.25, 0.30, 0.35, 0.40, 0.50, 0.60, 0.75,
                 1.00, 1.25, 1.50, 2.00]

    print(f"\n  MFE percentiles by EVZ regime (% of entry price):")
    print(f"  {'Regime':<22}{'p25':>7}{'p50':>7}{'p75':>7}"
          f"{'p90':>7}{'p95':>7}{'p99':>8}{'max':>8}")
    print(f"  {'─'*70}")

    for regime in sorted(df["evz_regime"].unique()):
        sub = df[df["evz_regime"]==regime]
        mfe_pct = sub["mfe"] * 100
        print(f"  {regime:<22}"
              f"{mfe_pct.quantile(0.25):>7.3f}"
              f"{mfe_pct.quantile(0.50):>7.3f}"
              f"{mfe_pct.quantile(0.75):>7.3f}"
              f"{mfe_pct.quantile(0.90):>7.3f}"
              f"{mfe_pct.quantile(0.95):>7.3f}"
              f"{mfe_pct.quantile(0.99):>8.3f}"
              f"{mfe_pct.max():>8.3f}")

    print(f"\n  % of ALL trades (including losses) reaching each MFE level:")
    print(f"  {'Regime':<22}", end="")
    for tp in tp_levels:
        print(f"  {tp:.2f}%", end="")
    print()
    print(f"  {'─'*110}")
    for regime in sorted(df["evz_regime"].unique()):
        sub = df[df["evz_regime"]==regime]
        print(f"  {regime:<22}", end="")
        for tp in [t/100 for t in tp_levels]:
            pct = (sub["mfe"] >= tp).mean()*100
            print(f"  {pct:>5.1f}%", end="")
        print()

    print(f"\n  % of TP EXITS ONLY reaching each MFE level:")
    tp_exits = df[df["exit_reason"]=="tp"]
    print(f"  {'Regime':<22}", end="")
    for tp in tp_levels:
        print(f"  {tp:.2f}%", end="")
    print()
    print(f"  {'─'*110}")
    for regime in sorted(df["evz_regime"].unique()):
        sub = tp_exits[tp_exits["evz_regime"]==regime]
        if len(sub)==0: continue
        print(f"  {regime:<22}", end="")
        for tp in [t/100 for t in tp_levels]:
            pct = (sub["mfe"] >= tp).mean()*100
            print(f"  {pct:>5.1f}%", end="")
        print()

    # ── Loser analysis — how bad are the losses? ──────────────────────────
    print(f"\n{'='*70}")
    print("LOSER ANALYSIS — MAE on losing trades (risk is already fixed at 0.25%)")
    print(f"{'='*70}")

    losers = df[df["dollar_pnl_real"] < 0]
    print(f"\n  Total losers: {len(losers):,} ({len(losers)/n*100:.1f}%)")
    print(f"\n  MAE distribution on LOSING trades by EVZ regime:")
    print(f"  {'Regime':<22}{'N':>5}{'MAE med%':>10}{'MAE p75%':>10}"
          f"{'MAE p90%':>10}{'Reached SL%':>13}{'Avg $loss':>10}")
    print(f"  {'─'*75}")
    for regime in sorted(df["evz_regime"].unique()):
        sub  = losers[losers["evz_regime"]==regime]
        if len(sub)==0: continue
        mae_abs  = sub["mae"].abs()*100
        sl_reach = (sub["mae"].abs() >= STOP_PCT).mean()*100
        avg_loss = sub["dollar_pnl_real"].mean()
        print(f"  {regime:<22}{len(sub):>5}"
              f"{mae_abs.median():>10.3f}"
              f"{mae_abs.quantile(0.75):>10.3f}"
              f"{mae_abs.quantile(0.90):>10.3f}"
              f"{sl_reach:>12.1f}%"
              f"{avg_loss:>10.0f}")

    print(f"\n  WINNERS vs LOSERS — MAE comparison:")
    winners = df[df["dollar_pnl_real"] > 0]
    print(f"  {'':22}{'N':>5}{'MAE med%':>10}{'Avg $PnL':>10}")
    print(f"  {'─'*50}")
    print(f"  {'Winners':<22}{len(winners):>5}"
          f"{winners['mae'].abs().median()*100:>10.3f}"
          f"{winners['dollar_pnl_real'].mean():>10.0f}")
    print(f"  {'Losers':<22}{len(losers):>5}"
          f"{losers['mae'].abs().median()*100:>10.3f}"
          f"{losers['dollar_pnl_real'].mean():>10.0f}")

    # ── CORRECTED simulation: risk is already fixed ───────────────────────
    # Key insight: if a TP exit misses a wider TP, the WORST that happens
    # is the trade eventually exits via:
    #   a) SL at 0.25% (already the defined max loss — same as original SL trades)
    #   b) Z-exit (small return, likely near 0)
    #   c) Hold expiry (small return)
    # The extra risk from widening TP is NOT an additional SL — it is
    # simply the opportunity cost of not having exited at 0.20%.
    # We model this as: miss → exit at 0 (neither profit nor loss extra)
    # This is the realistic middle case between best (z-exit near 0.20%)
    # and worst (SL from 0.20% level = extra -0.45% vs not having exited)

    print(f"\n{'='*70}")
    print("CORRECTED SIMULATION — Fixed risk, full TP sweep")
    print("Miss = exit at breakeven (0 extra P&L) — realistic middle case")
    print(f"{'='*70}")

    sweep_results = []

    # First sweep: find optimal single TP per regime
    print(f"\n  TP LEVEL SWEEP — each regime independently:")
    print(f"  {'TP config':<45}{'Extra $':>10}{'Sharpe':>8}"
          f"{'WR%':>7}{'Affected':>10}")
    print(f"  {'─'*80}")

    for tp_calm, tp_norm, tp_elev, tp_stress, label in [
        (0.0020, 0.0020, 0.0020, 0.0020, "Flat 0.20% (baseline)"),
        (0.0020, 0.0023, 0.0025, 0.0030, "Gentle  (0.20/0.23/0.25/0.30)"),
        (0.0020, 0.0025, 0.0030, 0.0035, "Moderate(0.20/0.25/0.30/0.35)"),
        (0.0020, 0.0025, 0.0035, 0.0050, "Bold    (0.20/0.25/0.35/0.50)"),
        (0.0020, 0.0030, 0.0040, 0.0060, "Wider   (0.20/0.30/0.40/0.60)"),
        (0.0020, 0.0030, 0.0050, 0.0100, "Wide    (0.20/0.30/0.50/1.00)"),
        (0.0020, 0.0030, 0.0060, 0.0150, "Max     (0.20/0.30/0.60/1.50)"),
    ]:
        def tp_fn(evz, c=tp_calm, n_=tp_norm, e=tp_elev, s=tp_stress):
            if evz>=15: return s
            if evz>=11: return e
            if evz>=8:  return n_
            return c

        extras = []
        n_aff  = 0
        n_hit  = 0
        n_miss = 0

        for _, row in df.iterrows():
            mfe     = float(row["mfe"])
            exit_r  = str(row["exit_reason"])
            mult    = float(row["mult"])
            tp_wide = tp_fn(float(row["evz"]))

            if exit_r == "tp" and tp_wide > TP_BASE:
                n_aff += 1
                extra = (tp_wide - TP_BASE) * mult * NOTIONAL
                if mfe >= tp_wide:
                    extras.append(extra)
                    n_hit += 1
                else:
                    # Miss: exit at breakeven (0 extra vs original TP)
                    # This is realistic — trade had moved past 0.20%,
                    # now exits via z-reversal or hold expiry near zero P&L
                    extras.append(0)
                    n_miss += 1
            else:
                extras.append(0)

        extra_series = pd.Series(extras, index=df.index)
        pnl_new      = pnl_base + extra_series
        tot_new      = pnl_new.sum()
        wr_new       = (pnl_new > 0).mean()*100
        exc_new      = pnl_new/NOTIONAL - 0.04/per_yr
        sh_new       = (exc_new.mean()/exc_new.std()*np.sqrt(per_yr)
                        if exc_new.std()>0 else 0)
        extra_total  = extra_series.sum()

        hit_pct = n_hit/n_aff*100 if n_aff>0 else 0
        marker  = " ◄" if sh_new > sh_base+0.1 else ""
        print(f"  {label:<45}{extra_total:>10,.0f}"
              f"{sh_new:>8.2f}{wr_new:>7.1f}"
              f"{n_aff:>6,} ({hit_pct:.0f}% hit){marker}")

        sweep_results.append({
            'label': label, 'tp_calm': tp_calm, 'tp_norm': tp_norm,
            'tp_elev': tp_elev, 'tp_stress': tp_stress,
            'sharpe': round(sh_new,2), 'wr': round(wr_new,1),
            'extra': int(extra_total), 'total': int(tot_new),
            'n_hit': n_hit, 'n_miss': n_miss, 'n_aff': n_aff,
        })

    # ── Find optimal per-regime ───────────────────────────────────────────
    print(f"\n{'─'*70}")
    print("OPTIMAL TP LEVEL PER EVZ REGIME (independent sweep)")
    print("Question: what single TP level maximises extra profit per regime?")
    print(f"{'─'*70}")

    for regime, label in [
        ("1_Calm   (<8)",     "Calm   EVZ <8"),
        ("2_Normal (8-11)",   "Normal EVZ 8-11"),
        ("3_Elevated(11-15)", "Elevated EVZ 11-15"),
        ("4_Stress (15+)",    "Stress EVZ 15+"),
    ]:
        sub_tp = df[(df["evz_regime"]==regime) & (df["exit_reason"]=="tp")]
        if len(sub_tp)==0: continue
        print(f"\n  {label}  ({len(sub_tp)} TP exits):")
        print(f"  {'TP level':>10}{'Hit%':>8}{'Miss%':>8}"
              f"{'Extra$/trade':>14}{'Cumulative extra':>18}")
        print(f"  {'─'*58}")
        best_extra = 0
        for tp in [0.0020,0.0023,0.0025,0.0027,0.0030,0.0035,0.0040,
                   0.0050,0.0060,0.0075,0.0100,0.0125,0.0150,0.0200]:
            hit  = (sub_tp["mfe"] >= tp).mean()*100
            miss = 100 - hit
            avg_extra = ((sub_tp["mfe"]>=tp) * (tp-TP_BASE) *
                          sub_tp["mult"] * NOTIONAL).mean()
            cum_extra = avg_extra * len(sub_tp)
            marker = " ◄ optimal" if cum_extra > best_extra else ""
            if cum_extra > best_extra:
                best_extra = cum_extra
            print(f"  {tp*100:>9.2f}%{hit:>8.1f}%{miss:>8.1f}%"
                  f"{avg_extra:>14.0f}  ${cum_extra:>14,.0f}{marker}")

    # ── Final recommendation ──────────────────────────────────────────────
    print(f"\n{'='*70}")
    print("FINAL RECOMMENDATION")
    print(f"{'='*70}")

    best = max(sweep_results[1:], key=lambda r: r['sharpe'])
    print(f"\n  Baseline Sharpe : {sh_base:.2f}  Total: ${tot_base:,.0f}")
    print(f"  Best approach   : {best['label']}")
    print(f"  Best Sharpe     : {best['sharpe']:.2f}  "
          f"Total: ${best['total']:,.0f}  "
          f"Extra: ${best['extra']:,.0f}")
    print(f"  Trades affected : {best['n_aff']:,} "
          f"({best['n_hit']:,} hit / {best['n_miss']:,} miss)")

    gain = best['sharpe'] - sh_base
    if gain >= 0.3:
        print(f"\n  VERDICT: IMPLEMENT — Sharpe +{gain:.2f} above baseline")
        print(f"  The wider TP captures genuine fat-tail moves without")
        print(f"  adding new risk (SL already defined at entry)")
    elif gain >= 0.1:
        print(f"\n  VERDICT: MARGINAL — Sharpe +{gain:.2f}, below +0.3 threshold")
        print(f"  Monitor live: if high-EVZ trades consistently travel further")
        print(f"  than 0.20% TP in practice, revisit with live data")
    else:
        print(f"\n  VERDICT: NO IMPROVEMENT beyond baseline")

    print(f"\n  NOTE ON RISK: The fixed 0.25% SL caps downside regardless")
    print(f"  of TP level. A missed wider TP exits at z-reversal or hold")
    print(f"  expiry — it does not create new loss beyond original SL risk.")


if __name__ == "__main__":
    main()
