"""XS01b — Double-sort Momentum × Low-Vol Score = xs_zscore(past_return(close, 60)) + xs_zscore(-roll_std(ret, 30)) Combines cross-sectional momentum with low-vol preference (lower realized vol = higher score). Grid: universe x H x k variations, <=5 total backtests. """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec") import xslib as xs import numpy as np # --- score factory --- def score_mom_lowvol(mom_L=60, vol_win=30): """Double-sort: momentum z + low-vol z. Both causal (data <= close[i]).""" def _score(P): mom = xs.xs_zscore(xs.past_return(P.close, mom_L)) # low vol = higher score -> negate std lowvol = xs.xs_zscore(-xs.roll_std(P.ret, vol_win)) return mom + lowvol return _score # Grid (<=5 calls total): # 1. Baseline: majors H10 k5 LS (19 assets, closest to XS01 universe) # 2. All universe H10 k5 LS # 3. All universe H5 k5 LS (faster rebalance) # 4. Majors H10 k5 LS with longer mom window (90d) to differ from XS01 # 5. All universe H10 k7 LS (wider book) configs = [ dict(name="XS01b-MAJ-H10-k5", universe="majors", H=10, k=5, long_short=True, fn=score_mom_lowvol(60,30)), dict(name="XS01b-ALL-H10-k5", universe="all", H=10, k=5, long_short=True, fn=score_mom_lowvol(60,30)), dict(name="XS01b-ALL-H5-k5", universe="all", H=5, k=5, long_short=True, fn=score_mom_lowvol(60,30)), dict(name="XS01b-MAJ-H10-MOM90", universe="majors", H=10, k=5, long_short=True, fn=score_mom_lowvol(90,30)), dict(name="XS01b-ALL-H10-k7", universe="all", H=10, k=7, long_short=True, fn=score_mom_lowvol(60,30)), ] results = [] for cfg in configs: print(f"\nRunning {cfg['name']} ...") fn = cfg.pop("fn") rep = xs.study_xs(score_fn=fn, **cfg) results.append(rep) print(xs.fmt(rep)) print() # --- pick best: prefer earns_slot, then hold-out sharpe, then corr_xs01 < 0.6 def score_result(r): earns = 1 if r["earns_slot"] else 0 hold_sh = r["holdout"].get("sharpe", -99) full_sh = r["full"]["sharpe"] distinct = 1 if (r["corr_xs01"] or 1.0) < 0.6 else 0 return (earns, hold_sh, full_sh, distinct) best = max(results, key=score_result) print("\n" + "="*60) print("BEST CONFIG:") print(xs.fmt(best)) print("JSON:", xs.as_json(best))