#!/usr/bin/env python3 """HONEST cross-venue OOS: track the FIXED venue pair chosen at entry (not the daily max-min). The earlier sim booked daily max-min funding (assumes you always hold the optimal venue pair = too optimistic; capture 0.68 shows ~32% reshuffle loss). Realistic: at entry pick A=argmax venue, B=argmin; hold THAT pair; book realized = funding_A - funding_B on the FIXED pair each day; exit when your pair's spread decays below exit. Clean OOS split (IS-pick config -> OOS-blind). """ import json import math import numpy as np PANEL = "data/surfer/xvenue2/panel2.json" COST = 0.0010 def main(): panel = json.load(open(PANEL)) dates = sorted(set().union(*[set(v) for v in panel.values()])) coins = list(panel) T = len(dates) F = {c: panel[c] for c in coins} # F[coin][date] = {venue: daily_funding} split = int(0.60 * T) print(f"HONEST cross-venue OOS (fixed held pair): {len(coins)} coins, {T} days") print(f" IS {dates[0]}..{dates[split-1]} | OOS {dates[split]}..{dates[-1]}") def hyst(t0, t1, K, entry, exit_, cost): held = {} # coin -> (A, B) rets, prev = [], set() for t in range(t0, t1 - 1): dt, dn = dates[t], dates[t + 1] # exit: keep held coins whose FIXED-pair spread still > exit keep = {} for c, (A, B) in held.items(): ft = F[c].get(dt) if ft and A in ft and B in ft and (ft[A] - ft[B]) > exit_: keep[c] = (A, B) held = keep # entry: rank fresh coins by current max-min, add above entry hurdle cands = [] for c in coins: if c in held: continue ft = F[c].get(dt) if ft and len(ft) >= 2: A = max(ft, key=ft.get); B = min(ft, key=ft.get) if ft[A] - ft[B] > entry: cands.append((ft[A] - ft[B], c, A, B)) cands.sort(reverse=True, key=lambda x: x[0]) for sp, c, A, B in cands: if len(held) >= K: break held[c] = (A, B) # realized NEXT day on the FIXED pair (short A receive f_A, long B receive -f_B) if held: tot = 0.0 for c, (A, B) in held.items(): fn = F[c].get(dn) if fn and A in fn and B in fn: tot += fn[A] - fn[B] realized = tot / len(held) else: realized = 0.0 cur = set(held) turn = len(cur ^ prev) / max(len(cur), 1) if cur else 0 rets.append(realized - turn * (cost / 2)); prev = cur r = np.array(rets) ann = r.mean() * 365 if len(r) else float("nan") vol = r.std() * math.sqrt(365) if len(r) else float("nan") return ann, (ann / vol if vol > 0 else float("nan")), r grid = [(K, e, x) for K in (10, 20) for e in (0.0005, 0.0010, 0.0020) for x in (0.0003, 0.0005, 0.0010) if x <= e] scored = [(cfg, hyst(0, split, *cfg, COST)[1], hyst(split, T, *cfg, COST)[1], hyst(split, T, *cfg, COST)[0]) for cfg in grid] best = max(scored, key=lambda s: s[1]) cfg, is_sh, oos_sh, oos_ann = best print(f"\n IS-BEST (blind to OOS): K={cfg[0]} entry={cfg[1]*1e4:.0f}bp exit={cfg[2]*1e4:.0f}bp") print(f" IS Sharpe {is_sh:+.1f} -> OOS Sharpe {oos_sh:+.1f} | OOS ann {100*oos_ann:+.1f}% (realistic ~/2.5 = {oos_sh/2.5:+.1f})") good = [s for s in scored if s[1] > 2]; robust = [s for s in good if s[2] > 1] print(f" robustness: {len(robust)}/{len(good)} IS-good configs also OOS Sharpe>1") _, _, r = hyst(split, T, *cfg, COST) mo = [dates[t][:7] for t in range(split, T - 1)] print(" OOS per-month Sharpe:") for m in sorted(set(mo)): seg = r[np.array(mo) == m] if len(seg) > 6 and seg.std() > 0: print(f" {m}: {seg.mean()/seg.std()*math.sqrt(365):+.1f}") print("\n VERDICT (honest, fixed-pair): OOS Sharpe>1 realistic + months positive = genuinely deployable.") if __name__ == "__main__": main()