#!/usr/bin/env python3
"""
Canonical **QS actionable** ETF sleeve (equal-weight calendar / overnight diversifiers).

Presets:
  * ``actionable-4`` — stock-only book (turnaround Tue, O/N 3-down, 1st-of-month, O/N 10d-low)
  * ``actionable-7`` — full options-book diversifier set (includes MR + combo)

Example::

    cd /Users/robzingale/trading_bot
    PYTHONUNBUFFERED=1 .venv/bin/python RenTech/strategy_stack/run_qs_actionable_etf_standard.py \\
        --preset actionable-4 --start 2016-01-04 --end 2026-04-02 --capital 100000
"""

from __future__ import annotations

import argparse
import json
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.qs_systematic_library import (
    QS_ACTIONABLE_PRESETS,
    actionable_daily_return,
)
from RenTech.strategy_stack.run_qs_top_ideas_backtest import _align, _fetch_ohlc, _metrics

LOGS = _REPO / "RenTech" / "data" / "logs"
DEFAULT_OUT_PREFIX_BY_PRESET = {
    "actionable-4": LOGS / "qs_actionable_4_standard",
    "actionable-7": LOGS / "qs_actionable_etf_standard",
}


def _load_panels(yahoo_period: str) -> dict[str, pd.DataFrame]:
    tickers = {
        "spy": "SPY",
        "tlt": "TLT",
        "gld": "GLD",
        "vix": "^VIX",
        "xlp": "XLP",
        "xlu": "XLU",
        "qqq": "QQQ",
    }
    return {k: _fetch_ohlc(t, yahoo_period) for k, t in tickers.items()}


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    ap.add_argument(
        "--preset",
        default="actionable-7",
        choices=tuple(QS_ACTIONABLE_PRESETS.keys()),
        help="actionable-4 = stock-only book; actionable-7 = full diversifier set",
    )
    ap.add_argument("--start", default="2016-01-04")
    ap.add_argument("--end", default="")
    ap.add_argument("--yahoo-period", default="max")
    ap.add_argument("--capital", type=float, default=100_000.0)
    ap.add_argument("--out-prefix", type=Path, default=None)
    args = ap.parse_args()

    preset = str(args.preset)
    members = QS_ACTIONABLE_PRESETS[preset]
    start = pd.Timestamp(args.start)
    end = pd.Timestamp(args.end) if str(args.end).strip() else None
    cap = float(args.capital)

    raw = _load_panels(args.yahoo_period)
    spy = _align(raw["spy"], start, end)
    panels = {k: _align(v, start, end) for k, v in raw.items()}
    r = actionable_daily_return(panels, spy.index, members).astype(np.float64)

    pnl = r * cap
    eq_usd = cap * (1.0 + r).cumprod()
    m = _metrics(r, cap)

    default_prefix = DEFAULT_OUT_PREFIX_BY_PRESET[preset]
    prefix = (
        args.out_prefix.expanduser().resolve()
        if args.out_prefix is not None
        else default_prefix
    )
    prefix.parent.mkdir(parents=True, exist_ok=True)
    daily_path = Path(f"{prefix}_daily.csv")
    meta_path = Path(f"{prefix}_meta.json")
    metrics_path = Path(f"{prefix}_metrics.txt")

    pd.DataFrame(
        {
            "date": spy.index.strftime("%Y-%m-%d"),
            "daily_ret": r.values,
            "daily_pnl_usd": pnl.values,
            "equity_usd": eq_usd.values,
        }
    ).to_csv(daily_path, index=False)

    meta = {
        "sleeve": "qs_actionable_etf",
        "preset": preset,
        "members": list(members),
        "combine": "equal_weight_daily_returns",
        "start": str(start.date()),
        "end": str(end.date()) if end is not None else "",
        "capital": cap,
        "metrics": m,
    }
    meta_path.write_text(json.dumps(meta, indent=2))

    label = f"QS {preset}"
    metrics_path.write_text(
        "\n".join(
            [
                f"{label}  {start.date()} → {end.date() if end else 'latest'}  ${cap:,.0f}",
                f"members: {', '.join(members)}",
                f"return%={m['total_return_pct']}  CAGR={m['cagr_pct']}%  Sharpe={m['sharpe']}  maxDD={m['max_dd_pct']}%",
                f"daily → {daily_path}",
            ]
        )
        + "\n"
    )

    print(
        f"{preset}  ret={m['total_return_pct']:.1f}%  "
        f"Sharpe={m['sharpe']:.2f}  DD={m['max_dd_pct']:.1f}%",
        flush=True,
    )
    print(f"Wrote {daily_path}", flush=True)


if __name__ == "__main__":
    main()
