"""XVa2 — Cross-sectional RSI reversal. Idea: compute RSI(14) per asset; score = -RSI so oversold assets go long (low RSI = long). This is a mean-reversion signal: buy the most oversold, short the most overbought. Grid (<=5 calls): 1. RSI(14) reversal, majors, H=10, k=5, LS 2. RSI(14) reversal, all, H=10, k=5, LS 3. RSI(14) reversal, all, H=5, k=5, LS (faster rebalance) 4. RSI(7) reversal, all, H=5, k=5, LS (shorter RSI period) 5. RSI(14) reversal, all, H=10, k=7, LS (wider basket) """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec") import xslib as xs import numpy as np sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt") import altlib as al def rsi_score(close: np.ndarray, win: int = 14) -> np.ndarray: """Compute -RSI(win) per asset column causally. Returns (n_days, n_assets) score matrix.""" n, A = close.shape out = np.full((n, A), np.nan) for a in range(A): out[:, a] = -al.rsi(close[:, a], win) return out results = [] # 1. RSI(14) on majors, H=10, k=5, LS rep1 = xs.study_xs( "XVa2-RSI14-majors-H10-k5", lambda P: rsi_score(P.close, 14), universe="majors", H=10, k=5, long_short=True ) print(xs.fmt(rep1)) results.append(rep1) # 2. RSI(14) on all, H=10, k=5, LS rep2 = xs.study_xs( "XVa2-RSI14-all-H10-k5", lambda P: rsi_score(P.close, 14), universe="all", H=10, k=5, long_short=True ) print(xs.fmt(rep2)) results.append(rep2) # 3. RSI(14) on all, H=5, k=5, LS (faster rebalance) rep3 = xs.study_xs( "XVa2-RSI14-all-H5-k5", lambda P: rsi_score(P.close, 14), universe="all", H=5, k=5, long_short=True ) print(xs.fmt(rep3)) results.append(rep3) # 4. RSI(7) on all, H=5, k=5, LS (shorter RSI) rep4 = xs.study_xs( "XVa2-RSI7-all-H5-k5", lambda P: rsi_score(P.close, 7), universe="all", H=5, k=5, long_short=True ) print(xs.fmt(rep4)) results.append(rep4) # 5. RSI(14) on all, H=10, k=7, LS (wider basket) rep5 = xs.study_xs( "XVa2-RSI14-all-H10-k7", lambda P: rsi_score(P.close, 14), universe="all", H=10, k=7, long_short=True ) print(xs.fmt(rep5)) results.append(rep5) # Pick best by: earns_slot first, then hold-out Sharpe, then distinctness from XS01 def score_rep(r): earns = 1 if r["earns_slot"] else 0 hold_sh = r["holdout"].get("sharpe", -999) or -999 xs01_corr = abs(r["corr_xs01"] or 1.0) full_sh = r["full"].get("sharpe", -999) or -999 return (earns, hold_sh, full_sh, -xs01_corr) best = max(results, key=score_rep) print("\n" + "=" * 60) print("BEST CONFIG:") print(xs.fmt(best)) print("JSON:", xs.as_json(best))