Files
foxhunt/scripts/surfer/crypto_stablecoin_harden.py
jgrusewski f4bd1e2432 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>
2026-06-21 10:40:34 +02:00

146 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""Stablecoin peg-reversion — HARDENED. Make-or-break test of the crypto-FX dislocation edge.
The spike (crypto_stablecoin_dislocation.py) found a real reversion edge (SR 3-4) but was blind to:
(1) survivorship — all sampled stables reverted; the true tail is buying a cheap stable that goes to
ZERO (UST -75%->delist). (2) overlap inflation. (3) stress slippage. (4) long/short asymmetry.
This hardens all four:
* UST included (the real death-spiral). FRAX excluded (garbage prints). USTC excluded (post-death).
* NON-OVERLAPPING event walk with explicit exit (revert / max_hold / stop-loss).
* deviation-SCALED slippage: cost_rt = base_bp + slip_k*|deviation| (big depeg = thin book).
* LONG-cheap (unbounded -100% tail) vs SHORT-rich (bounded) decomposed separately.
* collapse filters compared: raw / stop-loss / skip-falling-knife(+stop).
* per-year sign + BTC-stress correlation proxy (does it lose when crypto crashes?).
KILL: long-side net<=0 after UST+filters, OR edge only pre-cost, OR loses concentrated in BTC crashes.
"""
import math
import os
import numpy as np
OUT = "data/surfer/stablecoin"
HEALTHY = ["USDCUSDT", "FDUSDUSDT", "TUSDUSDT", "BUSDUSDT", "USDPUSDT", "SUSDUSDT", "DAIUSDT"]
TERMINAL = ["USTUSDT"]
DAY_MS = 86_400_000
THRESH_BP = 50
EXIT_BP = 10
MAX_HOLD = 5
BASE_BP = 2.0
SLIP_K = 0.15 # slippage = 15% of the entry deviation (stress thinness)
STOP = 0.03 # 3% stop-loss
def fetch(sym):
import json, time, urllib.request
cache = f"{OUT}/{sym}.npz"
if os.path.exists(cache):
d = np.load(cache); return d["ts"], d["close"]
g = lambda u: json.load(urllib.request.urlopen(urllib.request.Request(u, headers={"User-Agent": "curl/8"}), timeout=30))
start, end = 1_546_300_800_000, int(time.time() * 1000)
rows, cur = [], start
for _ in range(400):
k = g(f"https://api.binance.com/api/v3/klines?symbol={sym}&interval=1d&startTime={cur}&limit=1000")
if not k:
break
rows += k; cur = k[-1][0] + DAY_MS
if len(k) < 1000 or cur >= end:
break
time.sleep(0.05)
if not rows:
return np.array([]), np.array([])
d = {int(r[0]): float(r[4]) for r in rows}
ts = np.array(sorted(d)); close = np.array([d[t] for t in ts])
os.makedirs(OUT, exist_ok=True); np.savez(cache, ts=ts, close=close)
return ts, close
def walk(ts, close, mode):
"""Non-overlapping reversion trades. mode: 'raw'|'stop'|'skip'. Returns list of (dir, year, net_bp, entry_dev_bp)."""
n = len(close); d = close - 1.0; th = THRESH_BP / 1e4; ex = EXIT_BP / 1e4
trades = []; i = 1
while i < n - 1:
if abs(d[i]) <= th:
i += 1; continue
direction = -1.0 if d[i] > 0 else 1.0 # rich->short(-1), cheap->long(+1)
# skip-falling-knife: only buy a cheap stable if it is NOT still dropping (bounce confirmation)
if mode == "skip" and direction > 0 and close[i] < close[i - 1]:
i += 1; continue
entry = close[i]; j = i + 1; stop = STOP if mode in ("stop", "skip") else 9.9
while j < n and (j - i) <= MAX_HOLD:
pnl = direction * (close[j] / entry - 1.0)
if pnl <= -stop or abs(close[j] - 1.0) < ex:
break
j += 1
j = min(j, n - 1)
gross = direction * (close[j] / entry - 1.0)
slip = (BASE_BP + SLIP_K * abs(d[i]) * 1e4) / 1e4
yr = 1970 + int(ts[i]) // (365 * DAY_MS)
trades.append((direction, yr, (gross - slip) * 1e4, abs(d[i]) * 1e4))
i = j + 1
return trades
def stats(pnls):
p = np.array(pnls)
if len(p) < 3:
return f"n={len(p)} (too few)"
sr = p.mean() / p.std() * math.sqrt(365 / ((MAX_HOLD + 1) / 2)) if p.std() > 0 else float("nan")
return f"n={len(p):>4} mean={p.mean():>+7.1f}bp hit={100*np.mean(p>0):>4.1f}% worst={p.min():>+7.0f}bp SR~{sr:>+5.2f}"
def run():
data = {}
for s in HEALTHY + TERMINAL:
ts, c = fetch(s)
if len(ts) > 30:
data[s] = (ts, c)
print("\n===== STABLECOIN PEG-REVERSION — HARDENED (non-overlap, UST-in, slip-scaled, long/short split) =====")
print(f"pairs: {', '.join(data)} thr={THRESH_BP}bp hold<={MAX_HOLD}d exit<{EXIT_BP}bp slip={BASE_BP}+{SLIP_K}*|dev| stop={STOP*100:.0f}%\n")
for mode in ("raw", "stop", "skip"):
allt = []
for s in data:
allt += [(s, *t) for t in walk(*data[s], mode)]
longs = [t[3] for t in allt if t[1] > 0]
shorts = [t[3] for t in allt if t[1] < 0]
ust = [t[3] for t in allt if t[0] == "USTUSDT"]
print(f"[{mode:>4}] ALL : {stats([t[3] for t in allt])}")
print(f" LONG(cheap, -100% tail): {stats(longs)}")
print(f" SHORT(rich, bounded) : {stats(shorts)}")
print(f" UST trades only : {stats(ust)}")
print()
# per-year (SHORT-only, skip mode = the candidate deployable core)
print("--- per-year SHORT-rich net mean bp (the bounded-risk core), skip mode ---")
by_yr = {}
for s in data:
for t in walk(*data[s], "skip"):
if t[0] < 0:
by_yr.setdefault(t[1], []).append(t[2])
print(" ".join(f"{y}:{np.mean(v):+.0f}({len(v)})" for y, v in sorted(by_yr.items()) if v))
# BTC-stress proxy: does the LONG side lose during BTC crashes?
bts, btc = fetch("BTCUSDT")
if len(btc) > 100:
bret = {int(bts[k]) // DAY_MS: (btc[k] / btc[k - 1] - 1.0) for k in range(1, len(btc))}
crash, calm = [], []
for s in data:
ts, c = data[s]
for dirn, yr, net, dev in walk(ts, c, "skip"):
pass
# simpler: tag each long trade entry day with BTC 1d return
for s in data:
ts, c = data[s]; d = c - 1.0; th = THRESH_BP / 1e4
for i in range(1, len(c) - 1):
if abs(d[i]) > th and d[i] < 0: # long-cheap entries
br = bret.get(int(ts[i]) // DAY_MS, 0.0)
(crash if br < -0.05 else calm).append(1)
print(f"\nLONG-cheap entries on BTC-crash days (>-5%): {len(crash)} vs calm: {len(calm)} "
f"({100*len(crash)/max(1,len(crash)+len(calm)):.0f}% in crashes = tail-correlation flag)")
print("\nREAD: deployable core = SHORT-rich (bounded). LONG-cheap viable only if filters tame the UST tail.")
if __name__ == "__main__":
run()