#!/usr/bin/env python3
"""
vrp_all_weather_backtest.py
===========================

Quick combined backtest:

  * **Tactical All Weather** — :class:`TacticalAllWeatherManager` (SPY/TLT/IEF/GLD/DBC baseline,
    momentum + 200-SMA sleeve gates), same construction as ``main.py --mode swing-sector``.
  * **4-regime VRP options** — :class:`VRPBacktester` + :class:`SyntheticLoader` on the same SPY/VIX
    panel (aligned dates).

Daily blend (default **70%** notional on AW, **30%** on VRP):

  ``r_blend = w_aw * r_aw + w_vrp * r_vrp``

VRP equity is taken from the backtester’s **trade-based** equity curve (updates on position exits),
forward-filled to each calendar day, then converted to daily returns. Intra-trade MTM is not in the
curve — this is a **quick** macro view, not a marked-to-market options book.

Run::

    python RenTech/strategy_stack/vrp_all_weather_backtest.py

Optional::

    python RenTech/strategy_stack/vrp_all_weather_backtest.py --period 5y --w-vrp 0.25
"""

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 numpy as np
import pandas as pd

from RenTech.core.synthetic_data_loader import SyntheticLoader
from RenTech.strategy_stack.data_loader import DataLoader
from RenTech.strategy_stack.portfolio_risk_manager import BASE_WEIGHTS, TacticalAllWeatherManager
from RenTech.strategy_stack.vrp_backtester import (
    DEFAULT_STARTING_CAPITAL,
    VRPBacktester,
    load_spy_vix_from_yfinance,
    normalize_spy_df,
)


def _max_drawdown(cumulative_equity: pd.Series) -> float:
    peak = cumulative_equity.cummax()
    dd = (cumulative_equity / peak) - 1.0
    return float(dd.min()) if len(dd) else 0.0


def _approx_sharpe(daily_ret: pd.Series, rf_annual: float = 0.04) -> float:
    r = daily_ret.dropna().astype(np.float64)
    if len(r) < 10:
        return float("nan")
    ex = r - rf_annual / 252.0
    s = float(ex.std(ddof=1))
    if s < 1e-12:
        return float("nan")
    return float(ex.mean() / s * math.sqrt(252.0))


def build_tactical_all_weather(period: str, cash_annual_yield: float) -> pd.DataFrame:
    loader = DataLoader()
    data_dict: dict[str, pd.DataFrame] = {}
    for t in BASE_WEIGHTS:
        daily_in = loader.fetch_daily(t, period=period)
        if daily_in.empty:
            raise RuntimeError(f"No daily data for {t}")
        close = daily_in["close"].astype(np.float64)
        df = daily_in.copy()
        df["ret"] = close.pct_change()
        df["sma_200"] = close.rolling(200, min_periods=200).mean()
        df["aqr_mom"] = close.shift(21) / close.shift(252) - 1.0
        data_dict[t] = df

    pm = TacticalAllWeatherManager()
    return pm.build_portfolio(data_dict, cash_annual_yield=cash_annual_yield)


def vrp_equity_series(
    spy_panel: pd.DataFrame,
    initial_capital: float,
) -> tuple[pd.Series, VRPBacktester]:
    ld = SyntheticLoader(spy_panel)
    bt = VRPBacktester(ld, initial_capital=initial_capital, spy_df=spy_panel)
    bt.run_backtest()
    idx = pd.DatetimeIndex([pd.Timestamp(d).normalize() for d, _ in bt._equity_curve])
    vals = [v for _, v in bt._equity_curve]
    return pd.Series(vals, index=idx).sort_index(), bt


def main() -> None:
    p = argparse.ArgumentParser(description="Blend Tactical All Weather + 4-regime VRP (synthetic).")
    p.add_argument("--period", default="10y", help="yfinance period for AW sleeves (e.g. 5y, 10y, max)")
    p.add_argument("--capital", type=float, default=DEFAULT_STARTING_CAPITAL, help="Starting USD")
    p.add_argument("--w-vrp", type=float, default=0.30, help="Weight on VRP sleeve (0–1)")
    p.add_argument("--cash-yield", type=float, default=0.04, help="Annual cash yield in AW engine")
    args = p.parse_args()

    w_vrp = float(args.w_vrp)
    if not 0.0 <= w_vrp <= 1.0:
        raise SystemExit("--w-vrp must be in [0, 1]")
    w_aw = 1.0 - w_vrp

    print("=" * 72)
    print(" Combined backtest: Tactical All Weather + 4-regime VRP (SyntheticLoader)")
    print(f" period={args.period!r}  capital=${args.capital:,.0f}  weights AW={w_aw:.0%} VRP={w_vrp:.0%}")
    print("=" * 72)

    aw_df = build_tactical_all_weather(args.period, args.cash_yield)
    master = aw_df.index.sort_values()

    start = master.min().strftime("%Y-%m-%d")
    end = (master.max() + pd.Timedelta(days=1)).strftime("%Y-%m-%d")
    spy_panel = normalize_spy_df(load_spy_vix_from_yfinance(start, end))

    common = master.intersection(spy_panel.index.sort_values())
    if common.empty:
        raise RuntimeError("No overlapping dates between AW panel and SPY/VIX panel")
    common = common.sort_values()

    aw_ret = aw_df["portfolio_bar_ret"].reindex(common).fillna(0.0).astype(np.float64)

    vrp_eq, bt = vrp_equity_series(spy_panel, args.capital)
    vrp_eq = vrp_eq.reindex(common).ffill()
    vrp_eq = vrp_eq.fillna(args.capital)
    vrp_ret = vrp_eq.pct_change().fillna(0.0).astype(np.float64)

    blend_ret = w_aw * aw_ret + w_vrp * vrp_ret
    eq_blend = args.capital * (1.0 + blend_ret).cumprod()
    eq_aw_only = args.capital * (1.0 + aw_ret).cumprod()
    eq_vrp_only = args.capital * (1.0 + vrp_ret).cumprod()

    tot_aw = float((1.0 + aw_ret).prod() - 1.0)
    tot_vrp = float((1.0 + vrp_ret).prod() - 1.0)
    tot_bl = float((1.0 + blend_ret).prod() - 1.0)

    print(f"\nCalendar: {common.min().date()} → {common.max().date()}  ({len(common)} days)")
    print(f"VRP closed trades: {len(bt.trade_log)}")

    print("\n--- Total return (simple compounded daily) ---")
    print(f"  Tactical All Weather only:  {tot_aw:+.2%}")
    print(f"  VRP only (100% sleeve):     {tot_vrp:+.2%}")
    print(f"  Blended ({w_aw:.0%}/{w_vrp:.0%}):        {tot_bl:+.2%}")

    print("\n--- Ending equity ($) ---")
    print(f"  AW only:    {eq_aw_only.iloc[-1]:,.2f}")
    print(f"  VRP only:   {eq_vrp_only.iloc[-1]:,.2f}")
    print(f"  Blended:    {eq_blend.iloc[-1]:,.2f}")

    print("\n--- Max drawdown (on cumulative equity) ---")
    print(f"  AW only:    {_max_drawdown(eq_aw_only / eq_aw_only.iloc[0]):.2%}")
    print(f"  VRP only:   {_max_drawdown(eq_vrp_only / eq_vrp_only.iloc[0]):.2%}")
    print(f"  Blended:    {_max_drawdown(eq_blend / eq_blend.iloc[0]):.2%}")

    print("\n--- Approx. Sharpe (excess vs 4% annual, daily) ---")
    print(f"  AW only:    {_approx_sharpe(aw_ret):.3f}")
    print(f"  VRP only:   {_approx_sharpe(vrp_ret):.3f}")
    print(f"  Blended:    {_approx_sharpe(blend_ret):.3f}")

    print("\nDone.")


if __name__ == "__main__":
    main()
