"""Demo / validation of the MARGINAL-vs-TP01 scorer (2026-06-20).

Shows the lesson of the 104-hypothesis sweep operationalized: strategies that scored
an absolute PASS but are just TP01/TSMOM with an overlay collapse to REDUNDANT/NEUTRAL/
DILUTES under marginal scoring, while the one genuine diversifier (STA05 long-short)
earns ADDS. Run: uv run python scripts/research/alt/marginal_demo.py
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import numpy as np
import pandas as pd

import altlib as al
from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio


def tsmom_dir(df):
    """Long-flat multi-horizon TSMOM direction in {0,1} (the bare TP01 trend signal)."""
    c = df["close"].values.astype(float)
    bpd = al.bars_per_day(df)
    d = np.zeros(len(c))
    for h in (30 * bpd, 90 * bpd, 180 * bpd):
        s = np.full(len(c), np.nan)
        s[h:] = np.sign(c[h:] / c[:-h] - 1.0)
        d += np.nan_to_num(s)
    return np.clip(np.sign(d), 0, None)


def tp01_target(df):
    return TrendPortfolio(**CANONICAL).target_series(df)


FAST, SLOW = [5, 10, 20, 40], [40, 80, 120, 200]
PAIRS = [(f, s) for f in FAST for s in SLOW if f < s]


def sta05(df, long_only):
    c = df["close"].values.astype(float)
    v = np.zeros(len(c))
    for f, s in PAIRS:
        v += np.sign(al.ema(c, f) - al.ema(c, s))
    d = v / len(PAIRS)
    if long_only:
        d = np.clip(d, 0.0, 1.0)
    return al.vol_target(d, df, 0.20, 30, 2.0)


def vol03(df, asset):
    """DVOL-gated TSMOM (active only when DVOL below its expanding median)."""
    d = tsmom_dir(df)
    dv = pd.Series(al.dvol(df, asset))
    thr = dv.expanding(min_periods=30).quantile(0.5)
    gate = dv.isna() | thr.isna() | (dv < thr)
    d = np.where(gate.values, d, 0.0)
    return al.vol_target(d, df, 0.20, 30, 2.0)


def cmb04(df):
    """Momentum + low-vol filter (TSMOM taken only when realized vol below expanding median)."""
    d = tsmom_dir(df)
    bpd = al.bars_per_day(df)
    rv = al.realized_vol(al.simple_returns(df["close"].values.astype(float)), 30 * bpd, bpd * 365.25)
    med = pd.Series(rv).expanding(min_periods=60).median().values
    d = np.where((rv < med) | np.isnan(med), d, 0.0)
    return al.vol_target(d, df, 0.20, 30, 2.0)


CANDIDATES = [
    ("TP01-itself (sanity)", tp01_target),
    ("STA05 long-short (the lead)", lambda df: sta05(df, False)),
    ("STA05 long-only", lambda df: sta05(df, True)),
    ("VOL03 DVOL-gated TSMOM (overlay)", vol03),
    ("CMB04 momentum+low-vol (overlay)", cmb04),
]

print("=" * 78)
print("MARGINAL SCORING vs TP01 baseline — absolute Sharpe is NOT enough for a slot")
print("=" * 78)
rows = []
for name, fn in CANDIDATES:
    rep = al.study_marginal(name, fn, tf="1d")
    print()
    print(al.fmt_marginal(rep))
    rows.append((name, rep["abs_grade"], rep["marginal_verdict"], rep["earns_slot"]))

print("\n" + "=" * 78)
print(f"{'candidate':<36s} {'absolute':>9s} {'marginal':>10s} {'earns_slot':>11s}")
for n, ag, mv, es in rows:
    print(f"{n:<36s} {ag:>9s} {mv:>10s} {str(es):>11s}")

# sanity asserts: baseline reproduces, and an overlay-on-TSMOM does NOT earn a slot
sanity = al.marginal_vs_tp01(al.candidate_daily(tp01_target))
assert sanity["corr_full"] > 0.95, f"TP01-vs-itself corr should be ~1, got {sanity['corr_full']}"
assert abs(sanity["blends"]["w25"]["uplift_full"]) < 0.05, "TP01-vs-itself uplift should be ~0"
print("\nSANITY OK: TP01-vs-itself corr", sanity["corr_full"],
      "uplift_full", sanity["blends"]["w25"]["uplift_full"], "->", sanity["marginal_verdict"])
