"""XD01 — Low-skew / anti-lottery cross-sectional strategy. Score = -roll_skew(ret, 60): short high-skew "lottery" alts, long low-skew alts. Rationale: lottery-preference premium — investors overpay for positive-skew assets (right-tail lottery tickets), so they should earn lower returns; negative-skew assets are underpriced relative to their systematic risk. Grid (<=5 calls): 1. Baseline: "majors" (19 XS01 universe), H=10, k=5, L/S 2. Wider universe: "all" (~49 alts), H=10, k=5, L/S 3. Vary rebalance period: "all", H=5, k=5, L/S (more frequent) 4. Vary top-k: "all", H=10, k=7, L/S (more diversified) 5. Combined: -skew60 + -skew30 blend (multi-horizon), "all", H=10, k=5, L/S """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec") import xslib as xs import numpy as np SKEW_WIN = 60 # lookback for rolling skew (days) SKEW_WIN2 = 30 # shorter lookback for blend def score_anti_lottery(P, win=SKEW_WIN): """Anti-lottery score: negate rolling skew so LOW-skew assets score HIGH (long).""" sk = xs.roll_skew(P.ret, win) # (n_days x n_assets); higher skew = lottery return -sk # higher = lower skew = long def score_anti_lottery_blend(P, w1=SKEW_WIN, w2=SKEW_WIN2): """Multi-horizon blend of negated skews (cross-sectionally z-scored before blend).""" sk1 = xs.xs_zscore(-xs.roll_skew(P.ret, w1)) sk2 = xs.xs_zscore(-xs.roll_skew(P.ret, w2)) return 0.5 * sk1 + 0.5 * sk2 if __name__ == "__main__": results = [] # --- Run 1: majors universe, H=10, k=5, L/S --- print("Running XD01-v1: majors, H=10, k=5, L/S ...") rep1 = xs.study_xs( "XD01-v1-majors", lambda P: score_anti_lottery(P, 60), universe="majors", H=10, k=5, long_short=True, ) print(xs.fmt(rep1)) results.append(rep1) # --- Run 2: all universe, H=10, k=5, L/S --- print("\nRunning XD01-v2: all, H=10, k=5, L/S ...") rep2 = xs.study_xs( "XD01-v2-all", lambda P: score_anti_lottery(P, 60), universe="all", H=10, k=5, long_short=True, ) print(xs.fmt(rep2)) results.append(rep2) # --- Run 3: all, H=5 (more frequent rebalance), k=5, L/S --- print("\nRunning XD01-v3: all, H=5, k=5, L/S ...") rep3 = xs.study_xs( "XD01-v3-H5", lambda P: score_anti_lottery(P, 60), universe="all", H=5, k=5, long_short=True, ) print(xs.fmt(rep3)) results.append(rep3) # --- Run 4: all, H=10, k=7, L/S (more diversified) --- print("\nRunning XD01-v4: all, H=10, k=7, L/S ...") rep4 = xs.study_xs( "XD01-v4-k7", lambda P: score_anti_lottery(P, 60), universe="all", H=10, k=7, long_short=True, ) print(xs.fmt(rep4)) results.append(rep4) # --- Run 5: blend multi-horizon skew, all, H=10, k=5, L/S --- print("\nRunning XD01-v5: blend skew30+60, all, H=10, k=5, L/S ...") rep5 = xs.study_xs( "XD01-v5-blend", lambda P: score_anti_lottery_blend(P, 60, 30), universe="all", H=10, k=5, long_short=True, ) print(xs.fmt(rep5)) results.append(rep5) # --- Pick best config by: earns_slot > holdout sharpe > full sharpe > distinctness --- def rank_key(r): earns = int(r["earns_slot"]) h_sh = r["holdout"].get("sharpe", -99) f_sh = r["full"]["sharpe"] distinct = 1.0 - abs(r["corr_xs01"] or 1.0) # higher = more distinct verdict_score = {"ADDS": 3, "NEUTRAL": 2, "DILUTES": 1, "REDUNDANT": 0, "N/A": 0}.get( r["marginal"].get("verdict", "N/A"), 0) return (earns, verdict_score, h_sh, f_sh, distinct) best = max(results, key=rank_key) print("\n" + "=" * 60) print("BEST CONFIG:") print(xs.fmt(best)) print("JSON:", xs.as_json(best))