From ca3877c3288a6bc9486f4d1317cffc128ace30c3 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Sat, 6 Jun 2026 16:07:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(surfer):=20execution-realism=20stress=20?= =?UTF-8?q?=E2=80=94=20honest=20momentum=20magnitude=20~0.4-0.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Realism layers on PIT mom_20 TOPK=50: robust to 20bp (+0.59) and liquidity floor (top-50 already deep, no effect); death-spiral exclusion +0.60. Winsorize +/-20%/day cuts to +0.50 and weakens 2021/22 -> edge leans partly on large moves. COMBINED realistic stress +0.18 (CPCV~0); HARSH negative. winsor likely over-conservative -> true deployable Sharpe ~0.4-0.6, not the 0.76 headline. Real but modest edge; recent years strongest. Deploy needs signal-robustness refinement + diversifying 2nd edge. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/surfer/pit_realism.py | 87 +++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 scripts/surfer/pit_realism.py diff --git a/scripts/surfer/pit_realism.py b/scripts/surfer/pit_realism.py new file mode 100644 index 000000000..feda23a62 --- /dev/null +++ b/scripts/surfer/pit_realism.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Execution-realism stress for point-in-time crypto momentum — is the magnitude believable? + +Layers on the realistic frictions that could inflate the paper Sharpe: + - cost 10→20→30 bp on turnover (smaller coins cost more) + - WINSORIZE per-coin daily returns to ±cap (you cannot fill a short into an 80%/day collapse) + - EXCLUDE each coin's final K days (delisting death-spiral: untradeable → zero PnL there) + - liquidity FLOOR: only trade coins with trailing dollar-vol > floor +If mom_20/30 stays positive EVERY year under the combined stress, the edge magnitude is real. +""" +import math +import os +import sys + +import numpy as np + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import pit_sweep as ps # 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" + + +def trailing(lc, L): + out = np.full_like(lc, np.nan); out[L:] = lc[L:] - lc[:-L]; return out + + +def main(): + syms, days, close, qv, fund = ps.load() + 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) + fund = np.where(np.isfinite(fund), fund, 0.0) + qv = np.nan_to_num(qv) + dv30 = np.zeros((T, N)) + for t in range(30, T): + dv30[t] = qv[t - 30:t].mean(axis=0) + year = (1970 + days / 365.25).astype(int) + + # death-spiral mask: each coin's final 5 valid days = untradeable + tradeable = np.ones((T, N), bool) + for j in range(N): + idx = np.where(np.isfinite(close[:, j]))[0] + if len(idx): + tradeable[max(0, idx[-1] - 4):idx[-1] + 1, j] = False + + def variant(cost_bp, winsor, excl_death, dv_floor, L=20, TOPK=50): + Reff = (R - fund) + if winsor is not None: + Reff = np.clip(Reff, -winsor, winsor) + if excl_death: + Reff = np.where(tradeable, Reff, 0.0) + univ = np.zeros((T, N), bool) + for t in range(T): + elig = np.where((dv30[t] > dv_floor) & np.isfinite(close[t]))[0] + if len(elig) == 0: + continue + k = min(TOPK, len(elig)) + univ[t, elig[np.argsort(-dv30[t, elig])[:k]]] = True + sig = trailing(lc, L).copy(); sig[~univ] = np.nan + pnl = pnl_w(xs_weights(sig), Reff, cost_bp=cost_bp) + v = validate(pnl, days, 6) + py = [sharpe_t(torch.tensor(pnl[year[1:] == y], device=DEV, dtype=torch.float64)) + if (year[1:] == y).sum() > 30 else float("nan") for y in range(2020, 2027)] + return v, py + + print(f"\n===== EXECUTION-REALISM STRESS — PIT momentum (mom_20, TOPK=50) — {N} coins =====") + print(f"{'variant':>34} {'full':>6} {'IS':>6} {'OOS':>6} {'CPCVmed':>8} | per-year 2020..2026") + cfgs = [ + ("baseline 10bp", dict(cost_bp=10, winsor=None, excl_death=False, dv_floor=0)), + ("20bp cost", dict(cost_bp=20, winsor=None, excl_death=False, dv_floor=0)), + ("winsor ±20%", dict(cost_bp=10, winsor=0.20, excl_death=False, dv_floor=0)), + ("excl death-spiral (last 5d)", dict(cost_bp=10, winsor=None, excl_death=True, dv_floor=0)), + ("dv-floor $5M/day", dict(cost_bp=10, winsor=None, excl_death=False, dv_floor=5e6)), + ("COMBINED 20bp+wins20+death+$5M", dict(cost_bp=20, winsor=0.20, excl_death=True, dv_floor=5e6)), + ("HARSH 30bp+wins10+death+$10M", dict(cost_bp=30, winsor=0.10, excl_death=True, dv_floor=1e7)), + ] + for nm, cfg in cfgs: + v, py = variant(**cfg) + pys = " ".join(f"{p:>+5.2f}" if not math.isnan(p) else " n/a" for p in py) + print(f"{nm:>34} {v['full']:>+6.2f} {v['is_']:>+6.2f} {v['oos']:>+6.2f} {v['med']:>+8.2f} | {pys}") + print("\nVERDICT: positive every year under COMBINED/HARSH => believable, sizable edge (not paper).") + + +if __name__ == "__main__": + main()