#!/usr/bin/env python3 """Stress-test the one survivor: crypto cross-sectional momentum. Three robustness probes on the broad-but-clean universe (funding-cov>0.90, >900d): 1. LOOKBACK SWEEP — is the edge smooth across 5..120d (real factor) or a single spike (overfit)? 2. COIN-BOOTSTRAP — resample WHICH coins are in the cross-section 200× (survivorship proxy): does momentum stay positive regardless of which coins survived? 3. VOL-SCALED — does risk-adjusting the momentum signal (ret/vol) help? Validation reused from the sweep (full/IS/OOS Sharpe, CPCV, Deflated Sharpe). """ import math import os import sys import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import crypto_sweep as cs # noqa: E402 from signal_sweep import xs_weights, pnl_w, validate, sharpe_t # noqa: E402 import torch # noqa: E402 DEV = "cuda" if torch.cuda.is_available() else "cpu" cs.MIN_FUNDCOV, cs.MIN_DAYS = 0.90, 900 def trailing(lc, L): out = np.full_like(lc, np.nan); out[L:] = lc[L:] - lc[:-L]; return out def main(): syms, days, close, fund = cs.load_crypto() T, N = close.shape lc = np.log(close) R = np.zeros((T, N)); R[1:] = lc[1:] - lc[:-1]; R = np.where(np.isfinite(R), R, 0.0) Reff = R - np.where(np.isfinite(fund), fund, 0.0) vol30 = np.full_like(lc, np.nan) for t in range(30, T): vol30[t] = np.nanstd(R[t - 30:t], axis=0) print(f"\n===== MOMENTUM ROBUSTNESS — {N} clean coins, {T}d =====") print("\n[1] LOOKBACK SWEEP (XS momentum, ~10bp cost)") print(f"{'lookback':>9} {'full':>6} {'IS':>6} {'OOS':>6} {'CPCVmed':>8} {'DSR':>5}") for L in [5, 10, 20, 30, 45, 60, 90, 120]: pnl = pnl_w(xs_weights(trailing(lc, L)), Reff, cost_bp=10) v = validate(pnl, days, 8) print(f"{L:>9} {v['full']:>+6.2f} {v['is_']:>+6.2f} {v['oos']:>+6.2f} {v['med']:>+8.2f} {v['dsr']:>5.2f}") print("\n[2] VOL-SCALED vs RAW (30d)") for nm, sg in [("raw_mom30", trailing(lc, 30)), ("volscaled_mom30", trailing(lc, 30) / np.where(vol30 > 0, vol30, np.nan))]: v = validate(pnl_w(xs_weights(sg), Reff, cost_bp=10), days, 8) print(f" {nm:>16}: full {v['full']:>+.2f} OOS {v['oos']:>+.2f} CPCVmed {v['med']:>+.2f}") print("\n[3] COIN-BOOTSTRAP — mom_30 on 200 random half-universes (survivorship proxy)") rng = np.random.default_rng(42) sig30 = trailing(lc, 30) sh = [] for _ in range(200): cols = rng.choice(N, size=max(N // 2, 10), replace=False) sub = np.full((T, N), np.nan); sub[:, cols] = sig30[:, cols] pnl = pnl_w(xs_weights(sub), Reff[:, :], cost_bp=10) # weights already zero outside cols s = sharpe_t(torch.tensor(pnl, device=DEV, dtype=torch.float64)) if not math.isnan(s): sh.append(s) sh = np.array(sh) print(f" draws={len(sh)} mean Sharpe {sh.mean():+.2f} median {np.median(sh):+.2f} " f"5th-pct {np.percentile(sh,5):+.2f} frac>0 {np.mean(sh>0):.2f}") print("\nVERDICT: smooth lookback curve + bootstrap frac>0 near 1.0 + 5th-pct>0 => robust real factor.") if __name__ == "__main__": main()