"""XV02 — Low Idiosyncratic Volatility Anomaly.

Score = -roll_std(residual_return(ret, beta_win=60), 30)
(negative idiosyncratic volatility: low idio-vol = long, high idio-vol = short).

Distinct from total-vol because we strip the market factor first (beta*mkt),
keeping only the firm-specific noise. In equities this is the "low idio-vol" anomaly
(Ang et al. 2006): low idiosyncratic volatility stocks outperform. Testing if the
same holds cross-sectionally on the HL alt panel.

Grid (<=5 calls total):
  1. majors H=10 k=5 LS   (baseline)
  2. all    H=10 k=5 LS   (broader universe)
  3. majors H=5  k=5 LS   (faster rebalance)
  4. majors H=10 k=4 LS   (narrower book)
  5. majors H=10 k=5 LS, shorter beta window (30d)  [to test sensitivity]
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec")
import xslib as xs
import numpy as np


# ---------------------------------------------------------------------------
# SCORE: negative idiosyncratic vol over last 30 days
#        (idio ret = ret - rolling_beta_60d * market_ret, then 30d rolling std)
# ---------------------------------------------------------------------------
def score_idiovol(P, beta_win=60, vol_win=30):
    """Low idiosyncratic volatility score (higher = lower idio-vol = long)."""
    idio = xs.residual_return(P.ret, beta_win)           # n_days x n_assets
    idio_vol = xs.roll_std(idio, vol_win)                 # rolling std of idio ret
    # negate: lower vol → higher score → long
    return -idio_vol


results = []

# ---- 1. Baseline: majors, H=10, k=5, LS (beta_win=60, vol_win=30) ----
rep1 = xs.study_xs(
    "XV02_majors_H10k5",
    lambda P: score_idiovol(P, beta_win=60, vol_win=30),
    universe="majors",
    H=10, k=5, long_short=True,
)
print(xs.fmt(rep1))
print("JSON:", xs.as_json(rep1))
results.append(rep1)

# ---- 2. Broader universe: all, H=10, k=5, LS ----
rep2 = xs.study_xs(
    "XV02_all_H10k5",
    lambda P: score_idiovol(P, beta_win=60, vol_win=30),
    universe="all",
    H=10, k=5, long_short=True,
)
print(xs.fmt(rep2))
print("JSON:", xs.as_json(rep2))
results.append(rep2)

# ---- 3. Faster rebalance: majors, H=5, k=5, LS ----
rep3 = xs.study_xs(
    "XV02_majors_H5k5",
    lambda P: score_idiovol(P, beta_win=60, vol_win=30),
    universe="majors",
    H=5, k=5, long_short=True,
)
print(xs.fmt(rep3))
print("JSON:", xs.as_json(rep3))
results.append(rep3)

# ---- 4. Narrower book: majors, H=10, k=4, LS ----
rep4 = xs.study_xs(
    "XV02_majors_H10k4",
    lambda P: score_idiovol(P, beta_win=60, vol_win=30),
    universe="majors",
    H=10, k=4, long_short=True,
)
print(xs.fmt(rep4))
print("JSON:", xs.as_json(rep4))
results.append(rep4)

# ---- 5. Shorter beta window: majors, H=10, k=5, LS, beta_win=30 ----
rep5 = xs.study_xs(
    "XV02_majors_H10k5_bw30",
    lambda P: score_idiovol(P, beta_win=30, vol_win=30),
    universe="majors",
    H=10, k=5, long_short=True,
)
print(xs.fmt(rep5))
print("JSON:", xs.as_json(rep5))
results.append(rep5)

# ---- Summary ----
print("\n=== XV02 GRID SUMMARY ===")
for r in results:
    earns = r["earns_slot"]
    print(
        f"  {r['name']:30s}  FULL {r['full']['sharpe']:+.2f}  "
        f"HOLD {r['holdout'].get('sharpe', 0):+.2f}  "
        f"corr_xs01 {r['corr_xs01']}  "
        f"marginal={r['marginal']['verdict']}  "
        f"earns_slot={earns}"
    )

# ---- Best config: pick by earns_slot first, then hold-out ----
earners = [r for r in results if r["earns_slot"]]
if earners:
    best = max(earners, key=lambda r: r["holdout"].get("sharpe", -999))
    print(f"\nBEST (earns_slot): {best['name']}")
else:
    # fallback: best hold-out Sharpe with distinct XS01 corr
    distinct = [r for r in results if (r["corr_xs01"] or 1.0) < 0.6]
    if distinct:
        best = max(distinct, key=lambda r: r["holdout"].get("sharpe", -999))
    else:
        best = max(results, key=lambda r: r["holdout"].get("sharpe", -999))
    print(f"\nBEST (hold-out, no earns_slot): {best['name']}")

print("\n--- BEST CONFIG ---")
print(xs.fmt(best))
print("JSON:", xs.as_json(best))
