"""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))