#!/usr/bin/env python3 """CLEAN OOS test of the cross-venue funding arb (hysteresis). The hysteresis params (entry/exit/K) were chosen by looking at the data = in-sample. Honest test: pick the best config on IN-SAMPLE (first 60% of days) by IS Sharpe, then apply that EXACT config BLIND to OUT-OF-SAMPLE (last 40%). If OOS holds -> real; if it collapses -> in-sample fit. Reports IS-best config + its OOS result, robustness across configs, and OOS per-month. Taker cost 10bp (conservative). Sharpe is inflated by low vol + idealized fills (realistic ~ /2.5). """ 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()])) 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) split = int(0.60 * T) print(f"cross-venue OOS: {N} coins, {T} days | IS {dates[0]}..{dates[split-1]} | OOS {dates[split]}..{dates[-1]}") def hyst(t0, t1, K, entry, exit_, cost): held, rets, prev = {}, [], set() for t in range(t0, t1 - 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 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 = [] for cfg in grid: _, is_sh, _ = hyst(0, split, *cfg, COST) _, oos_sh, _ = hyst(split, T, *cfg, COST) oa = hyst(split, T, *cfg, COST)[0] scored.append((cfg, is_sh, oos_sh, oa)) best = max(scored, key=lambda s: s[1]) # pick by IS Sharpe ONLY cfg, is_sh, oos_sh, oos_ann = best print(f"\n IS-BEST config (chosen 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: of {len(good)} configs with IS Sharpe>2, {len(robust)} also OOS Sharpe>1") # OOS per-month on the IS-best config _, _, r = hyst(split, T, *cfg, COST) mo = [dates[t][:7] for t in range(split, T - 1)] print(" OOS per-month Sharpe (IS-best config):") 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} (n={len(seg)})") print("\n VERDICT: IS-best OOS Sharpe>1 (realistic, after /2.5) + most months positive = REAL out-of-sample.") print(" If OOS collapses to ~0/negative = in-sample fit, joins the dead list.") if __name__ == "__main__": main()