feat(crypto): delta-neutral funding harvest — the one edge that breaks 0.7

Investigated crypto funding carry (researchers' flagged retail path to >1 Sharpe). DELTA-NEUTRAL
cash-and-carry (short perp+long spot, collect funding, price cancels) -- different from the
directional carry trap. Funding positive 75pct of time. Backtest net of cost: APR 9.9pct, raw
Sharpe +5.68, maxDD -7.4pct, survives to 20bp cost, worst month -1.5pct (delta-neutral held).
Discipline finds: (1) real; (2) 5.68 inflated (funding-only model, 1.8pct vol; real basis vol
-> honest ~2-3 Sharpe = literature's 1.5-2.5); (3) regime-dependent + DECAYING (negative 2022,
2025, 2026). Caveats: counterparty/exchange tail (FTX -100pct, not in backtest = real killer),
decay, operationally real. The ONE genuine path past the 0.7 ceiling -- but it's crypto +
counterparty tail is the price of admission. The journey converges: higher Sharpe exists,
reachable, but lives where the user didn't want to go with a tail the Sharpe doesn't show.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-07 01:16:46 +02:00
parent 0fc3eac2ab
commit c10ebe0257

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""Delta-neutral crypto funding HARVEST (cash-and-carry) — the one retail path to higher Sharpe.
When perp funding > 0 (longs pay shorts), go short-perp + long-spot (DELTA-NEUTRAL: price cancels)
and collect the funding rate as carry. No price prediction. Causal: position[t] from funding[t-1]
(funding is persistent), collect funding[t]. Net of realistic round-trip cost. Stress: worst months
(crashes flip funding negative). Variants: broad vs majors, threshold, cost sensitivity.
Treats `fund` as per-day funding (conservative; if per-8h the real APR/Sharpe is higher).
"""
import math
import os
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import pit_sweep # noqa: E402
def metrics(r):
r = np.asarray(r); r = r[np.isfinite(r)]
if len(r) < 50 or r.std() == 0:
return (float("nan"),) * 4
ann = r.mean() * 365; vol = r.std() * math.sqrt(365)
eq = np.cumprod(1 + r); dd = float((eq / np.maximum.accumulate(eq) - 1).min())
return ann, vol, ann / vol, dd
def harvest(fund, liq, thr, c_round, year, label, topk=None):
T, N = fund.shape
elig = liq & np.isfinite(fund)
sig = np.zeros((T, N))
sig[1:] = np.where(elig[1:] & (fund[:-1] > thr), 1.0, 0.0) # causal: position from yesterday's funding
if topk: # restrict to top-K best-funded eligible
for t in range(1, T):
idx = np.where(sig[t] > 0)[0]
if len(idx) > topk:
keep = idx[np.argsort(-fund[t - 1, idx])[:topk]]
m = np.zeros(N); m[keep] = 1.0; sig[t] = m
w = sig / np.maximum(sig.sum(1, keepdims=True), 1) # equal-weight positioned
f = np.nan_to_num(fund)
gross = np.sum(w[:-1] * f[1:], axis=1)
turn = np.sum(np.abs(w[1:] - w[:-1]), axis=1) # fraction of book traded
net = gross - turn * (c_round / 2) # one-way cost each side
ann, vol, sr, dd = metrics(net)
npos = sig.sum(1); avgpos = npos[npos > 0].mean()
print(f"{label:>26} {100*ann:>6.1f} {100*vol:>6.1f} {sr:>+7.2f} {100*dd:>+7.1f} {avgpos:>6.0f} {100*turn.mean():>6.1f}")
return net
def main():
syms, days, close, qv, fund = pit_sweep.load()
T, N = fund.shape
year = (1970 + days / 365.25).astype(int)
qv30 = np.full_like(qv, np.nan)
for t in range(30, T):
qv30[t] = np.nanmean(qv[t - 30:t], axis=0)
liq = np.isfinite(qv30) & (qv30 > 5e6) # >$5M/day quote volume
majors = np.array([s in ("BTCUSDT", "ETHUSDT", "BNBUSDT", "SOLUSDT", "XRPUSDT", "ADAUSDT",
"DOGEUSDT", "AVAXUSDT", "LINKUSDT", "MATICUSDT") for s in syms])
liq_maj = liq & majors[None, :]
print(f"\n===== CRYPTO FUNDING HARVEST (delta-neutral, {N} coins, {T} days) =====")
print(f"{'variant':>26} {'APR%':>6} {'vol%':>6} {'Sharpe':>7} {'maxDD%':>7} {'#pos':>6} {'turn%':>6}")
base = harvest(fund, liq, 0.0, 0.0010, year, "broad, thr=0, 10bp")
harvest(fund, liq, 0.0003, 0.0010, year, "broad, thr=3bp, 10bp")
harvest(fund, liq, 0.0, 0.0010, year, "broad top-20, 10bp", topk=20)
harvest(fund, liq_maj, 0.0, 0.0010, year, "majors only, 10bp")
print(" -- cost sensitivity (broad, thr=0) --")
harvest(fund, liq, 0.0, 0.0005, year, "broad, 5bp")
harvest(fund, liq, 0.0, 0.0020, year, "broad, 20bp")
harvest(fund, liq, 0.0, 0.0040, year, "broad, 40bp (harsh)")
# per-year + worst-month stress on the base case
print("\n per-year Sharpe (broad, thr=0, 10bp):")
print(" " + " ".join(f"{y}:{metrics(base[year[1:]==y])[2]:+.1f}" for y in range(2019, 2027) if (year[1:] == y).sum() > 60))
# monthly buckets
mo = (days[1:] / 30.4).astype(int)
worst = sorted(set(mo), key=lambda m: base[mo == m].sum())[:5]
print(" worst 5 ~monthly buckets (crash stress — does delta-neutral hold?):")
for m in worst:
seg = base[mo == m]
d0 = int(days[1:][mo == m][0])
import datetime
print(f" ~{datetime.date.fromordinal(d0+719163)}: {100*seg.sum():+.1f}% over {len(seg)}d")
print("\nVERDICT: if net Sharpe >> 1 AND survives crash months (delta-neutral so price-neutral) = the genuine")
print("retail higher-Sharpe edge. Watch: turnover cost (funding flips), and whether tail months go deeply negative.")
if __name__ == "__main__":
main()