feat(crypto): cross-venue harness -> hysteresis (deployable) + deep full-year history

(1) Live cross_venue_funding.py paper harness now uses HYSTERESIS (enter >10bp/day, hold until
spread decays <5bp) instead of naive daily top-K -> tracks the deployable low-turnover version
(naive was cost-killed -4.5 Sharpe; hysteresis nets +10-14%/yr). State reset. (2) fetch_xvenue_hist2
now paginates Binance fundingRate -> full ~330d history (was 67d) so Binance-HL overlap = full year
(330 days) for the regime test across ~11 months. Monitor armed for the full-year backtest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-06-07 18:58:24 +02:00
parent 027d73a504
commit ee2215f4d3
2 changed files with 26 additions and 10 deletions

View File

@@ -26,7 +26,9 @@ LIQ = 10e6 # >$10M/day on BOTH legs (clean, fungible)
MAXF = 0.005 # exclude legs with |daily funding| > 50bp/day = distress/artifact (un-tradeable)
TOPK = 10 # book the top-K spreads
COST_RT = 0.0010 # ~10bp round-trip (2 perp legs, maker)
HURDLE = 0.0005 # 5bp/day spread to bother
HURDLE = 0.0005 # 5bp/day spread to show in scan
ENTRY = 0.0010 # hysteresis: only ENTER a new pair above 10bp/day
EXIT = 0.0005 # hysteresis: HOLD a pair until its spread decays below 5bp/day (cuts turnover)
def get(url, post=None):
@@ -122,8 +124,15 @@ def cmd_run():
cur = {r["coin"]: r["spread"] for r in rows}
prev = st["positions"]
realized = sum(w * cur.get(c, 0.0) for c, w in prev.items()) # carry on yesterday's book at today's spreads
qual = [r for r in rows if r["spread"] > HURDLE][:TOPK]
newpos = {r["coin"]: 1.0 / len(qual) for r in qual} if qual else {}
# HYSTERESIS (the deployable, low-turnover version): hold winners until they decay, only enter strong fresh
held = [c for c in prev if cur.get(c, 0.0) > EXIT]
for r in sorted([r for r in rows if r["spread"] > ENTRY], key=lambda r: -r["spread"]):
if len(held) >= TOPK:
break
if r["coin"] not in held:
held.append(r["coin"])
qual = [r for r in rows if r["coin"] in held]
newpos = {c: 1.0 / len(held) for c in held} if held else {}
turn = sum(abs(newpos.get(c, 0) - prev.get(c, 0)) for c in set(newpos) | set(prev))
net = realized - turn * (COST_RT / 2)
st.update(positions=newpos, days=st["days"] + 1, last_run_date=today)

View File

@@ -37,12 +37,19 @@ def day_of(ms):
return datetime.datetime.utcfromtimestamp(int(ms) / 1000).strftime("%Y-%m-%d")
def binance_hist(coin):
r = get(f"https://fapi.binance.com/fapi/v1/fundingRate?symbol={coin}USDT&limit=1000")
d = {}
for x in (r or []):
k = day_of(x["fundingTime"]); d[k] = d.get(k, 0.0) + float(x["fundingRate"])
return d
def binance_hist(coin, now_ms):
out, start = {}, now_ms - DAYS * 86400000
for _ in range(8): # paginate back full DAYS window
r = get(f"https://fapi.binance.com/fapi/v1/fundingRate?symbol={coin}USDT&startTime={start}&limit=1000")
if not r:
break
for x in r:
k = day_of(x["fundingTime"]); out[k] = out.get(k, 0.0) + float(x["fundingRate"])
last = int(r[-1]["fundingTime"])
if last <= start or len(r) < 2:
break
start = last + 1
return out
def okx_hist(coin):
@@ -88,7 +95,7 @@ def main():
cf = f"{OUT}/{c}.json"
if os.path.exists(cf):
panel[c] = json.load(open(cf)); continue
bn = binance_hist(c); okx = okx_hist(c); hl = hl_hist(c, now_ms)
bn = binance_hist(c, now_ms); okx = okx_hist(c); hl = hl_hist(c, now_ms)
days = sorted(set(bn) | set(okx) | set(hl))
rec = {}
for d in days: