"""P20-pre: Mean-reversion shorts diagnostic — pandas-only R/R check.

For each ticker × day in V49 200d window:
  - Compute RSI(14) using close price
  - Compute 20d SMA and 2σ band
  - If RSI > 80 AND close > MA20 + 2σ → "overextended" candidate
  - Look at next day open-to-close return

Hypothetical SHORT R/R:
  - Win: next-day return < 0 (close fell from open)
  - Loss: next-day return > 0
  - Compute avg_win_pct, avg_loss_pct, WR, expectancy

KILL GATE (advisor): avg_win < avg_loss → adverse R/R, abort engine build.
"""
from __future__ import annotations

import json
import sys
from pathlib import Path
import numpy as np
import pandas as pd

UNIVERSE_FILE = "data/cache/screener_universe/c71fae94f9066a528905bba18f8a9b307c95f126.json"
DAILY_DIR = Path("data/cache/daily_snapshots/orb_daily_v46_20260423")
WINDOW_START = "2025-07-09"
WINDOW_END = "2026-04-23"
MIN_PRICE = 10.0
MIN_AVG_DOLLAR_VOL = 25_000_000  # match V49 liquidity floor


def rsi(series: pd.Series, period: int = 14) -> pd.Series:
    delta = series.diff()
    gain = delta.clip(lower=0)
    loss = -delta.clip(upper=0)
    avg_gain = gain.ewm(alpha=1.0 / period, adjust=False).mean()
    avg_loss = loss.ewm(alpha=1.0 / period, adjust=False).mean()
    rs = avg_gain / avg_loss.replace(0, np.nan)
    return 100 - 100 / (1 + rs)


def main() -> None:
    universe = json.load(open(UNIVERSE_FILE))["symbols"]
    ws = pd.Timestamp(WINDOW_START)
    we = pd.Timestamp(WINDOW_END)

    candidates = []
    n_processed = 0
    n_with_data = 0

    for sym in universe:
        n_processed += 1
        path = DAILY_DIR / f"{sym}.parquet"
        if not path.exists():
            continue
        try:
            df = pd.read_parquet(path)
        except Exception:
            continue
        if len(df) < 35:
            continue
        df = df.sort_values("date").reset_index(drop=True)
        df["date"] = pd.to_datetime(df["date"])

        # Liquidity filter
        df["dollar_vol"] = df["close"] * df["volume"]
        df["adv20"] = df["dollar_vol"].rolling(20).mean()

        df["rsi14"] = rsi(df["close"], 14)
        df["ma20"] = df["close"].rolling(20).mean()
        df["std20"] = df["close"].rolling(20).std()
        df["upper2sig"] = df["ma20"] + 2 * df["std20"]

        # Next day OHLC
        df["next_open"] = df["open"].shift(-1)
        df["next_close"] = df["close"].shift(-1)
        df["next_high"] = df["high"].shift(-1)
        df["next_low"] = df["low"].shift(-1)
        df["next_oc_ret"] = (df["next_close"] / df["next_open"]) - 1.0

        # Overextended condition
        cond = (
            (df["date"] >= ws) & (df["date"] <= we)
            & (df["rsi14"] > 80)
            & (df["close"] > df["upper2sig"])
            & (df["close"] >= MIN_PRICE)
            & (df["adv20"] >= MIN_AVG_DOLLAR_VOL)
            & df["next_oc_ret"].notna()
        )
        rows = df.loc[cond, ["date", "rsi14", "close", "upper2sig", "next_open", "next_close", "next_oc_ret", "next_high", "next_low"]].copy()
        if len(rows):
            rows["ticker"] = sym
            candidates.append(rows)
            n_with_data += 1

    if not candidates:
        print("No candidates found.")
        return

    cdf = pd.concat(candidates, ignore_index=True)
    print(f"=== P20-pre: Mean-reversion shorts diagnostic ===")
    print(f"Universe: {len(universe)} symbols, processed {n_processed}, hits in {n_with_data} tickers")
    print(f"Window: {WINDOW_START} → {WINDOW_END}")
    print(f"Conditions: RSI(14) > 80 AND close > MA20 + 2σ AND price ≥ ${MIN_PRICE}, ADV20 ≥ ${MIN_AVG_DOLLAR_VOL/1e6:.0f}M")
    print(f"Total signals: {len(cdf)}")
    print()

    # Hypothetical SHORT R/R
    # Short: win = next_oc_ret < 0, loss = next_oc_ret > 0
    short_pnl_pct = -cdf["next_oc_ret"]  # short profit = -(close-open)/open
    wins = short_pnl_pct[short_pnl_pct > 0]
    losses = short_pnl_pct[short_pnl_pct < 0]
    flats = short_pnl_pct[short_pnl_pct == 0]

    n_total = len(short_pnl_pct)
    wr = len(wins) / n_total if n_total else 0.0
    avg_win = wins.mean() if len(wins) else 0.0
    avg_loss = -losses.mean() if len(losses) else 0.0  # absolute value
    expectancy = short_pnl_pct.mean()

    print(f"=== HYPOTHETICAL SHORT (open-to-close, no slippage/fees) ===")
    print(f"n: {n_total} (wins={len(wins)}, losses={len(losses)}, flats={len(flats)})")
    print(f"WR: {wr*100:.2f}%")
    print(f"Avg WIN  (price went DOWN): +{avg_win*100:.3f}%")
    print(f"Avg LOSS (price went UP):   -{avg_loss*100:.3f}%")
    print(f"Expectancy / trade:         {expectancy*100:+.4f}%")
    print(f"Total cumulative ret:       {short_pnl_pct.sum()*100:+.2f}%")
    print()

    # R/R asymmetry check (KILL gate)
    if n_total >= 30:
        ratio = avg_win / avg_loss if avg_loss > 0 else float("inf")
        print(f"R/R asymmetry: avg_win / avg_loss = {ratio:.3f}")
        if avg_win < avg_loss:
            print(f"VERDICT: KILL — adverse R/R asymmetry. avg_win < avg_loss.")
        elif expectancy < 0:
            print(f"VERDICT: KILL — negative expectancy even with R/R favorable.")
        else:
            print(f"VERDICT: PROCEED — favorable R/R, positive expectancy.")
    else:
        print(f"VERDICT: INSUFFICIENT n ({n_total} < 30) — likely too restrictive.")

    print()
    # Distribution stats
    print(f"=== Distribution ===")
    print(f"Median next-day OC: {cdf['next_oc_ret'].median()*100:+.4f}%")
    print(f"Std: {cdf['next_oc_ret'].std()*100:.3f}%")
    print(f"5th pct (worst short loss): {cdf['next_oc_ret'].quantile(0.95)*100:+.3f}%")
    print(f"95th pct (best short win):  {cdf['next_oc_ret'].quantile(0.05)*100:+.3f}%")
    print()

    # Sweep variations
    print(f"=== Threshold sensitivity ===")
    print(f"{'rsi_thr':>8} | {'sigma_thr':>8} | {'n':>6} | {'WR%':>7} | {'avg_win%':>9} | {'avg_loss%':>10} | {'expectancy%':>12} | {'verdict':>10}")
    for rsi_thr in [70, 75, 80, 85]:
        for sigma_thr in [1.5, 2.0, 2.5]:
            cond_rows = []
            for sym in universe:
                path = DAILY_DIR / f"{sym}.parquet"
                if not path.exists():
                    continue
                try:
                    df = pd.read_parquet(path)
                except Exception:
                    continue
                if len(df) < 35:
                    continue
                df = df.sort_values("date").reset_index(drop=True)
                df["date"] = pd.to_datetime(df["date"])
                df["dollar_vol"] = df["close"] * df["volume"]
                df["adv20"] = df["dollar_vol"].rolling(20).mean()
                df["rsi14"] = rsi(df["close"], 14)
                df["ma20"] = df["close"].rolling(20).mean()
                df["std20"] = df["close"].rolling(20).std()
                df["upper_band"] = df["ma20"] + sigma_thr * df["std20"]
                df["next_open"] = df["open"].shift(-1)
                df["next_close"] = df["close"].shift(-1)
                df["next_oc_ret"] = (df["next_close"] / df["next_open"]) - 1.0
                c = (
                    (df["date"] >= ws) & (df["date"] <= we)
                    & (df["rsi14"] > rsi_thr)
                    & (df["close"] > df["upper_band"])
                    & (df["close"] >= MIN_PRICE)
                    & (df["adv20"] >= MIN_AVG_DOLLAR_VOL)
                    & df["next_oc_ret"].notna()
                )
                rr = df.loc[c, "next_oc_ret"]
                if len(rr):
                    cond_rows.append(rr)
            if not cond_rows:
                continue
            r = pd.concat(cond_rows, ignore_index=True)
            sp = -r
            n = len(sp)
            w = sp[sp > 0]
            l = sp[sp < 0]
            wr_v = len(w) / n if n else 0
            aw = w.mean() if len(w) else 0
            al = -l.mean() if len(l) else 0
            ex = sp.mean()
            verdict = "PROCEED" if (aw >= al and ex > 0 and n >= 30) else "KILL"
            print(f"{rsi_thr:>8d} | {sigma_thr:>8.1f} | {n:>6d} | {wr_v*100:>6.2f} | {aw*100:>8.3f} | {al*100:>9.3f} | {ex*100:>+11.4f} | {verdict:>10}")


if __name__ == "__main__":
    main()
