#!/usr/bin/env python3
"""
Legacy **SPDR sector buy-the-dip** sleeve (RSI washout in SMA200 uptrend).

**Superseded** for the Best Ideas stack by ``run_sp500_dip_standard.py`` (S&P 500
≥3% drop, hold 10d, top 10). Kept for benchmarks and ``experiment_buy_the_dip.py``.

Universe: 11 Select Sector SPDR ETFs.

Example::

    cd /Users/robzingale/trading_bot
    PYTHONUNBUFFERED=1 .venv/bin/python RenTech/strategy_stack/run_sector_dip_standard.py \\
        --start 2016-01-04 --yahoo-period max --top-n 3
"""

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.data_loader import DataLoader
from RenTech.strategy_stack.main import _compute_daily_backtest_features, _load_sector_etf_dict
from RenTech.strategy_stack.multi_strategy_manager import BuyTheDipSleeve, SPDR_SECTOR_TICKERS

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


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="")
    ap.add_argument("--yahoo-period", default="max", help="Yahoo history window (default max)")
    ap.add_argument("--top-n", type=int, default=3, help="Max new dip entries per day (default 3)")
    ap.add_argument("--capital", type=float, default=100_000.0, help="Notional for daily_pnl_usd column")
    ap.add_argument("--out-prefix", type=Path, default=DEFAULT_OUT_PREFIX)
    ap.add_argument("--rsi-period", type=int, default=5)
    ap.add_argument("--rsi-max", type=float, default=20.0)
    ap.add_argument("--hold-days", type=int, default=5)
    ap.add_argument("--atr-period", type=int, default=5)
    ap.add_argument("--trend-sma", type=int, default=200)
    ap.add_argument(
        "--spy-bull-only",
        action="store_true",
        help="Only open new dips when SPY > SMA200",
    )
    args = ap.parse_args()

    etf_dict = _load_sector_etf_dict(args.yahoo_period)
    spy_df = _compute_daily_backtest_features(
        DataLoader().fetch_daily("SPY", period=args.yahoo_period)
    )
    eng = BuyTheDipSleeve(
        rsi_period=int(args.rsi_period),
        rsi_max=float(args.rsi_max),
        atr_period=int(args.atr_period),
        hold_trading_days=int(args.hold_days),
        sma_trend_window=int(args.trend_sma),
        only_when_spy_bull=bool(args.spy_bull_only),
    )
    daily_ret = eng.generate_returns(
        etf_dict,
        top_n=int(args.top_n),
        spy_df=spy_df,
        verbose=True,
    )
    daily_ret = daily_ret.sort_index()
    daily_ret.index = pd.to_datetime(daily_ret.index).tz_localize(None)
    mask = daily_ret.index >= pd.Timestamp(args.start)
    if args.end.strip():
        mask &= daily_ret.index <= pd.Timestamp(args.end)
    r = daily_ret.loc[mask].fillna(0.0).astype(np.float64)

    cap = float(args.capital)
    pnl = r * cap
    eq_unit = (1.0 + r).cumprod()
    eq_usd = cap * eq_unit

    n = len(r)
    years = n / 252.0
    end_eq = float(eq_usd.iloc[-1])
    total_ret = end_eq / cap - 1.0
    cagr = (end_eq / cap) ** (1.0 / years) - 1.0 if years > 0 else float("nan")
    dd = eq_usd / eq_usd.cummax() - 1.0
    max_dd = float(dd.min())
    sd = float(r.std(ddof=1)) if n > 1 else float("nan")
    sharpe = float(r.mean() / sd * np.sqrt(252.0)) if sd > 1e-12 else float("nan")
    vol_ann = sd * np.sqrt(252.0) if np.isfinite(sd) else float("nan")

    spy_r = spy_df["ret"].astype(float)
    spy_r.index = pd.to_datetime(spy_r.index).tz_localize(None)
    rho_spy = float(pd.DataFrame({"dip": r, "spy": spy_r}).dropna().corr().iloc[0, 1])

    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")
    metrics_path = Path(f"{prefix}_metrics.txt")

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

    cmd = (
        f"cd {_REPO} && PYTHONUNBUFFERED=1 .venv/bin/python "
        f"RenTech/strategy_stack/run_sector_dip_standard.py "
        f"--start {args.start} --yahoo-period {args.yahoo_period} --top-n {args.top_n}"
    )
    if args.end.strip():
        cmd += f" --end {args.end}"
    if args.spy_bull_only:
        cmd += " --spy-bull-only"

    meta = {
        "strategy": "sector_dip",
        "token": "sector_dip",
        "portfolio_status": "approved_for_total_portfolio",
        "engine": "BuyTheDipSleeve",
        "tickers_universe": SPDR_SECTOR_TICKERS,
        "top_n": int(args.top_n),
        "capital_usd": cap,
        "yahoo_period": args.yahoo_period,
        "start": str(r.index.min().date()),
        "end": str(r.index.max().date()),
        "n_trading_days": int(n),
        "total_return_pct": round(100.0 * total_ret, 4),
        "cagr_pct": round(100.0 * cagr, 4),
        "sharpe_daily": round(sharpe, 4),
        "vol_annual_pct": round(100.0 * vol_ann, 4),
        "max_drawdown_pct": round(100.0 * max_dd, 4),
        "corr_vs_spy_daily": round(rho_spy, 4),
        "command": cmd,
        "daily_csv": str(daily_path),
    }
    meta_path.write_text(json.dumps(meta, indent=2) + "\n")

    metrics_path.write_text(
        f"""# Sector dip standard ({meta['start']} -> {meta['end']})

Command:
{cmd}

Headline ($ {cap:,.0f} notional, daily_pnl_usd = daily_ret * capital):
  Total return: {meta['total_return_pct']:.2f}%
  CAGR: {meta['cagr_pct']:.2f}%
  Sharpe: {meta['sharpe_daily']:.3f}
  Max DD: {meta['max_drawdown_pct']:.2f}%
  Corr vs SPY: {meta['corr_vs_spy_daily']:.3f}

Artifacts:
  {daily_path}
  {meta_path}
"""
    )

    print(metrics_path.read_text(), flush=True)
    print(f"Wrote {daily_path}", flush=True)


if __name__ == "__main__":
    main()
