#!/usr/bin/env python3
"""
Canonical **MA slope intraday day-trade** daily CSV for Best Ideas.

Config (de-tailed): ``confirm_entry_4b`` + top-5 + max 20%% weight per name.
Alpaca 5m RTH bars; enter ~10:45 after 4-bar confirm; hold MOC; no overnight.

Writes ``date,daily_ret,daily_pnl_usd,equity_usd`` (plus meta JSON).

Example::

    cd /Users/robzingale/trading_bot && PYTHONUNBUFFERED=1 .venv/bin/python \\
        RenTech/strategy_stack/run_ma_slope_intraday_standard.py \\
        --start 2020-01-02 --end 2026-06-26 --capital 100000
"""

from __future__ import annotations

import argparse
import json
import sys
from dataclasses import asdict, replace
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.alpaca_minute_loader import (
    DEFAULT_ALPACA_RTH_DIR,
    compound_intraday_to_daily,
    list_parquet_symbols,
    load_equity_panels,
)
from RenTech.strategy_stack.ma_slope_intraday_enhanced import (
    EnhancedIntradayEngine,
    baseline_enhanced_config,
    metrics_daily,
)
from RenTech.strategy_stack.run_johansen_triplet_sp500 import load_sp500_sectors

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


def _confirm_cap20() :
    return replace(
        baseline_enhanced_config(),
        hold_mode="confirm_entry",
        confirm_lag_bars=4,
        max_weight_per_name=0.20,
    )


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    ap.add_argument("--data-dir", type=Path, default=DEFAULT_ALPACA_RTH_DIR)
    ap.add_argument("--start", default="2020-01-02")
    ap.add_argument("--end", default="2026-06-26")
    ap.add_argument("--top-n", type=int, default=5)
    ap.add_argument("--capital", type=float, default=100_000.0)
    ap.add_argument("--max-tickers", type=int, default=500)
    ap.add_argument(
        "--pad-from",
        default="2011-01-03",
        help="Zero-fill daily rows from this date through first trade (stock-only calendar)",
    )
    ap.add_argument("--out-prefix", type=Path, default=DEFAULT_OUT)
    args = ap.parse_args()

    data_dir = args.data_dir.expanduser().resolve()
    syms = list_parquet_symbols(data_dir)[: int(args.max_tickers)]
    if "SPY" not in syms:
        syms.insert(0, "SPY")
    sector_map = dict(
        zip(
            load_sp500_sectors()["ticker"].astype(str).str.upper(),
            load_sp500_sectors()["sector"].astype(str),
        )
    )

    print(f"Loading {len(syms)} symbols …", flush=True)
    intra, _ = load_equity_panels(
        syms,
        data_dir=data_dir,
        start="2019-11-01",
        end=str(args.end),
        warmup_sessions=15,
    )

    cfg = _confirm_cap20()
    eng = EnhancedIntradayEngine(config=cfg)
    panels = eng.build_panels(intra, sector_map)
    port_r = eng.run(intra, int(args.top_n), panels=panels, return_start=pd.Timestamp(args.start))
    port_r = port_r.loc[port_r.index <= pd.Timestamp(args.end)]
    daily = compound_intraday_to_daily(port_r).dropna().astype(np.float64)
    daily.name = "daily_ret"

    # Pad early calendar with zeros so stock-only intersection can keep full history.
    if args.pad_from.strip():
        pad_start = pd.Timestamp(args.pad_from)
        first = daily.index.min()
        if pad_start < first:
            pad_idx = pd.bdate_range(pad_start, first - pd.Timedelta(days=1))
            pad = pd.Series(0.0, index=pad_idx, name="daily_ret")
            daily = pd.concat([pad, daily]).sort_index()
            daily = daily[~daily.index.duplicated(keep="last")]

    cap = float(args.capital)
    pnl = daily * cap
    eq = cap * (1.0 + daily).cumprod()
    out_df = pd.DataFrame(
        {
            "date": daily.index,
            "daily_ret": daily.values,
            "daily_pnl_usd": pnl.values,
            "equity_usd": eq.values,
        }
    )

    prefix = args.out_prefix.expanduser().resolve()
    daily_path = Path(f"{prefix}_daily.csv")
    out_df.to_csv(daily_path, index=False)

    # Metrics on live (non-pad) window only
    live = daily.loc[daily.index >= pd.Timestamp(args.start)]
    live = live.loc[live.index <= pd.Timestamp(args.end)]
    # Prefer non-zero period for headline if pad included
    nz = live[live.abs() > 1e-15]
    first_trade = str(nz.index.min().date()) if len(nz) else args.start
    m = metrics_daily(live)
    meta = {
        "token": "ma_slope_intraday",
        "config": "confirm_entry_4b + top-5 + max_weight_per_name=0.20",
        "command": (
            f".venv/bin/python RenTech/strategy_stack/run_ma_slope_intraday_standard.py "
            f"--start {args.start} --end {args.end} --top-n {args.top_n} --capital {cap:.0f}"
        ),
        "start": args.start,
        "end": args.end,
        "pad_from": args.pad_from,
        "first_trade_date": first_trade,
        "n_loaded": len(intra),
        "top_n": int(args.top_n),
        "capital": cap,
        "cfg": asdict(cfg),
        "metrics": m,
        "daily_csv": str(daily_path),
    }
    Path(f"{prefix}_meta.json").write_text(json.dumps(meta, indent=2) + "\n")

    print(
        f"Return {m.get('total_return_pct', float('nan')):+.1f}%  "
        f"CAGR {m.get('cagr_pct', float('nan')):.1f}%  "
        f"Sharpe {m.get('sharpe', float('nan')):.2f}  "
        f"MaxDD {m.get('max_dd_pct', float('nan')):.1f}%",
        flush=True,
    )
    print(f"Wrote {daily_path}", flush=True)
    print(f"Wrote {prefix}_meta.json", flush=True)


if __name__ == "__main__":
    main()
