"""XV04 — Low Downside-Vol / Semivariance
Score = -roll_std(min(ret, 0), W)
Only downside dispersion is penalized; upside is irrelevant.
Buy lowest semivariance (most defensive), short highest.
W=30 canonical; small grid over universe/H/k.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec")
import xslib as xs
import numpy as np


def score_xv04(P, W=30):
    """Score = -roll_std(min(ret, 0), W).
    Causal: each row uses only past W returns.
    Higher score = lower downside vol = more preferred (long).
    """
    # clip positive returns to 0 so only downside contributes
    down = np.minimum(P.ret, 0.0)
    # rolling std of downside returns (semideviation)
    semi = xs.roll_std(down, W)
    # negate: lower semideviation = higher score = long bias
    return -semi


# --- Grid: 5 studies max ---
# 1) Canonical: all universe, W=30, H=10, k=5, L/S
rep1 = xs.study_xs(
    "XV04-W30-H10-k5-LS",
    lambda P: score_xv04(P, W=30),
    universe="all", H=10, k=5, long_short=True
)
print(xs.fmt(rep1))
print("JSON:", xs.as_json(rep1))

# 2) Majors universe (higher liquidity, 19 assets)
rep2 = xs.study_xs(
    "XV04-W30-H10-k5-LS-majors",
    lambda P: score_xv04(P, W=30),
    universe="majors", H=10, k=5, long_short=True
)
print(xs.fmt(rep2))
print("JSON:", xs.as_json(rep2))

# 3) Longer window W=60, all universe
rep3 = xs.study_xs(
    "XV04-W60-H10-k5-LS",
    lambda P: score_xv04(P, W=60),
    universe="all", H=10, k=5, long_short=True
)
print(xs.fmt(rep3))
print("JSON:", xs.as_json(rep3))

# 4) W=30, faster rebalance H=5
rep4 = xs.study_xs(
    "XV04-W30-H5-k5-LS",
    lambda P: score_xv04(P, W=30),
    universe="all", H=5, k=5, long_short=True
)
print(xs.fmt(rep4))
print("JSON:", xs.as_json(rep4))

# 5) W=30, k=3 (more concentrated)
rep5 = xs.study_xs(
    "XV04-W30-H10-k3-LS",
    lambda P: score_xv04(P, W=30),
    universe="all", H=10, k=3, long_short=True
)
print(xs.fmt(rep5))
print("JSON:", xs.as_json(rep5))

# Pick best by: earns_slot first, then holdout Sharpe, then distinctness
reps = [rep1, rep2, rep3, rep4, rep5]
earners = [r for r in reps if r["earns_slot"]]
if earners:
    best = max(earners, key=lambda r: r["holdout"].get("sharpe", -999))
else:
    # fallback: positive full + hold-out + corr_xs01 < 0.6
    candidates = [r for r in reps if r["full"]["sharpe"] > 0 and r["holdout"].get("sharpe", 0) > 0
                  and (r["corr_xs01"] or 1.0) < 0.6]
    if candidates:
        best = max(candidates, key=lambda r: r["holdout"].get("sharpe", -999))
    else:
        best = max(reps, key=lambda r: r["full"]["sharpe"])

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