"""XL03 [LIQ] — Low-turnover anomaly. Score = -roll_mean(close * volume, 30) : long low dollar-volume names. Idea: low-liquidity assets carry a liquidity premium and may outperform high-liquidity names on a risk-adjusted basis. Grid (<=5 runs): 1. baseline: universe=all, H=10, k=5, long_short=True, win=30 2. shorter window win=10 (faster signal) 3. longer window win=60 (more stable ranking) 4. long-only version (long low-liq only, no shorting high-liq names) 5. majors universe (check if effect holds in liquid-only subspace) """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec") import xslib as xs import numpy as np # --- score factory ----------------------------------------------------------- def liq_score(P, win=30): """Score = -roll_mean(close * dollar_vol, win). CAUSAL: roll_mean at row i uses data[i-win+1..i]. Higher score = LOWER liquidity = LONG. """ dollar_vol = P.close * P.vol # (n, A) daily dollar volume avg_dvol = xs.roll_mean(dollar_vol, win) # rolling mean, causal return -avg_dvol # negate: lower dvol -> higher score -> long # --- grid ------------------------------------------------------------------- print("=" * 70) print("XL03 [LIQ] Low-turnover anomaly — grid search") print("=" * 70) results = [] # Run 1: baseline (all, H=10, k=5, LS, win=30) r1 = xs.study_xs("XL03-w30-all-LS", lambda P: liq_score(P, 30), universe="all", H=10, k=5, long_short=True) print(xs.fmt(r1)) print("JSON:", xs.as_json(r1)) results.append(r1) # Run 2: shorter window win=10 r2 = xs.study_xs("XL03-w10-all-LS", lambda P: liq_score(P, 10), universe="all", H=10, k=5, long_short=True) print(xs.fmt(r2)) print("JSON:", xs.as_json(r2)) results.append(r2) # Run 3: longer window win=60 r3 = xs.study_xs("XL03-w60-all-LS", lambda P: liq_score(P, 60), universe="all", H=10, k=5, long_short=True) print(xs.fmt(r3)) print("JSON:", xs.as_json(r3)) results.append(r3) # Run 4: long-only (long low-liq, no short) r4 = xs.study_xs("XL03-w30-all-LO", lambda P: liq_score(P, 30), universe="all", H=10, k=5, long_short=False) print(xs.fmt(r4)) print("JSON:", xs.as_json(r4)) results.append(r4) # Run 5: majors universe only r5 = xs.study_xs("XL03-w30-majors-LS", lambda P: liq_score(P, 30), universe="majors", H=10, k=5, long_short=True) print(xs.fmt(r5)) print("JSON:", xs.as_json(r5)) results.append(r5) # --- pick best config ------------------------------------------------------- print("\n" + "=" * 70) print("SUMMARY — best by: earns_slot first, then hold-out Sharpe, then corr_xs01 < 0.6") print("=" * 70) def rank_key(r): earns = int(r["earns_slot"]) hold_sh = r["holdout"].get("sharpe", -9) or -9 xs01_corr = abs(r.get("corr_xs01") or 1.0) return (earns, hold_sh, -xs01_corr) best = max(results, key=rank_key) print(f"\nBEST CONFIG: {best['name']}") print(xs.fmt(best)) print("\nJSON (best):", xs.as_json(best))