#!/usr/bin/env python3
"""
Combine **stacking** literature specs (``run_signal_backtest_stack_while_signal``) on one
Theta window: merge per-session dollar PnL, then build equity ``capital + cumsum``.

**Not for headline risk metrics:** sum-mode max drawdown uses **realized exit-day** equity only
(no open-position MTM). For return / Sharpe / max DD on overlapping option books, use::

    python -m RenTech.strategy_stack.diverse_theta_strategies_v1.evaluate_theta_margin

Modes
-----
``sum``
    ``daily_total = Σᵢ pnl_i`` — one equity pool; same as multiple full stacking books sharing
    one cash ledger (**implicitly stacks concurrent risk** across sleeves).

``third``
    ``daily_total = mean(pnl_i)`` — **heuristic** 1/N weight on each sleeve's dollar PnL
    (useful if you think of a **$100k** book with equal budget across N engines).

Default sleeves (stacking): **S055** (put-write SMA50), **S057** / **S059** (short RR), **S089**
(mild-VIX put vertical). Override with ``--sids``.

VRP sleeve
----------
Pass ``--vrp-pnl-csv`` to load the VRP daily PnL (column ``--vrp-pnl-col``, default
``pnl_vrp``) from ``portfolio_opt_10dd_sharpe_fullvrp.csv``. The VRP series is reindexed
to the Theta session grid (non-trade days filled with 0).

Example::

    cd /Users/robzingale/trading_bot && PYTHONUNBUFFERED=1 .venv/bin/python \\
      RenTech/strategy_stack/combine_lit_stack_sleeves.py \\
      --sids S055,S057,S059,S089 --start 2016-01-04 --end 2026-04-02 --capital 100000 \\
      --mode sum third \\
      --vrp-pnl-csv RenTech/data/logs/portfolio_opt_10dd_sharpe_fullvrp.csv

    # Without VRP (default ``--sids`` includes S089):
    cd /Users/robzingale/trading_bot && PYTHONUNBUFFERED=1 .venv/bin/python \\
      RenTech/strategy_stack/combine_lit_stack_sleeves.py \\
      --start 2016-01-04 --end 2026-04-02 --capital 100000 \\
      --mode sum third
"""
from __future__ import annotations

import argparse
import math
import sys
from pathlib import Path

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

import pandas as pd

from RenTech.core.theta_chunks_loader import theta_chunks_date_bounds
from RenTech.strategy_stack import research_literature_theta_strategies as L
from RenTech.strategy_stack.literature_search_agent import _compile_signal, _compile_trade
from RenTech.strategy_stack.literature_strategy_catalog import build_catalog_100
from RenTech.strategy_stack.portfolio_vrp_plus_vxx import _metrics_block


def _run_stack(
    spec_id: str,
    *,
    by_id: dict,
    days: list,
    panel,
    get_chain,
    iv_atm,
    skew_put_minus_call_iv,
    n_contracts,
    spy_wide,
) -> tuple[list, list, int, int]:
    spec = by_id[spec_id]
    sig = _compile_signal(spec, panel, iv_atm, skew_put_minus_call_iv, n_contracts, spy_wide)
    tfn = _compile_trade(spec)
    return L.run_signal_backtest_stack_while_signal(
        days, get_chain, panel, sig, int(spec.hold), tfn, spec.trade_params
    )


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("--theta-dir", type=Path, default=_REPO / "RenTech/data/theta_chunks")
    ap.add_argument(
        "--sids",
        type=str,
        default="S055,S057,S059,S089",
        help="Comma-separated catalog sids (default: put-write + RR + mild-VIX put vert).",
    )
    ap.add_argument("--start", type=str, default="2016-01-04")
    ap.add_argument("--end", type=str, default="")
    ap.add_argument("--capital", type=float, default=100_000.0)
    ap.add_argument(
        "--mode",
        nargs="+",
        default=["sum", "third"],
        choices=["sum", "third"],
        help="One or both merge modes (default: sum third)",
    )
    ap.add_argument(
        "--vrp-pnl-csv",
        type=Path,
        default=None,
        metavar="PATH",
        help=(
            "Optional path to a date-indexed CSV containing VRP daily PnL "
            "(e.g. portfolio_opt_10dd_sharpe_fullvrp.csv). "
            "Adds a 'VRP' sleeve to every merge."
        ),
    )
    ap.add_argument(
        "--vrp-pnl-col",
        type=str,
        default="pnl_vrp",
        metavar="COL",
        help="Column name in --vrp-pnl-csv to use as VRP daily PnL (default: pnl_vrp).",
    )
    ap.add_argument(
        "--out-equity-csv",
        type=Path,
        default=None,
        metavar="PATH",
        help="Write date-indexed CSV with per-sleeve and combined equity columns.",
    )
    args = ap.parse_args()

    sids = [x.strip() for x in str(args.sids).split(",") if x.strip()]
    by_id = {s.sid: s for s in build_catalog_100()}
    for sid in sids:
        if sid not in by_id:
            raise SystemExit(f"Unknown sid {sid!r}")

    end = str(args.end).strip()
    if not end:
        _, d1 = theta_chunks_date_bounds(args.theta_dir.expanduser().resolve())
        end = d1.strftime("%Y-%m-%d")
        print(f"--end omitted → {end}", flush=True)

    cap = float(args.capital)
    print(f"Preparing Theta {args.start}..{end} …", flush=True)
    days, panel, get_chain, iv_atm, skew_put_minus_call_iv, n_contracts, spy_wide = (
        L.prepare_theta_research_context(
            theta_dir=args.theta_dir,
            capital=cap,
            start=str(args.start),
            end=end,
            max_days=0,
        )
    )
    idx = pd.DatetimeIndex([L._norm(d) for d in days])
    print(f"  sessions={len(idx)}  capital=${cap:,.0f}", flush=True)

    daily_by: dict[str, pd.Series] = {}
    meta: dict[str, tuple[int, int, float, float]] = {}
    for sid in sids:
        ex, pnls, ntr, mx = _run_stack(
            sid,
            by_id=by_id,
            days=days,
            panel=panel,
            get_chain=get_chain,
            iv_atm=iv_atm,
            skew_put_minus_call_iv=skew_put_minus_call_iv,
            n_contracts=n_contracts,
            spy_wide=spy_wide,
        )
        p = L.daily_pnl_series(ex, pnls, days)
        eq, sh = L.equity_curve_from_realized(ex, pnls, days, cap)
        tot_ret = (float(eq.iloc[-1]) / cap - 1.0) * 100.0
        daily_by[sid] = p
        meta[sid] = (ntr, mx, float(sh), tot_ret)
        print(
            f"  [{sid}] hold={by_id[sid].hold}  trades={ntr}  max_open={mx}  "
            f"standalone_Sharpe={sh:.4f}  standalone_return%={tot_ret:.2f}  "
            f"{by_id[sid].description[:56]}",
            flush=True,
        )

    # --- Optional VRP sleeve ---
    if args.vrp_pnl_csv is not None:
        vrp_path = args.vrp_pnl_csv.expanduser()
        if not vrp_path.is_file():
            raise SystemExit(f"VRP PnL CSV not found: {vrp_path}")
        vrp_df = pd.read_csv(vrp_path, index_col=0, parse_dates=True)
        vrp_df.index = pd.to_datetime(vrp_df.index).normalize()
        if args.vrp_pnl_col not in vrp_df.columns:
            raise SystemExit(
                f"Column '{args.vrp_pnl_col}' not in {vrp_path}. "
                f"Available: {list(vrp_df.columns)}"
            )
        vrp_pnl = vrp_df[args.vrp_pnl_col].reindex(idx).fillna(0.0)
        vrp_standalone_eq = cap + vrp_pnl.cumsum()
        vrp_ret = (float(vrp_standalone_eq.iloc[-1]) / cap - 1.0) * 100.0
        vrp_sharpe_r = vrp_standalone_eq.pct_change().fillna(0.0)
        vrp_sh = (
            float(vrp_sharpe_r.mean()) / float(vrp_sharpe_r.std(ddof=1)) * math.sqrt(252)
            if float(vrp_sharpe_r.std(ddof=1)) > 1e-12
            else 0.0
        )
        print(
            f"  [VRP] (from CSV col '{args.vrp_pnl_col}')  "
            f"standalone_Sharpe={vrp_sh:.4f}  standalone_return%={vrp_ret:.2f}",
            flush=True,
        )
        daily_by["VRP"] = vrp_pnl

    sleeve_label = "+".join(list(daily_by.keys()))
    df = pd.DataFrame(daily_by)
    modes = list(args.mode)

    combined_equity_frames: dict[str, pd.Series] = {}
    # Per-sleeve standalone equity curves
    for sid, pnl in daily_by.items():
        combined_equity_frames[f"eq_{sid}"] = cap + pnl.cumsum()

    print("\n--- Combined stacking portfolio ---", flush=True)
    for mode in modes:
        if mode == "sum":
            comb = df.sum(axis=1)
        else:
            comb = df.mean(axis=1)
        eq_c = cap + comb.cumsum()
        combined_equity_frames[f"eq_combined_{mode}"] = eq_c
        m = _metrics_block(eq_c, f"{sleeve_label} [{mode}]")
        print(
            f"\nmode={mode!r}  "
            f"return_pct={m['return_pct']:.1f}%  CAGR%={m['cagr_pct']}  "
            f"Sharpe={m['sharpe']}  maxDD%={m['max_dd_pct']}  "
            f"end_equity=${m['end_equity']:,.0f}  years={m['years']}",
            flush=True,
        )

    if args.out_equity_csv is not None:
        out_path = args.out_equity_csv.expanduser()
        out_path.parent.mkdir(parents=True, exist_ok=True)
        eq_df = pd.DataFrame(combined_equity_frames, index=idx)
        eq_df.index.name = "date"
        eq_df.to_csv(out_path)
        print(f"\nEquity CSV → {out_path}", flush=True)


if __name__ == "__main__":
    main()
