"""XM07 — Sharpe-rank momentum cross-sectional strategy. Score = roll_mean(ret, L) / roll_std(ret, L) (realized Sharpe ratio over L days) Rank assets cross-sectionally each H days, long top-k / short bottom-k. Grid: L in {30, 60, 90}, then vary universe/H/k around the best L. <=5 study_xs calls total. """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec") import xslib as xs import numpy as np def sharpe_score(P, L): """Causal realized Sharpe = roll_mean(ret, L) / roll_std(ret, L). Uses daily returns (P.ret). Higher = stronger risk-adjusted momentum -> long. """ mu = xs.roll_mean(P.ret, L) sigma = xs.roll_std(P.ret, L) # avoid division by near-zero vol; set to NaN if sigma too small score = mu / np.where(sigma > 1e-8, sigma, np.nan) return score # (n_days x n_assets), higher = long # ---- Grid (5 calls) -------------------------------------------------------- # Step 1: sweep L on "majors" universe with fixed H=10, k=5, long_short=True print("=" * 60) print("XM07 Sharpe-rank momentum — grid search") print("=" * 60) results = {} # Call 1: L=30, majors r1 = xs.study_xs("XM07_L30_majors", lambda P: sharpe_score(P, 30), universe="majors", H=10, k=5, long_short=True) print(xs.fmt(r1)) results["L30_majors"] = r1 # Call 2: L=60, majors r2 = xs.study_xs("XM07_L60_majors", lambda P: sharpe_score(P, 60), universe="majors", H=10, k=5, long_short=True) print(xs.fmt(r2)) results["L60_majors"] = r2 # Call 3: L=90, majors r3 = xs.study_xs("XM07_L90_majors", lambda P: sharpe_score(P, 90), universe="majors", H=10, k=5, long_short=True) print(xs.fmt(r3)) results["L90_majors"] = r3 # Pick best L by hold-out Sharpe among the 3 best_L_key = max(["L30_majors", "L60_majors", "L90_majors"], key=lambda k: results[k]["holdout"]["sharpe"]) best_L = int(best_L_key.split("_")[0][1:]) # extract integer print(f"\nBest L = {best_L} (by hold-out Sharpe)") # Call 4: best L on "all" universe (49 alts) to test breadth r4 = xs.study_xs(f"XM07_L{best_L}_all", lambda P: sharpe_score(P, best_L), universe="all", H=10, k=5, long_short=True) print(xs.fmt(r4)) results[f"L{best_L}_all"] = r4 # Call 5: best L on majors, try H=20 (less frequent rebalance, lower fee drag) r5 = xs.study_xs(f"XM07_L{best_L}_H20", lambda P: sharpe_score(P, best_L), universe="majors", H=20, k=5, long_short=True) print(xs.fmt(r5)) results[f"L{best_L}_H20"] = r5 # ---- Pick overall best config ----------------------------------------------- print("\n" + "=" * 60) print("SUMMARY — picking best config") print("=" * 60) def score_config(r): """Prefer: earns_slot, then hold-out, then full Sharpe, then distinctness.""" earns = int(r.get("earns_slot", False)) ho = r["holdout"]["sharpe"] full = r["full"]["sharpe"] dist = 1.0 - abs(r.get("corr_xs01", 1.0)) # higher = more distinct return (earns, ho, full, dist) best_key = max(results.keys(), key=lambda k: score_config(results[k])) best = results[best_key] print(f"Best config: {best_key}") print(xs.fmt(best)) print("JSON:", xs.as_json(best))