#!/usr/bin/env python3 """Deep multi-venue funding history (Binance + OKX + Hyperliquid, ~1yr) for a confident cross-venue backtest. Binance & HL are deep (~330d); OKX ~3mo. Aggregate to daily per venue. Cache per coin. Saves data/surfer/xvenue/panel2.json = {coin: {date: {bn, okx, hl}}}. """ import datetime import json import os import time import urllib.request OUT = "data/surfer/xvenue2" NCOINS = 35 DAYS = 330 def get(u, post=None): data = json.dumps(post).encode() if post else None h = {"User-Agent": "Mozilla/5.0", "Accept": "application/json"} if post: h["Content-Type"] = "application/json" for a in range(4): try: return json.loads(urllib.request.urlopen(urllib.request.Request(u, data=data, headers=h), timeout=25).read()) except Exception: if a == 3: return None time.sleep(3 * (a + 1)) return None def base(s): return s[:-4] if s.endswith("USDT") else s 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 okx_hist(coin): out, after = {}, None for _ in range(6): u = f"https://www.okx.com/api/v5/public/funding-rate-history?instId={coin}-USDT-SWAP&limit=100" if after: u += f"&after={after}" r = get(u); data = (r or {}).get("data") or [] if not data: break for x in data: k = day_of(x["fundingTime"]); out[k] = out.get(k, 0.0) + float(x["fundingRate"]) after = data[-1]["fundingTime"]; time.sleep(0.2) return out def hl_hist(coin, now_ms): out, start = {}, now_ms - DAYS * 86400000 for _ in range(30): r = get("https://api.hyperliquid.xyz/info", post={"type": "fundingHistory", "coin": coin, "startTime": start}) if not r: break for x in r: k = day_of(x["time"]); out[k] = out.get(k, 0.0) + float(x["fundingRate"]) last = int(r[-1]["time"]) if last <= start or len(r) < 2: break start = last + 1 return out def main(): os.makedirs(OUT, exist_ok=True) now_ms = int(time.time() * 1000) binv = {base(x["symbol"]): float(x["quoteVolume"]) for x in get("https://fapi.binance.com/fapi/v1/ticker/24hr") if x["symbol"].endswith("USDT")} hlmeta = get("https://api.hyperliquid.xyz/info", post={"type": "metaAndAssetCtxs"}) hlcoins = {u["name"] for u in hlmeta[0]["universe"]} coins = sorted([c for c in binv if c in hlcoins and binv[c] > 10e6], key=lambda c: -binv[c])[:NCOINS] print(f"universe: {len(coins)} coins (Binance>$10M & on Hyperliquid)") panel = {} for i, c in enumerate(coins): 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) days = sorted(set(bn) | set(okx) | set(hl)) rec = {} for d in days: v = {} if d in bn: v["bn"] = bn[d] if d in okx: v["okx"] = okx[d] if d in hl: v["hl"] = hl[d] if len(v) >= 2: rec[d] = v panel[c] = rec json.dump(rec, open(cf, "w")) print(f" {i+1}/{len(coins)} {c}: {len(rec)} days w/ >=2 venues (bn{len(bn)} okx{len(okx)} hl{len(hl)})") time.sleep(0.3) json.dump(panel, open(f"{OUT}/panel2.json", "w")) print(f"DONE: {len(panel)} coins -> {OUT}/panel2.json") if __name__ == "__main__": main()