#!/usr/bin/env python3
"""
Regime-router on top of existing **MTM daily series**.

Goal: improve 2022–2025 behavior by dynamically allocating between:
  - SPY Theta MTM book (lit4 + D6 + VRP) from `best_ideas_spy6_margin_daily.csv`
  - Dynamic VXX Regime Strategy Stack MTM from `vxx_regime_mtm_*_dynamic_vxx_regime_stack_daily_mtm.csv`

Router signals (simple, transparent):
  - Trend: SPY close vs SMA50
  - Realized-vol: 20-session realized vol percentile (computed over full history)

Allocation rule (default):
  if (SPY < SMA50) or (rv20_pctile >= 0.75): shift toward VXX (risk-off)
  else: shift toward SPY Theta book (risk-on)

Outputs:
  {out_prefix}_daily.csv
  {out_prefix}_meta.json
  {out_prefix}_yearly.csv

Run:

  cd /Users/robzingale/trading_bot
  PYTHONUNBUFFERED=1 .venv/bin/python RenTech/strategy_stack/run_best_ideas_regime_router.py \
    --start 2016-01-04 --end 2026-04-02 --capital 100000 \
    --out-prefix RenTech/data/logs/best_ideas_router_spy6_vxx
"""

from __future__ import annotations

import argparse
import json
import math
import sys
from pathlib import Path

import numpy as np
import pandas as pd


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

from RenTech.strategy_stack.portfolio_vrp_plus_vxx import _metrics_block


LOGS = _REPO / "RenTech" / "data" / "logs"

DEFAULT_SPY6_MTM_DAILY = LOGS / "best_ideas_spy6_margin_daily.csv"
DEFAULT_VXX_MTM_DAILY = LOGS / "vxx_regime_mtm_2016_2026_dynamic_vxx_regime_stack_daily_mtm.csv"


def _yearly_table(dates: pd.Series, pnl: pd.Series, *, capital: float) -> pd.DataFrame:
    df = pd.DataFrame({"date": pd.to_datetime(dates).dt.normalize(), "pnl": pnl.astype(float)})
    rows = []
    eq = float(capital)
    for yr, g in df.groupby(df["date"].dt.year, sort=True):
        p = float(g["pnl"].sum())
        end = eq + p
        ret = (end / eq - 1) * 100 if eq > 0 else 0.0
        intra = eq + g["pnl"].cumsum()
        dd = float(((intra / intra.cummax()) - 1).min()) * 100 if len(intra) else 0.0
        rows.append(
            {
                "year": int(yr),
                "start_equity": round(eq, 0),
                "end_equity": round(end, 0),
                "pnl_usd": round(p, 0),
                "return_pct": round(ret, 2),
                "max_dd_pct": round(dd, 2),
                "sessions": int(len(g)),
            }
        )
        eq = end
    return pd.DataFrame(rows)


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    ap.add_argument("--start", default="2016-01-04")
    ap.add_argument("--end", default="2026-04-02")
    ap.add_argument("--capital", type=float, default=100_000.0)

    ap.add_argument("--spy-mtm-daily", type=Path, default=DEFAULT_SPY6_MTM_DAILY)
    ap.add_argument("--vxx-mtm-daily", type=Path, default=DEFAULT_VXX_MTM_DAILY)

    ap.add_argument("--sma-window", type=int, default=50)
    ap.add_argument("--rv-window", type=int, default=20)
    ap.add_argument("--rv-riskoff-pctile", type=float, default=0.75)

    ap.add_argument("--w_spy_riskon", type=float, default=1.0)
    ap.add_argument("--w_vxx_riskon", type=float, default=0.5)
    ap.add_argument("--w_spy_riskoff", type=float, default=0.25)
    ap.add_argument("--w_vxx_riskoff", type=float, default=1.5)

    ap.add_argument("--out-prefix", type=Path, default=LOGS / "best_ideas_router_spy6_vxx")
    args = ap.parse_args()

    spy_path = args.spy_mtm_daily.expanduser().resolve()
    vxx_path = args.vxx_mtm_daily.expanduser().resolve()
    if not spy_path.is_file():
        raise SystemExit(f"Missing {spy_path}")
    if not vxx_path.is_file():
        raise SystemExit(f"Missing {vxx_path}")

    spy = pd.read_csv(spy_path, parse_dates=["date"])
    spy["date"] = pd.to_datetime(spy["date"]).dt.normalize()
    spy = spy.set_index("date").sort_index()
    if "spy_close" not in spy.columns or "equity_mtm_usd" not in spy.columns:
        raise SystemExit(f"Expected columns spy_close, equity_mtm_usd in {spy_path}")

    vxx = pd.read_csv(vxx_path, parse_dates=["date"])
    vxx["date"] = pd.to_datetime(vxx["date"]).dt.normalize()
    vxx = vxx.set_index("date").sort_index()
    if "pnl_stack" not in vxx.columns:
        raise SystemExit(f"Expected pnl_stack in {vxx_path}")

    idx = spy.index.intersection(vxx.index)
    idx = idx[(idx >= pd.Timestamp(args.start)) & (idx <= pd.Timestamp(args.end))]
    if len(idx) < 200:
        raise SystemExit(f"Too few aligned sessions ({len(idx)})")

    # Daily PnL series (MTM).
    pnl_spy = spy["equity_mtm_usd"].diff().fillna(0.0).reindex(idx).fillna(0.0)
    pnl_vxx = vxx["pnl_stack"].reindex(idx).fillna(0.0)

    # Signals from SPY close.
    close = spy["spy_close"].reindex(idx).astype(float)
    sma = close.rolling(int(args.sma_window)).mean()
    ret = close.pct_change()
    rv = ret.rolling(int(args.rv_window)).std() * math.sqrt(252.0)

    # Percentile computed over the whole sample (stable threshold across years).
    rv_valid = rv.dropna()
    if len(rv_valid) < 200:
        raise SystemExit("Not enough rv samples to compute percentile threshold")
    rv_thresh = float(rv_valid.quantile(float(args.rv_riskoff_pctile)))

    riskoff = (close < sma) | (rv >= rv_thresh)
    riskoff = riskoff.fillna(False)

    w_spy = np.where(riskoff.to_numpy(), float(args.w_spy_riskoff), float(args.w_spy_riskon))
    w_vxx = np.where(riskoff.to_numpy(), float(args.w_vxx_riskoff), float(args.w_vxx_riskon))

    pnl = pnl_spy.to_numpy() * w_spy + pnl_vxx.to_numpy() * w_vxx
    pnl = pd.Series(pnl, index=idx, dtype=float)
    eq = float(args.capital) + pnl.cumsum()

    out = pd.DataFrame(
        {
            "date": idx,
            "spy_close": close.values,
            "spy_sma": sma.values,
            "spy_rv20_ann": rv.values,
            "rv_thresh": rv_thresh,
            "is_riskoff": riskoff.values.astype(bool),
            "w_spy": w_spy,
            "w_vxx": w_vxx,
            "pnl_spy_theta_mtm": pnl_spy.values,
            "pnl_vxx_mtm": pnl_vxx.values,
            "pnl_router_mtm": pnl.values,
            "equity_mtm_usd": eq.values,
            "daily_return_mtm": eq.pct_change().fillna(0.0).values,
        }
    )

    prefix = args.out_prefix.expanduser().resolve()
    prefix.parent.mkdir(parents=True, exist_ok=True)
    daily_path = Path(f"{prefix}_daily.csv")
    meta_path = Path(f"{prefix}_meta.json")
    yearly_path = Path(f"{prefix}_yearly.csv")

    out.to_csv(daily_path, index=False)

    m = _metrics_block(out.set_index("date")["equity_mtm_usd"], "Router")
    meta = {
        "name": "Best Ideas Regime Router (SPY6 MTM vs VXX MTM)",
        "capital_usd": float(args.capital),
        "start": str(idx[0].date()),
        "end": str(idx[-1].date()),
        "n_sessions": int(len(idx)),
        "inputs": {
            "spy_mtm_daily": str(spy_path),
            "vxx_mtm_daily": str(vxx_path),
        },
        "signals": {
            "sma_window": int(args.sma_window),
            "rv_window": int(args.rv_window),
            "rv_riskoff_pctile": float(args.rv_riskoff_pctile),
            "rv_thresh": rv_thresh,
        },
        "weights": {
            "w_spy_riskon": float(args.w_spy_riskon),
            "w_vxx_riskon": float(args.w_vxx_riskon),
            "w_spy_riskoff": float(args.w_spy_riskoff),
            "w_vxx_riskoff": float(args.w_vxx_riskoff),
        },
        **m,
    }
    meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")

    yearly = _yearly_table(out["date"], out["pnl_router_mtm"], capital=float(args.capital))
    yearly.to_csv(yearly_path, index=False)

    print(json.dumps(meta, indent=2)[:4000], flush=True)
    print(f"\nWrote {daily_path}\nWrote {meta_path}\nWrote {yearly_path}", flush=True)


if __name__ == "__main__":
    main()

