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>
102 lines
4.7 KiB
Python
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()
|