Files
foxhunt/scripts/surfer/crypto_stablecoin_intraday.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

121 lines
5.0 KiB
Python

#!/usr/bin/env python3
"""Stablecoin peg-reversion — INTRADAY EXECUTION validation (the make-or-break-on-fills test).
Daily test found short-rich reversion ~+25bp/SR4. This checks it survives REALISTIC intraday execution:
* 1h bars, REALISTIC fills: enter at NEXT bar's OPEN (reaction lag — you can't transact at the peak),
exit at the reverting bar's close; pay round-trip spread (calibrated from live book: ~1bp/side).
* deviation HALF-LIFE: of rich events at hour t, what % still rich at t+1/+2/+4/+8 (do you have time?).
* short-rich (validated, bounded) focus; long-cheap with skip-falling-knife for contrast.
Universe = live-tradeable liquid stables only (USDC/FDUSD/TUSD/USDP; DAI/BUSD books are empty).
KILL: edge gone after next-open fills + spread, OR deviations vanish within 1h (no reaction window).
"""
import math
import os
import numpy as np
OUT = "data/surfer/stable1h"
PAIRS = ["USDCUSDT", "FDUSDUSDT", "TUSDUSDT", "USDPUSDT"]
HOUR_MS = 3_600_000
THRESHES = [25, 50]
EXIT_BP = 10
MAX_HOLD_H = 48
SPREAD_BP = 1.0 # per side, from live book; round-trip = 2*SPREAD_BP
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["open"], d["close"]
g = lambda u: json.load(urllib.request.urlopen(urllib.request.Request(u, headers={"User-Agent": "curl/8"}), timeout=30))
start, end = int(time.time() * 1000) - 3 * 365 * 86_400_000, int(time.time() * 1000)
rows, cur = [], start
for _ in range(800):
k = g(f"https://api.binance.com/api/v3/klines?symbol={sym}&interval=1h&startTime={cur}&limit=1000")
if not k:
break
rows += k; cur = k[-1][0] + HOUR_MS
if len(k) < 1000 or cur >= end:
break
time.sleep(0.04)
if not rows:
return np.array([]), np.array([]), np.array([])
o = {int(r[0]): (float(r[1]), float(r[4])) for r in rows}
ts = np.array(sorted(o)); op = np.array([o[t][0] for t in ts]); cl = np.array([o[t][1] for t in ts])
os.makedirs(OUT, exist_ok=True); np.savez(cache, ts=ts, open=op, close=cl)
return ts, op, cl
def walk(op, cl, thresh, side, skip=False):
"""Non-overlapping. side='short'(rich) or 'long'(cheap). enter next-bar OPEN, exit revert close, pay spread."""
n = len(cl); d = cl - 1.0; th = thresh / 1e4; ex = EXIT_BP / 1e4; rt = 2 * SPREAD_BP / 1e4
out = []; i = 1
while i < n - 2:
rich = d[i] > th; cheap = d[i] < -th
take = (side == "short" and rich) or (side == "long" and cheap)
if not take:
i += 1; continue
if skip and side == "long" and cl[i] < cl[i - 1]: # don't catch a still-falling knife
i += 1; continue
direction = -1.0 if rich else 1.0
entry = op[i + 1] # realistic: fill at next bar open
j = i + 2
while j < n and (j - (i + 1)) <= MAX_HOLD_H:
if abs(cl[j] - 1.0) < ex:
break
j += 1
j = min(j, n - 1)
gross = direction * (cl[j] / entry - 1.0)
out.append((gross - rt) * 1e4)
i = j + 1
return np.array(out)
def half_life(cl, thresh):
d = cl - 1.0; th = thresh / 1e4
ev = np.where(np.abs(d) > th)[0]; ev = ev[ev < len(cl) - 9]
if len(ev) == 0:
return None
return {h: 100 * np.mean(np.abs(d[ev + h]) > th) for h in (1, 2, 4, 8)}
def st(p):
if len(p) < 3:
return f"n={len(p)} (few)"
sr = p.mean() / p.std() * math.sqrt(365 * 24 / (MAX_HOLD_H / 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 PAIRS:
ts, op, cl = fetch(s)
if len(ts) > 500:
data[s] = (op, cl, ts)
print("\n===== STABLECOIN INTRADAY EXECUTION (1h, next-open fills, spread 2bp rt) =====")
print(f"pairs: {', '.join(f'{s}({len(v[1])}h)' for s,v in data.items())}\n")
print("--- deviation HALF-LIFE: % of events still beyond threshold after H hours (reaction window) ---")
for s, (op, cl, ts) in data.items():
for th in THRESHES:
hl = half_life(cl, th)
if hl:
print(f" {s:>9} thr{th}: t+1={hl[1]:.0f}% t+2={hl[2]:.0f}% t+4={hl[4]:.0f}% t+8={hl[8]:.0f}%")
print()
for th in THRESHES:
sh = np.concatenate([walk(d[0], d[1], th, "short") for d in data.values()]) if data else np.array([])
lo = np.concatenate([walk(d[0], d[1], th, "long", skip=True) for d in data.values()]) if data else np.array([])
print(f"thr={th}bp SHORT-rich : {st(sh)}")
print(f" LONG-cheap*: {st(lo)} (*skip-falling-knife)")
# per-pair short
for s, (op, cl, ts) in data.items():
print(f" {s:>9} short: {st(walk(op, cl, th, 'short'))}")
print()
print("READ: edge survives if SHORT-rich stays +ve net of next-open+spread AND half-life>~2h (time to act).")
if __name__ == "__main__":
run()