Files
foxhunt/scripts/surfer/multiasset_futures.py
jgrusewski dbbe8f7b22 feat(surfer): multi-asset futures test — uncorrelated but no edge (dilutes)
Expanded futures universe 22->39 liquid CME roots ($16.39 credits, 16y daily). (A)
breadth did NOT unlock an edge: XS momentum + TS trend all negative/zero, DSR 0
(efficient market, unlike crypto). (B) diversifier check: futures-trend +0.01,
crypto-momentum +0.88, CORRELATION -0.01 (genuinely uncorrelated) but combined +0.63
< +0.88 -> an uncorrelated sleeve with zero standalone edge DILUTES, not diversifies.
A diversifier needs low-corr AND positive edge; futures trend has only the former.
Caveat: roll-zeroing understates futures trend/carry (floor work ~+0.05-0.14 w/ proper
rolls) -> marginal at best. Crypto momentum still the only real edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 17:08:40 +02:00

102 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""Multi-asset futures test: does breadth (39 roots) unlock an edge, and is a CTA trend book
a genuine DIVERSIFIER to the crypto momentum book?
(A) XS momentum (was null on 22 roots) + TS trend-following (directional CTA) on the 39-asset
daily futures panel — deflated, per-year, realistic cost.
(B) The prize: correlation of the futures TS-trend book to the crypto XS-momentum book (overlap
2019-2026) + the combined two-market book. Low correlation + both positive = real diversification.
"""
import math
import os
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from signal_sweep import load_panel, build_returns, xs_weights, pnl_w, validate, sharpe_t # noqa: E402
import pit_sweep # noqa: E402
from surfer_poc import compute_weights, CFG # noqa: E402
import torch # noqa: E402
DEV = "cuda" if torch.cuda.is_available() else "cpu"
def roll(fn, X, L):
out = np.full_like(X, np.nan)
for t in range(L, len(X)):
out[t] = fn(X[t - L:t], axis=0)
return out
def trailing(lc, L):
out = np.full_like(lc, np.nan); out[L:] = lc[L:] - lc[:-L]; return out
def main():
roots, days, close, op, inst, weekday = load_panel()
lc, R, ov, intr = build_returns(close, op, inst)
T, N = close.shape
vol60 = roll(np.std, R, 60)
invvol = np.where(vol60 > 0, 1.0 / vol60, 0.0)
year = (1970 + days / 365.25).astype(int)
yrs = list(range(2011, 2027))
def wdir(sig): # directional unit-gross (TS trend, net exposure)
s = np.nan_to_num(sig)
g = np.sum(np.abs(s), axis=1, keepdims=True); g[g == 0] = 1
return s / g
def peryear(p, yy):
return [sharpe_t(torch.tensor(p[yy[1:] == y], device=DEV, dtype=torch.float64))
if (yy[1:] == y).sum() > 30 else float("nan") for y in yrs]
print(f"\n===== (A) MULTI-ASSET FUTURES — {N} roots, {T} days =====")
print(f"{'signal':>16} {'full':>6} {'IS':>6} {'OOS':>6} {'CPCVmed':>8} {'DSR':>5}")
sigs = {}
for L in [20, 60, 120]:
sigs[f"XS_mom_{L}"] = pnl_w(xs_weights(trailing(lc, L)), R, cost_bp=1.5)
for L in [60, 120, 250]:
sigs[f"TS_trend_{L}"] = pnl_w(wdir(np.sign(trailing(lc, L)) * invvol), R, cost_bp=1.5)
NT = 40 # cumulative-session deflation (honest)
best_ts, best_ts_pnl, best_med = None, None, -9
for nm, p in sigs.items():
v = validate(p, days, NT)
print(f"{nm:>16} {v['full']:>+6.2f} {v['is_']:>+6.2f} {v['oos']:>+6.2f} {v['med']:>+8.2f} {v['dsr']:>5.2f}")
if nm.startswith("TS_trend") and v["med"] > best_med:
best_ts, best_ts_pnl, best_med = nm, p, v["med"]
print(f" futures TS-trend per-year ({best_ts}): " + " ".join(f"{x:+.2f}" if not math.isnan(x) else " n/a" for x in peryear(best_ts_pnl, year)))
# ---------- (B) DIVERSIFIER: futures trend vs crypto momentum ----------
syms, cdays, cclose, cqv, cfund = pit_sweep.load()
cR = np.zeros_like(cclose); cR[1:] = np.log(cclose)[1:] - np.log(cclose)[:-1]
cR = np.where(np.isfinite(cR), cR, 0.0)
cfund = np.where(np.isfinite(cfund), cfund, 0.0)
cw, _ = compute_weights(cclose, cqv, cdays, CFG)
cReff = cR - cfund
crypto_book = np.sum(cw[:-1] * cReff[1:], axis=1) # crypto momentum daily PnL (cdays[1:])
fut_book = best_ts_pnl # futures trend daily PnL (days[1:])
# align on common epoch-days
fd, cd = days[1:], cdays[1:]
fmap = {int(d): fut_book[i] for i, d in enumerate(fd)}
cmap = {int(d): crypto_book[i] for i, d in enumerate(cd)}
common = sorted(set(fmap) & set(cmap))
fa = np.array([fmap[d] for d in common]); ca = np.array([cmap[d] for d in common])
m = np.isfinite(fa) & np.isfinite(ca)
corr = float(np.corrcoef(fa[m], ca[m])[0, 1])
sf, sc = float(np.std(fa[m])), float(np.std(ca[m]))
comb = ((1 / sf) * fa[m] + (1 / sc) * ca[m]) / (1 / sf + 1 / sc)
cyear = (1970 + np.array(common) / 365.25).astype(int)[m]
print(f"\n===== (B) DIVERSIFIER CHECK — futures-trend vs crypto-momentum (overlap {len(fa[m])} days) =====")
print(f"futures-trend Sharpe {sharpe_t(torch.tensor(fa[m],device=DEV,dtype=torch.float64)):+.2f} "
f"crypto-momentum Sharpe {sharpe_t(torch.tensor(ca[m],device=DEV,dtype=torch.float64)):+.2f}")
print(f"CORRELATION = {corr:+.2f} combined-book Sharpe = {sharpe_t(torch.tensor(comb,device=DEV,dtype=torch.float64)):+.2f}")
print("combined per-year: " + " ".join(f"{y}:{sharpe_t(torch.tensor(comb[cyear==y],device=DEV,dtype=torch.float64)):+.2f}"
for y in range(2019, 2027) if (cyear == y).sum() > 30))
print("\nVERDICT: low |corr| + combined Sharpe > each alone = genuine cross-market diversification.")
if __name__ == "__main__":
main()