Files
foxhunt/scripts/surfer/xcorr_leadlag.py
jgrusewski 247e469a31 test: cross-market connectedness = diversification, not alpha
User intuited markets are connected -> exploitable? Measured equity/bond/gold/commod/trend/crypto
(2019-26): contemporaneous corr low (+0.03 avg; equity-crypto +0.43, equity-trend -0.28), crisis
corr STABLE (didn't spike -> diversification held in stress), lead-lag ~0 (trade OOS Sharpe -1.01).
Connectedness is priced-in -> value is diversification (already in the book, validates it beating
60/40 low-DD), not a predictive signal. Same efficient wall for cross-market alpha.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:46:38 +02:00

73 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Cross-market connectedness: contemporaneous correlation, crisis-correlation spike, and lead-lag.
Streams: equity, bond, gold, commod(=energy proxy CL), trend, crypto. Three questions:
1. CONTEMPORANEOUS corr matrix — how connected are they (the diversification basis)?
2. CRISIS corr — do correlations spike on risk-off days (diversification breaks)?
3. LEAD-LAG — does market A's return predict B's NEXT return (a tradeable cross-market signal)?
+ a simple OOS test of the best lead-lag pair net of cost. Honest prior: ~0 (efficient).
"""
import math
import os
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from multistrat_book import build_streams # raw stream daily returns # noqa: E402
def main():
streams, days, year = build_streams()
names = list(streams)
R = np.column_stack([streams[n] for n in names])
R = np.nan_to_num(R)
T, S = R.shape
eq = names.index("equity")
print(f"CROSS-MARKET CONNECTEDNESS — {S} streams, {T} days ({names})")
print("\n (1) CONTEMPORANEOUS correlation:")
C = np.corrcoef(R.T)
print(" " + " ".join(f"{n[:5]:>6}" for n in names))
for i, n in enumerate(names):
print(f" {n[:5]:>5} " + " ".join(f"{C[i,j]:>+6.2f}" for j in range(S)))
off = (C.sum() - S) / (S * S - S)
print(f" avg off-diagonal: {off:+.2f} (low = good diversification)")
# (2) crisis correlation: risk-off days (equity < -1%)
riskoff = R[:, eq] < -0.01
Cn = np.corrcoef(R[~riskoff].T); Cc = np.corrcoef(R[riskoff].T)
offn = (Cn.sum() - S) / (S * S - S); offc = (Cc.sum() - S) / (S * S - S)
print(f"\n (2) avg corr NORMAL days: {offn:+.2f} RISK-OFF days (equity<-1%): {offc:+.2f}")
print(f" -> correlations {'SPIKE (diversification breaks in crises)' if offc > offn + 0.1 else 'stable'}")
# (3) lead-lag: corr(A[t], B[t+1])
print("\n (3) LEAD-LAG corr(A[t] -> B[t+1]) (rows=A leads, cols=B follows):")
LL = np.zeros((S, S))
for i in range(S):
for j in range(S):
a = R[:-1, i]; b = R[1:, j]
LL[i, j] = np.corrcoef(a, b)[0, 1]
print(" " + " ".join(f"{n[:5]:>6}" for n in names))
for i, n in enumerate(names):
print(f" {n[:5]:>5} " + " ".join(f"{LL[i,j]:>+6.2f}" for j in range(S)))
# strongest |lead-lag| pair (excluding self)
LLm = LL.copy(); np.fill_diagonal(LLm, 0)
i, j = np.unravel_index(np.argmax(np.abs(LLm)), LLm.shape)
print(f" strongest: {names[i]}[t] -> {names[j]}[t+1] = {LL[i,j]:+.3f}")
# OOS test of the strongest lead-lag pair: trade B by sign of A, net 10bp
split = int(0.6 * T)
sig = np.sign(R[:-1, i]) * np.sign(LL[i, j]) # follow the lead direction
pnl = sig * R[1:, j] - 0.0010 * np.abs(np.diff(np.concatenate([[0], sig])))
def sh(x):
x = x[np.isfinite(x)]; return x.mean() / (x.std() + 1e-9) * math.sqrt(252) if x.std() > 0 else float("nan")
print(f"\n lead-lag trade ({names[i]}->{names[j]}) net 10bp: IS Sharpe {sh(pnl[:split]):+.2f} OOS Sharpe {sh(pnl[split:]):+.2f}")
print("\n VERDICT: contemporaneous corr low (diversification basis) + crisis-spike (risk to manage) = the")
print(" connection's REAL value is risk/diversification. Lead-lag OOS Sharpe ~0 = no tradeable prediction")
print(" (efficient: the connection is already priced). If OOS lead-lag >1, a real cross-market signal exists.")
if __name__ == "__main__":
main()