"""XVa3 — Price-to-high value (mean reversion from recent highs). IDEA: Score = -(close / rolling_max(close, W)) Long the most beaten-down assets vs their rolling high (W=90). Negative sign: lower ratio (more beaten down) -> higher score -> long. CAUSAL: rolling_max at row i uses only data[i-W+1 .. i] (pandas rolling handles this). """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec") import xslib as xs import numpy as np def score_pth(close, W): """Price-to-high score: -(close / rolling_max(close, W)), causal.""" import pandas as pd df = pd.DataFrame(close) roll_max = df.rolling(W, min_periods=W // 2).max().values ratio = close / np.where(roll_max > 0, roll_max, np.nan) return -ratio # lower ratio (more beaten down) -> higher score -> long # --- Grid: 5 backtests total --- # Config 1: canonical W=90, H=10, k=5, long-short, all universe r1 = xs.study_xs( "XVa3-W90-H10-k5-LS-all", lambda P: score_pth(P.close, 90), universe="all", H=10, k=5, long_short=True, ) print(xs.fmt(r1)) print("JSON:", xs.as_json(r1)) print() # Config 2: W=60 (shorter lookback), H=10, k=5, long-short, all universe r2 = xs.study_xs( "XVa3-W60-H10-k5-LS-all", lambda P: score_pth(P.close, 60), universe="all", H=10, k=5, long_short=True, ) print(xs.fmt(r2)) print("JSON:", xs.as_json(r2)) print() # Config 3: W=90, H=5 (faster rebalance), k=5, long-short, all r3 = xs.study_xs( "XVa3-W90-H5-k5-LS-all", lambda P: score_pth(P.close, 90), universe="all", H=5, k=5, long_short=True, ) print(xs.fmt(r3)) print("JSON:", xs.as_json(r3)) print() # Config 4: W=90, H=10, k=5, majors only (more liquid) r4 = xs.study_xs( "XVa3-W90-H10-k5-LS-majors", lambda P: score_pth(P.close, 90), universe="majors", H=10, k=5, long_short=True, ) print(xs.fmt(r4)) print("JSON:", xs.as_json(r4)) print() # Config 5: W=120 (longer lookback), H=10, k=5, long-short, all r5 = xs.study_xs( "XVa3-W120-H10-k5-LS-all", lambda P: score_pth(P.close, 120), universe="all", H=10, k=5, long_short=True, ) print(xs.fmt(r5)) print("JSON:", xs.as_json(r5)) print() # --- Pick best config --- # Prefer: earns_slot first, then holdout sharpe, then distinctness results = [r1, r2, r3, r4, r5] earns = [r for r in results if r["earns_slot"]] if earns: best = max(earns, key=lambda r: r["holdout"].get("sharpe", -999)) else: # Fall back to positive full+hold, distinct from XS01 candidates = [r for r in results if r["full"]["sharpe"] > 0 and r["holdout"].get("sharpe", 0) > 0 and (r["corr_xs01"] or 1.0) < 0.6] if candidates: best = max(candidates, key=lambda r: r["holdout"].get("sharpe", -999)) else: best = max(results, key=lambda r: r["holdout"].get("sharpe", -999)) print("=== BEST CONFIG ===") print(xs.fmt(best)) print("JSON:", xs.as_json(best))