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:
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user