"""XS04b — Ensemble z-vote cross-sectional strategy.

Score = mean of xs_zscore over {momentum90, -vol30, -skew60, -beta60}.
Each component is z-scored cross-sectionally per row, then averaged.
Diversified signal: momentum (strong assets), low vol (stable), negative skew
(avoid lottery stocks), low beta (idiosyncratic leaders).

Grid: universe x H x k — 5 calls max.
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec")
import xslib as xs
import numpy as np


def score_matrix(P: xs.Panel) -> np.ndarray:
    """Ensemble z-vote: mean of four xs_zscored components (causal)."""
    # 1. Momentum 90d: higher = stronger recent trend
    mom90 = xs.past_return(P.close, 90)
    z_mom = xs.xs_zscore(mom90)

    # 2. Negative vol 30d: lower vol = more stable = prefer
    vol30 = xs.roll_std(P.ret, 30)
    z_vol = xs.xs_zscore(-vol30)   # negative: lower vol -> higher score

    # 3. Negative skew 60d: negative skew = avoid lottery/pump; prefer normal/negative-skew
    skew60 = xs.roll_skew(P.ret, 60)
    z_skew = xs.xs_zscore(-skew60)  # negative: lower skew -> higher score

    # 4. Negative beta 60d: low-beta assets have idiosyncratic edge in cross-section
    beta60 = xs.roll_beta(P.ret, 60)
    z_beta = xs.xs_zscore(-beta60)  # negative: lower beta -> higher score

    # Ensemble: simple mean across components (NaN-safe per cell)
    stack = np.stack([z_mom, z_vol, z_skew, z_beta], axis=0)
    score = np.nanmean(stack, axis=0)
    return score


# ── Grid (5 calls max) ──────────────────────────────────────────────────────
results = []

# 1. Majors, H=10, k=5, L/S
r = xs.study_xs("XS04b_maj_H10_k5_ls", score_matrix, universe="majors",
                H=10, k=5, long_short=True)
print(xs.fmt(r)); print("JSON:", xs.as_json(r))
results.append(r)

# 2. Majors, H=5, k=5, L/S  (faster rebalance)
r = xs.study_xs("XS04b_maj_H5_k5_ls", score_matrix, universe="majors",
                H=5, k=5, long_short=True)
print(xs.fmt(r)); print("JSON:", xs.as_json(r))
results.append(r)

# 3. All, H=10, k=5, L/S
r = xs.study_xs("XS04b_all_H10_k5_ls", score_matrix, universe="all",
                H=10, k=5, long_short=True)
print(xs.fmt(r)); print("JSON:", xs.as_json(r))
results.append(r)

# 4. All, H=10, k=7, L/S  (wider book)
r = xs.study_xs("XS04b_all_H10_k7_ls", score_matrix, universe="all",
                H=10, k=7, long_short=True)
print(xs.fmt(r)); print("JSON:", xs.as_json(r))
results.append(r)

# 5. Majors, H=10, k=5, long-only  (avoid short-side noise)
r = xs.study_xs("XS04b_maj_H10_k5_lo", score_matrix, universe="majors",
                H=10, k=5, long_short=False)
print(xs.fmt(r)); print("JSON:", xs.as_json(r))
results.append(r)

# ── Pick best by: earns_slot > holdout > corr_xs01 distance ─────────────────
def score_rep(r):
    es = 1 if r.get("earns_slot") else 0
    ho = r.get("holdout", {}).get("sharpe", -99)
    dist = 1 - abs(r.get("corr_xs01", 1))  # distinctness
    return (es, ho, dist)

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