"""
V39 Diagnostic: Pre-Market Volume Acceleration Signal

Hypothesis: The QUALITY of pre-market activity matters beyond its magnitude.
A stock where pre-market volume is BUILDING into the open (acceleration) suggests
fresh sustained demand vs. a single early spike that fades.

The existing weight_premarket_dollar_vol captures the QUANTITY of pre-market activity.
This tests the SHAPE — specifically whether volume is accelerating toward the open.

Features (computed from pre-market intraday bars 4:00-9:29 ET):
  pm_accel_ratio  : last_30min_vol / first_half_vol — >1.0 means volume building
  pm_vol_per_bar  : premarket_dollar_vol / pm_bar_count — density measure
  pm_hold_ratio   : last_premarket_close / session_premarket_high — is price holding near high?

Context: The V24 400d run has trade dates with full intraday data in cache.
"""
from __future__ import annotations

import concurrent.futures
import datetime as dt
import json
import os
import sys
from pathlib import Path

import pyarrow.parquet as pq

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")))

from zoneinfo import ZoneInfo

_ET = ZoneInfo("America/New_York")
_PRE_OPEN = dt.time(4, 0)
_MKT_OPEN = dt.time(9, 30)

INTRADAY_CACHE_DIR = "data/cache/intraday"
V24_400D_RUN = "runs/intraday_orb/intraday_20260422_011012_06f59ede.json"


def _parse_ts(ts_raw: object) -> dt.datetime:
    s = str(ts_raw)
    if s.endswith("Z"):
        s = s[:-1] + "+00:00"
    return dt.datetime.fromisoformat(s).astimezone(_ET)


def load_premarket_bars(ticker: str, date: str) -> list[dict]:
    """Load all pre-market bars (4:00-9:29 ET) for a ticker on a date."""
    path = Path(INTRADAY_CACHE_DIR) / ticker / f"{date}.parquet"
    if not path.exists():
        return []
    try:
        table = pq.read_table(str(path))
        rows = table.to_pydict()
    except Exception:
        return []
    bars = []
    for i, ts_raw in enumerate(rows.get("timestamp", [])):
        try:
            ts = _parse_ts(ts_raw)
        except Exception:
            continue
        if _PRE_OPEN <= ts.time() < _MKT_OPEN:
            o = float(rows["open"][i] or 0)
            h = float(rows["high"][i] or 0)
            lo = float(rows["low"][i] or 0)
            c = float(rows["close"][i] or 0)
            v = float(rows["volume"][i] or 0)
            if v > 0 and c > 0:
                bars.append({
                    "time": ts.time(), "open": o, "high": h,
                    "low": lo, "close": c, "volume": v, "dv": c * v,
                })
    return bars


def compute_pm_features(pm_bars: list[dict]) -> dict | None:
    if len(pm_bars) < 4:
        return None
    total_dv = sum(b["dv"] for b in pm_bars)
    if total_dv <= 0:
        return None

    n = len(pm_bars)
    # Acceleration: last 30 min (last ~6 bars) vs first half
    last_6 = pm_bars[-6:]
    first_half = pm_bars[:n // 2]
    last_vol = sum(b["dv"] for b in last_6)
    first_vol = sum(b["dv"] for b in first_half)
    if first_vol <= 0:
        accel_ratio = None
    else:
        accel_ratio = last_vol / first_vol

    # Price hold: last close vs session high
    pm_high = max(b["high"] for b in pm_bars)
    last_close = pm_bars[-1]["close"]
    if pm_high <= 0:
        hold_ratio = None
    else:
        hold_ratio = last_close / pm_high

    return {
        "accel_ratio": accel_ratio,
        "hold_ratio": hold_ratio,
        "bar_count": n,
    }


def pearson(xs: list[float], ys: list[float]) -> float | None:
    n = len(xs)
    if n < 2:
        return None
    xm, ym = sum(xs) / n, sum(ys) / n
    num = sum((xs[i] - xm) * (ys[i] - ym) for i in range(n))
    dx = sum((x - xm) ** 2 for x in xs) ** 0.5
    dy = sum((y - ym) ** 2 for y in ys) ** 0.5
    if dx <= 0 or dy <= 0:
        return None
    return num / (dx * dy)


def tercile_stats(vals: list[float], rs: list[float]) -> dict:
    if len(vals) < 9:
        return {}
    pairs = sorted(zip(vals, rs), key=lambda p: p[0])
    n = len(pairs)
    t = n // 3
    def stats(sub):
        ys = [p[1] for p in sub]
        return {"n": len(ys), "wr": sum(1 for y in ys if y > 0) / len(ys), "avg_r": sum(ys) / len(ys)}
    return {"low": stats(pairs[:t]), "mid": stats(pairs[t:2*t]), "high": stats(pairs[2*t:])}


def report_feature(label: str, vals: list[float], rs: list[float]) -> None:
    n = len(vals)
    p = pearson(vals, rs)
    ts = tercile_stats(vals, rs)
    if not ts or p is None:
        print(f"  {label}: n={n}, insufficient data")
        return
    low, mid, high = ts["low"], ts["mid"], ts["high"]
    avg_r_gap = abs(high["avg_r"] - low["avg_r"])
    wr_gap = abs(high["wr"] - low["wr"])
    g1 = abs(p) >= 0.07 and n >= 120
    g2 = avg_r_gap >= 0.30
    g3 = wr_gap >= 0.05
    print(f"\n  [{label}]  n={n}  Pearson={p:+.3f}")
    print(f"    G1: {'PASS' if g1 else 'FAIL'} (|{abs(p):.3f}| {'≥' if abs(p)>=0.07 else '<'} 0.07, n={n})")
    print(f"    G2: {'PASS' if g2 else 'FAIL'} (avg_R gap = {avg_r_gap:.3f}R  [threshold 0.30R])")
    print(f"    G3: {'PASS' if g3 else 'FAIL'} (WR gap = {wr_gap*100:.1f}pp  [threshold 5pp])")
    print(f"    Bottom tercile: WR {low['wr']*100:.1f}%  avg_R {low['avg_r']:+.3f}  n={low['n']}")
    print(f"    Middle tercile: WR {mid['wr']*100:.1f}%  avg_R {mid['avg_r']:+.3f}  n={mid['n']}")
    print(f"    Top tercile:    WR {high['wr']*100:.1f}%  avg_R {high['avg_r']:+.3f}  n={high['n']}")
    print(f"    → {'ALL GATES PASS ✓' if (g1 and g2 and g3) else 'FAIL'}")


def main() -> None:
    print("=== V39 Pre-Market Volume Acceleration Diagnostic ===\n")

    with open(V24_400D_RUN) as f:
        run_data = json.load(f)
    trades = run_data.get("trades", [])
    m = run_data.get("metrics", {})
    print(f"Loaded V24 400d: {m.get('total_trades')} trades, "
          f"{m.get('start_date')} → {m.get('end_date')}")
    print(f"Return: {m.get('total_return_pct', 0)*100:.2f}%  Sharpe: {m.get('sharpe_ratio', 0):.3f}\n")

    trade_records = [
        {"ticker": t["ticker"], "date": t["date"][:10],
         "r_multiple": float(t["r_multiple_at_exit"])}
        for t in trades
        if t.get("r_multiple_at_exit") is not None
    ]
    print(f"Trades with r_multiple: {len(trade_records)}")

    accel_vals, hold_vals, r_mults = [], [], []
    missing = 0

    for rec in trade_records:
        pm_bars = load_premarket_bars(rec["ticker"], rec["date"])
        feats = compute_pm_features(pm_bars)
        if feats is None or feats["accel_ratio"] is None or feats["hold_ratio"] is None:
            missing += 1
            continue
        accel_vals.append(feats["accel_ratio"])
        hold_vals.append(feats["hold_ratio"])
        r_mults.append(rec["r_multiple"])

    n_valid = len(r_mults)
    print(f"Valid trades (pm data): {n_valid} / {len(trade_records)}  (missing: {missing})")

    print("\n" + "=" * 60)
    print("GATE RESULTS (G1: |P|≥0.07 & n≥120; G2: avg_R≥0.30R; G3: WR≥5pp)")

    report_feature("pm_accel_ratio (last_30min_dv / first_half_dv)", accel_vals, r_mults)
    report_feature("pm_hold_ratio (last_close / pm_high)", hold_vals, r_mults)

    print("\n=== Summary ===")
    print("OBV-slope 20d: Pearson=+0.235, G2=0.394R — the only passing axis.")
    print("This tests INTRADAY pre-market shape, not captured by existing premarket_dollar_vol.")


if __name__ == "__main__":
    main()
