"""P18: Regime-aware sizing diagnostic.

Pull V49 daily PnL + QQQ daily return + breadth proxy. Identify worst-10% loss days,
test whether a simple QQQ-return threshold classifier (applied as size-down filter)
improves V49 net Sharpe/DD without sacrificing return.
"""
from __future__ import annotations

import json
import statistics
import sys
from pathlib import Path

import pandas as pd

V49_BASELINE_RUN = "runs/v50_sweep12/v49_baseline_200d.json/intraday_20260424_215409_73036072.json"
QQQ_PARQUET = "data/cache/daily/QQQ.parquet"


def load_v49_daily() -> pd.DataFrame:
    with open(V49_BASELINE_RUN) as f:
        d = json.load(f)
    rows = d["daily_summary"]
    df = pd.DataFrame(rows)
    df["date"] = pd.to_datetime(df["date"])
    return df[["date", "daily_pnl", "trades", "skip_reason", "regime_scaler",
               "breadth_scaler", "market_orb_quality_scaler"]]


def load_qqq() -> pd.DataFrame:
    qqq = pd.read_parquet(QQQ_PARQUET)
    qqq["date"] = pd.to_datetime(qqq["date"])
    qqq = qqq.sort_values("date").reset_index(drop=True)
    qqq["qqq_ret"] = (qqq["close"] / qqq["open"]) - 1.0  # intraday open-to-close
    qqq["qqq_overnight_ret"] = (qqq["open"] / qqq["close"].shift(1)) - 1.0
    qqq["qqq_full_ret"] = (qqq["close"] / qqq["close"].shift(1)) - 1.0
    return qqq[["date", "qqq_ret", "qqq_overnight_ret", "qqq_full_ret"]]


def main() -> None:
    v49 = load_v49_daily()
    qqq = load_qqq()
    df = v49.merge(qqq, on="date", how="left")
    df = df.dropna(subset=["qqq_full_ret"])  # drop pre-window days

    print(f"=== V49 200d daily PnL stats (n={len(df)}) ===")
    print(f"Total PnL: ${df['daily_pnl'].sum():,.2f}")
    print(f"Mean daily PnL: ${df['daily_pnl'].mean():.2f}")
    print(f"Median: ${df['daily_pnl'].median():.2f}")
    print(f"Std: ${df['daily_pnl'].std():.2f}")
    print(f"Active days (trades>0): {(df['trades']>0).sum()}")
    print(f"Pos days: {(df['daily_pnl']>0).sum()}, Neg days: {(df['daily_pnl']<0).sum()}, Zero days: {(df['daily_pnl']==0).sum()}")

    # Sort worst → best
    sorted_df = df.sort_values("daily_pnl").reset_index(drop=True)
    worst10 = sorted_df.head(int(len(df) * 0.10))
    worst20 = sorted_df.head(int(len(df) * 0.20))
    best10 = sorted_df.tail(int(len(df) * 0.10))

    print(f"\n=== Worst-10% days (n={len(worst10)}) ===")
    print(f"Total PnL contribution: ${worst10['daily_pnl'].sum():,.2f}")
    print(f"Avg QQQ intraday ret on worst-10%: {worst10['qqq_ret'].mean()*100:.3f}%")
    print(f"Avg QQQ overnight ret on worst-10%: {worst10['qqq_overnight_ret'].mean()*100:.3f}%")
    print(f"Avg QQQ full ret on worst-10%: {worst10['qqq_full_ret'].mean()*100:.3f}%")
    print(f"Worst-10% with QQQ_full_ret < 0: {(worst10['qqq_full_ret']<0).sum()}/{len(worst10)}")
    print(f"Worst-10% with QQQ_intraday < 0: {(worst10['qqq_ret']<0).sum()}/{len(worst10)}")

    print(f"\n=== Best-10% days (n={len(best10)}) ===")
    print(f"Total PnL contribution: ${best10['daily_pnl'].sum():,.2f}")
    print(f"Avg QQQ intraday ret on best-10%: {best10['qqq_ret'].mean()*100:.3f}%")
    print(f"Avg QQQ full ret on best-10%: {best10['qqq_full_ret'].mean()*100:.3f}%")

    # Correlation
    active = df[df["trades"] > 0]
    if len(active) > 5:
        corr_intraday = active[["daily_pnl", "qqq_ret"]].corr().iloc[0, 1]
        corr_full = active[["daily_pnl", "qqq_full_ret"]].corr().iloc[0, 1]
        print(f"\n=== Correlations (active days only, n={len(active)}) ===")
        print(f"V49 PnL vs QQQ intraday ret: {corr_intraday:.4f}")
        print(f"V49 PnL vs QQQ full ret: {corr_full:.4f}")

    # Test classifier: skip days where QQQ_full_ret prior bar (proxy for entry-time regime) was very negative
    # Use overnight + so-far signal: QQQ_overnight_ret as the entry-time signal (known by 9:30)
    print(f"\n=== Classifier sweep: skip when QQQ_overnight_ret < threshold ===")
    print(f"{'thr':>8} | {'days_skipped':>12} | {'pnl_saved':>12} | {'pnl_lost':>12} | {'net':>10} | {'frac_worst10_caught':>20}")
    for thr in [-0.020, -0.015, -0.010, -0.005, -0.002, 0.0]:
        skip_mask = df["qqq_overnight_ret"] < thr
        days_skipped = skip_mask.sum()
        pnl_skipped = df.loc[skip_mask, "daily_pnl"]
        pnl_saved = -pnl_skipped[pnl_skipped < 0].sum()  # losses avoided
        pnl_lost = pnl_skipped[pnl_skipped > 0].sum()    # gains foregone
        net = pnl_saved - pnl_lost
        # Worst-10% days caught by this filter
        caught = skip_mask & df["daily_pnl"].isin(worst10["daily_pnl"])
        frac = caught.sum() / len(worst10) if len(worst10) > 0 else 0.0
        print(f"{thr:>8.4f} | {days_skipped:>12d} | ${pnl_saved:>10,.0f} | ${pnl_lost:>10,.0f} | ${net:>8,.0f} | {frac*100:>18.1f}%")

    # Same with intraday signal (cheating — uses look-ahead but useful as upper bound)
    print(f"\n=== UPPER BOUND (look-ahead): skip when QQQ_intraday_ret < threshold ===")
    print(f"{'thr':>8} | {'days_skipped':>12} | {'pnl_saved':>12} | {'pnl_lost':>12} | {'net':>10}")
    for thr in [-0.015, -0.010, -0.005, -0.002, 0.0, 0.002]:
        skip_mask = df["qqq_ret"] < thr
        days_skipped = skip_mask.sum()
        pnl_skipped = df.loc[skip_mask, "daily_pnl"]
        pnl_saved = -pnl_skipped[pnl_skipped < 0].sum()
        pnl_lost = pnl_skipped[pnl_skipped > 0].sum()
        net = pnl_saved - pnl_lost
        print(f"{thr:>8.4f} | {days_skipped:>12d} | ${pnl_saved:>10,.0f} | ${pnl_lost:>10,.0f} | ${net:>8,.0f}")

    # Sharpe / DD calculation under best causal classifier
    print(f"\n=== Counterfactual: V49 with QQQ_overnight_ret >= 0 filter ===")
    causal_thr = 0.0
    keep_mask = df["qqq_overnight_ret"] >= causal_thr
    cf_pnl = df["daily_pnl"].copy()
    cf_pnl[~keep_mask] = 0.0  # filter trades on skip days
    base_total = df["daily_pnl"].sum()
    cf_total = cf_pnl.sum()
    print(f"Base total PnL: ${base_total:,.2f}")
    print(f"Filtered total PnL: ${cf_total:,.2f}")
    print(f"Net effect: ${cf_total - base_total:+,.2f} ({(cf_total/base_total - 1)*100:+.2f}%)")

    # Active-day sharpe
    base_active = df[df["trades"] > 0]["daily_pnl"]
    cf_active = cf_pnl[(df["trades"] > 0) & keep_mask]
    if len(base_active) > 1 and len(cf_active) > 1:
        base_sharpe = base_active.mean() / base_active.std() * (252 ** 0.5)
        cf_sharpe = cf_active.mean() / cf_active.std() * (252 ** 0.5)
        print(f"Base active-day Sharpe: {base_sharpe:.3f}")
        print(f"Filtered active-day Sharpe: {cf_sharpe:.3f}")

    # Drawdown
    base_eq = (1.0 + df["daily_pnl"] / 10000).cumprod()
    cf_eq = (1.0 + cf_pnl / 10000).cumprod()
    base_dd = ((base_eq - base_eq.cummax()) / base_eq.cummax()).min() * 100
    cf_dd = ((cf_eq - cf_eq.cummax()) / cf_eq.cummax()).min() * 100
    print(f"Base max DD: {base_dd:.2f}%")
    print(f"Filtered max DD: {cf_dd:.2f}%")


if __name__ == "__main__":
    main()
