research(surfer): crypto edge-falsification probes — intraday/cascade dead, stablecoin peg-reversion validated

- crypto_mft_xsec / mft_*: intraday/MFT XS price edge FALSIFIED (fee-trap)
- crypto_cascade_reversion: liquidation-cascade reversion FALSIFIED (continuation)
- crypto_trend_sizing: TS-trend return-engine reconfirmed (Sharpe ~1.25)
- crypto_stablecoin_dislocation/harden/intraday: short-rich peg-reversion VALIDATED (bounded, uncorrelated); long-cheap = death-spiral trap

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-21 10:40:34 +02:00
parent 6878aac950
commit f4bd1e2432
9 changed files with 1180 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Crypto TS-trend sizing — reconfirm the validated return engine + find the deployable CAGR.
The TSMOM crypto sleeve is VALIDATED (memory: Sharpe ~1.23, CAGR 75.7% @ 61% vol, corr~0 to funding).
The 76% CAGR is just 61%-vol sizing, not a free lunch. This re-runs the SAME pre-registered config
(LONG/FLAT, lookbacks 20/60/120, on crypto_pit daily close, liquidity floor on qvol) and sweeps a
vol-target overlay to answer the #2 question: at a SANE vol target, what is the deployable CAGR and
does the Sharpe hold (it should — vol-target is a rescale, but per-period sizing adds timing).
NOT a search: the 20/60/120 long/flat config is replicated verbatim from the validated harness.
"""
import glob
import math
import numpy as np
PIT = "data/surfer/crypto_pit"
LOOKBACKS = [20, 60, 120]
COST_BP = 5.0
ADV_FLOOR = 5e6 # $5M trailing quote-volume liquidity floor
VOL_TARGETS = [0.10, 0.15, 0.20]
LEV_CAP = 3.0
PPY = 365 # crypto trades every day
def build():
days = set()
raw = {}
for f in sorted(glob.glob(f"{PIT}/*.npz")):
d = np.load(f)
if len(d["day"]) < 130:
continue
sym = f.split("/")[-1][:-4]
raw[sym] = {int(dd): (c, q) for dd, c, q in zip(d["day"], d["close"], d["qvol"])}
days.update(int(x) for x in d["day"])
days = sorted(days)
di = {d: i for i, d in enumerate(days)}
syms = sorted(raw)
C = np.full((len(days), len(syms)), np.nan)
V = np.full((len(days), len(syms)), np.nan)
for j, s in enumerate(syms):
for dd, (c, q) in raw[s].items():
C[di[dd], j] = c; V[di[dd], j] = q
return np.array(days), syms, C, V
def maxdd(equity):
peak = np.maximum.accumulate(equity)
return float((equity / peak - 1).min())
def run():
days, syms, C, V = build()
T, N = C.shape
logC = np.log(C)
ret = np.full((T, N), np.nan)
ret[1:] = C[1:] / C[:-1] - 1.0
# long/flat signal = mean over lookbacks of 1[trailing-L log return > 0]
sig = np.zeros((T, N))
cnt = np.zeros((T, N))
for L in LOOKBACKS:
tr = np.full((T, N), np.nan)
tr[L:] = logC[L:] - logC[:-L]
on = np.where(np.isfinite(tr), (tr > 0).astype(float), np.nan)
m = np.isfinite(on)
sig[m] += on[m]; cnt[m] += 1
sig = np.where(cnt > 0, sig / np.maximum(cnt, 1), 0.0) # 0..1 long/flat conviction
print(f"\n===== CRYPTO TS-TREND SIZING (long/flat {LOOKBACKS}, ${ADV_FLOOR/1e6:.0f}M ADV floor, {COST_BP}bp/leg) =====")
print(f"panel: {T} days x {N} coins (survivorship-free incl. dead)\n")
# base book (gross 1, daily rebalance, equal-weight by conviction among liquid+on coins)
w_prev = np.zeros(N)
book = np.zeros(T)
for t in range(1, T):
eligible = np.isfinite(ret[t]) & np.isfinite(V[t - 1]) & (V[t - 1] > ADV_FLOOR)
s = sig[t - 1] * eligible
g = s.sum()
w = s / g if g > 0 else np.zeros(N)
turn = np.abs(w - w_prev).sum()
book[t] = float(np.nansum(w * ret[t])) - turn * COST_BP / 1e4
w_prev = w
valid = np.arange(T) > max(LOOKBACKS)
base = book[valid]
base_sr = base.mean() / base.std() * math.sqrt(PPY) if base.std() > 0 else float("nan")
base_vol = base.std() * math.sqrt(PPY)
base_cagr = float(np.prod(1 + base) ** (PPY / len(base)) - 1)
print(f"BASE (un-vol-targeted): Sharpe {base_sr:+.2f} vol {base_vol*100:.0f}% CAGR {base_cagr*100:+.1f}% maxDD {maxdd(np.cumprod(1+base))*100:.1f}%")
print(f" (memory reference: Sharpe ~1.23, CAGR ~76% @ ~61% vol — replicating)\n")
print(f"{'vol_tgt':>8} {'Sharpe':>7} {'CAGR':>7} {'real_vol':>9} {'maxDD':>7} {'avg_lev':>8} {'per-year SR':>30}")
print("-" * 86)
# vol-target overlay on the base book (trailing 30d realized, lagged, leverage-capped)
rv = np.full(T, np.nan)
for t in range(31, T):
w = book[t - 30:t]
rv[t] = w.std() * math.sqrt(PPY)
yrs = 1970 + days // 365
for vt in VOL_TARGETS:
lev = np.where(np.isfinite(rv) & (rv > 0), np.clip(vt / rv, 0, LEV_CAP), 0.0)
tgt = book * np.concatenate([[0], lev[:-1]]) # lag leverage by 1 day
s = tgt[valid]
sr = s.mean() / s.std() * math.sqrt(PPY) if s.std() > 0 else float("nan")
vol = s.std() * math.sqrt(PPY)
cagr = float(np.prod(1 + s) ** (PPY / len(s)) - 1)
dd = maxdd(np.cumprod(1 + s))
avglev = float(np.mean(lev[valid]))
yv = {}
for r, y in zip(s, yrs[valid]):
yv.setdefault(int(y), []).append(r)
ystr = " ".join(f"{y}:{(np.mean(v)/(np.std(v)+1e-12)*math.sqrt(PPY)):+.1f}"
for y, v in sorted(yv.items()) if len(v) >= 30)
print(f"{vt*100:>6.0f}% {sr:>+7.2f} {cagr*100:>+6.1f}% {vol*100:>8.0f}% {dd*100:>+6.1f}% {avglev:>8.2f} {ystr}")
print("-" * 86)
print("Deployable read: pick the vol target whose maxDD you can stomach; Sharpe should ~hold across targets.")
if __name__ == "__main__":
run()