#!/usr/bin/env python3
"""
Compare the **same** 4-regime VRP backtest on **identical** trading calendars:

  * **iVolatility** — EOD option chains from Parquet (:class:`IVolatilityLoader`).
  * **XGBoost surface** — synthetic mids from :class:`SyntheticLoader` (same SPY/VIX panel).

Both runs use the **same** ``spy_df`` (yfinance close + SMA200 + VIX) and the **same**
``trading_days`` list, so differences come from **option pricing / chain**, not from the
macro filter.

Usage::

    python RenTech/strategy_stack/vrp_iv_vs_xgb_compare.py
    python RenTech/strategy_stack/vrp_iv_vs_xgb_compare.py --parquet /path/to/options.parquet
"""

from __future__ import annotations

import argparse
import math
import sys
from pathlib import Path

_REPO_ROOT = Path(__file__).resolve().parents[2]
if str(_REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(_REPO_ROOT))

import pandas as pd

from RenTech.core.options_data_loader import IVolatilityLoader
from RenTech.core.synthetic_data_loader import SyntheticLoader
from RenTech.strategy_stack.vrp_backtester import (
    DEFAULT_IV_PARQUET_PATH,
    DEFAULT_STARTING_CAPITAL,
    VRPBacktester,
    load_spy_vix_from_yfinance,
    normalize_spy_df,
    parquet_file_date_bounds,
    trading_days_intersecting_spy,
)


def _run_labelled(
    label: str,
    bt: VRPBacktester,
    days: list[pd.Timestamp],
) -> dict[str, float | int]:
    bt.run_backtest(trading_days=days)
    m = bt.metrics()
    return {
        "label": label,
        "trades": int(m["total_trades"]),
        "end_cap": float(m["ending_capital"]),
        "tot_ret": float(m["total_return"]),
        "max_dd": float(m["max_drawdown"]),
        "win_rate": float(m["win_rate"]),
        "r1": int(m["pmcc_trades"]),
        "r2": int(m["diagonal_trades"]),
        "r3": int(m["naked_trades"]),
        "r4": int(m["credit_spread_trades"]),
    }


def _fmt_pct(x: float) -> str:
    if not math.isfinite(x):
        return "n/a"
    return f"{x:.2%}"


def main() -> None:
    ap = argparse.ArgumentParser(description="VRP: iVolatility Parquet vs Synthetic XGB surface")
    ap.add_argument(
        "--parquet",
        type=Path,
        default=DEFAULT_IV_PARQUET_PATH,
        help="Path to spy_options_eod_combined.parquet (or equivalent)",
    )
    ap.add_argument("--capital", type=float, default=DEFAULT_STARTING_CAPITAL)
    args = ap.parse_args()
    pq: Path = args.parquet

    if not pq.is_file():
        print(f"ERROR: Parquet not found: {pq}")
        print("Build it with IVolatilityLoader.combine_csvs_to_parquet(...) or set --parquet.")
        sys.exit(1)

    d0, d1 = parquet_file_date_bounds(pq)
    yf_start = (d0 - pd.Timedelta(days=400)).strftime("%Y-%m-%d")
    yf_end = (d1 + pd.Timedelta(days=5)).strftime("%Y-%m-%d")
    spy_wide = normalize_spy_df(load_spy_vix_from_yfinance(yf_start, yf_end))

    ld_iv = IVolatilityLoader(pq)
    days = trading_days_intersecting_spy(ld_iv, spy_wide.index, d0, d1)
    if not days:
        in_spy = set(spy_wide.index)
        days = sorted(
            {pd.Timestamp(d).normalize() for d in ld_iv.iter_chain_dates() if pd.Timestamp(d).normalize() in in_spy}
        )
    if not days:
        print("ERROR: No overlapping chain dates vs SPY panel.")
        sys.exit(1)

    cap = float(args.capital)
    print("=" * 76)
    print(" VRP 4-regime comparison: iVolatility (Parquet) vs XGBoost SyntheticLoader")
    print(f" Parquet: {pq}")
    print(f" Window:  {d0.date()} → {d1.date()}  |  {len(days)} shared trading days")
    print(f" Capital: ${cap:,.0f}  |  Same spy_df + same day list for both engines")
    print("=" * 76)

    res_iv = _run_labelled(
        "iVolatility EOD",
        VRPBacktester(ld_iv, initial_capital=cap, spy_df=spy_wide),
        days,
    )
    res_xgb = _run_labelled(
        "XGBoost synthetic",
        VRPBacktester(SyntheticLoader(spy_wide), initial_capital=cap, spy_df=spy_wide),
        days,
    )

    print(f"\n{'Metric':<22} {'iVolatility':>18} {'XGB synthetic':>18} {'Delta':>14}")
    print("-" * 76)
    print(f"{'Ending capital ($)':<22} {res_iv['end_cap']:>18,.2f} {res_xgb['end_cap']:>18,.2f} {res_xgb['end_cap'] - res_iv['end_cap']:>+14,.2f}")
    print(f"{'Total return':<22} {_fmt_pct(res_iv['tot_ret']):>18} {_fmt_pct(res_xgb['tot_ret']):>18} {_fmt_pct(res_xgb['tot_ret'] - res_iv['tot_ret']):>14}")
    print(f"{'Max drawdown':<22} {_fmt_pct(res_iv['max_dd']):>18} {_fmt_pct(res_xgb['max_dd']):>18} {_fmt_pct(res_xgb['max_dd'] - res_iv['max_dd']):>14}")
    print(f"{'Trade count':<22} {res_iv['trades']:>18} {res_xgb['trades']:>18} {res_xgb['trades'] - res_iv['trades']:>+14}")
    print(f"{'Win rate (all)':<22} {_fmt_pct(res_iv['win_rate']):>18} {_fmt_pct(res_xgb['win_rate']):>18} {_fmt_pct(res_xgb['win_rate'] - res_iv['win_rate']):>14}")
    print("-" * 76)
    print(f"{'R1 (pmcc key) trades':<22} {res_iv['r1']:>18} {res_xgb['r1']:>18} {res_xgb['r1'] - res_iv['r1']:>+14}")
    print(f"{'R2 diagonal trades':<22} {res_iv['r2']:>18} {res_xgb['r2']:>18} {res_xgb['r2'] - res_iv['r2']:>+14}")
    print(f"{'R3 naked trades':<22} {res_iv['r3']:>18} {res_xgb['r3']:>18} {res_xgb['r3'] - res_iv['r3']:>+14}")
    print(f"{'R4 credit trades':<22} {res_iv['r4']:>18} {res_xgb['r4']:>18} {res_xgb['r4'] - res_iv['r4']:>+14}")

    print("\n--- Interpretation ---")
    print(
        "SyntheticLoader uses model IV + B–S mids (tight, smooth). iVolatility uses "
        "observed EOD bid/ask/mid + real chain gaps — usually **lower** PnL if the model was optimistic."
    )
    iv_e, xg_e = res_iv["end_cap"], res_xgb["end_cap"]
    if iv_e > 1e3 and xg_e > 0:
        print(f"Ending equity ratio (XGB / IV) ≈ {xg_e / iv_e:.2f}x")
    elif iv_e <= 0 < xg_e:
        print("IV path ended with non-positive equity (loss spiral / sizing); ratio not meaningful.")
    print()


if __name__ == "__main__":
    main()
