"""XR05 — Overreaction Reversal (mid-horizon) IDEA: Score = -past_return(close, L) for L in {20, 30}. Assets that ran up the most over the past 20-30 days are SHORTED (expected to mean-revert); assets that dropped the most are LONGED. Pure cross-sectional contrarian on multi-week moves. Grid (<= 5 calls): 1. L=20, H=10, k=5, LS, universe=majors 2. L=30, H=10, k=5, LS, universe=majors 3. L=20, H=5, k=5, LS, universe=majors (faster rebal) 4. blend -PR20 and -PR30 (mean z-score), H=10, k=5, LS, universe=majors 5. blend -PR20 and -PR30, H=10, k=5, LS, universe=all (broader universe) """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec") import xslib as xs import numpy as np # ------------------------------------------------------------------ # Score helpers (causal: close[i] only uses data up to bar i) # ------------------------------------------------------------------ def score_rev20(P): return -xs.past_return(P.close, 20) def score_rev30(P): return -xs.past_return(P.close, 30) def score_rev20_fast(P): return -xs.past_return(P.close, 20) def score_blend_majors(P): z20 = xs.xs_zscore(-xs.past_return(P.close, 20)) z30 = xs.xs_zscore(-xs.past_return(P.close, 30)) return (z20 + z30) / 2.0 def score_blend_all(P): z20 = xs.xs_zscore(-xs.past_return(P.close, 20)) z30 = xs.xs_zscore(-xs.past_return(P.close, 30)) return (z20 + z30) / 2.0 # ------------------------------------------------------------------ # Grid # ------------------------------------------------------------------ configs = [ dict(name="XR05-REV20-H10-k5-majors", fn=score_rev20, universe="majors", H=10, k=5, long_short=True), dict(name="XR05-REV30-H10-k5-majors", fn=score_rev30, universe="majors", H=10, k=5, long_short=True), dict(name="XR05-REV20-H5-k5-majors", fn=score_rev20_fast, universe="majors", H=5, k=5, long_short=True), dict(name="XR05-BLENDz-H10-k5-majors", fn=score_blend_majors, universe="majors", H=10, k=5, long_short=True), dict(name="XR05-BLENDz-H10-k5-all", fn=score_blend_all, universe="all", H=10, k=5, long_short=True), ] results = [] for c in configs: print(f"\nRunning {c['name']} ...") rep = xs.study_xs( c["name"], c["fn"], universe=c["universe"], H=c["H"], k=c["k"], long_short=c["long_short"], ) print(xs.fmt(rep)) results.append(rep) # ------------------------------------------------------------------ # Pick best config: earns_slot first, then hold-out sharpe, then distinctness # ------------------------------------------------------------------ def _sort_key(r): earns = int(r["earns_slot"]) hold_sh = r["holdout"].get("sharpe", -99) corr_xs01 = abs(r["corr_xs01"] or 1.0) return (earns, hold_sh, -corr_xs01) best = max(results, key=_sort_key) print("\n" + "="*60) print("BEST CONFIG:") print(xs.fmt(best)) print("JSON:", xs.as_json(best))