Backtested the cross-venue funding arb on historical funding (Binance/OKX/Hyperliquid). Gross +21%/yr, spreads persist (capture 0.71), but NAIVE daily rebalance is cost-killed (net Sharpe -4.5, negative every month). HYSTERESIS (hold winners until spread decays, entry>10bp/exit>5bp) flips net to +10-14%/yr market-neutral (Sharpe +11-15, but inflated by ~1% vol + idealized fills; realistic ~3-6 / return ~10-14%). Turnover is the swing factor. First edge of the whole search to survive the net-of-cost horde -- market-neutral, persistent, no spot leg (solves hedgeability), operational not predictive. Caveats: 94d/one period, idealized fills, counterparty. Next: switch live harness to hysteresis, deepen history to full year, micro-live. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80 lines
3.3 KiB
Python
80 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Low-turnover rescue test: gross cross-venue spread is +21% with 0.71 persistence, but naive daily
|
|
rebalancing is cost-killed. Test if HYSTERESIS (hold a coin until its spread decays below an exit
|
|
floor) + lower maker-fee cost flips net positive. If yes -> real but needs patient execution; if no
|
|
-> cost-walled."""
|
|
import json
|
|
import math
|
|
|
|
import numpy as np
|
|
|
|
panel = json.load(open("data/surfer/xvenue2/panel2.json"))
|
|
dates = sorted(set().union(*[set(v) for v in panel.values()]))
|
|
di = {d: i for i, d in enumerate(dates)}
|
|
coins = list(panel)
|
|
T, N = len(dates), len(coins)
|
|
spread = np.full((T, N), np.nan)
|
|
for j, c in enumerate(coins):
|
|
for d, v in panel[c].items():
|
|
vals = list(v.values())
|
|
if len(vals) >= 2:
|
|
spread[di[d], j] = max(vals) - min(vals)
|
|
|
|
|
|
def sim(K, entry, exit_, cost):
|
|
held = {} # col -> True
|
|
rets = []
|
|
for t in range(T - 1):
|
|
s_t, s_n = spread[t], spread[t + 1]
|
|
# drop held coins whose spread decayed below exit (or data gone)
|
|
held = {j: 1 for j in held if np.isfinite(s_t[j]) and s_t[j] > exit_}
|
|
# fill up to K from fresh entries above entry hurdle
|
|
if len(held) < K:
|
|
cand = [j for j in np.where(np.isfinite(s_t) & (s_t > entry))[0] if j not in held]
|
|
cand.sort(key=lambda j: -s_t[j])
|
|
for j in cand[:K - len(held)]:
|
|
held[j] = 1
|
|
if not held:
|
|
rets.append(0.0); continue
|
|
w = 1.0 / len(held)
|
|
realized = sum(w * s_n[j] for j in held if np.isfinite(s_n[j]))
|
|
# turnover: this sim only changes the set on entry/exit crossings (low churn)
|
|
rets.append(realized)
|
|
sim._prev = set(held)
|
|
return np.array(rets)
|
|
|
|
|
|
def turnover_cost(K, entry, exit_, cost):
|
|
"""Re-run tracking set changes to charge cost properly."""
|
|
held, rets, prev = {}, [], set()
|
|
for t in range(T - 1):
|
|
s_t, s_n = spread[t], spread[t + 1]
|
|
held = {j: 1 for j in held if np.isfinite(s_t[j]) and s_t[j] > exit_}
|
|
if len(held) < K:
|
|
cand = [j for j in np.where(np.isfinite(s_t) & (s_t > entry))[0] if j not in held]
|
|
cand.sort(key=lambda j: -s_t[j])
|
|
for j in cand[:K - len(held)]:
|
|
held[j] = 1
|
|
cur = set(held)
|
|
turn = len(cur ^ prev) / max(len(cur), 1) if cur else 0
|
|
realized = (sum(s_n[j] for j in held if np.isfinite(s_n[j])) / len(held)) if held else 0.0
|
|
rets.append(realized - turn * (cost / 2)); prev = cur
|
|
return np.array(rets)
|
|
|
|
|
|
def stats(r):
|
|
ann = r.mean() * 365; vol = r.std() * math.sqrt(365)
|
|
return ann, (ann / vol if vol > 0 else float("nan"))
|
|
|
|
|
|
print(f"low-turnover rescue: {N} coins, {T} days")
|
|
print(f"{'config':>34} {'annNET%':>8} {'Sharpe':>7}")
|
|
for K in [10, 20]:
|
|
for entry, exit_ in [(0.0005, 0.0003), (0.0010, 0.0005), (0.0020, 0.0010)]:
|
|
for cost, cl in [(0.0006, "maker6bp"), (0.0010, "taker10bp")]:
|
|
r = turnover_cost(K, entry, exit_, cost)
|
|
a, sh = stats(r)
|
|
print(f" K={K:>2} entry{entry*1e4:.0f}/exit{exit_*1e4:.0f}bp {cl:>9} {100*a:>+8.1f} {sh:>+7.2f}")
|
|
print("\nVERDICT: any config net Sharpe>1 = edge survives with patient/low-turnover execution.")
|
|
print("If all still negative = cost-walled even with hysteresis -> not deployable.")
|