Lead-Lag Networks
Hayashi-Yoshida correlation, cross-correlation lags, and Granger causality
Markets don’t move in isolation. Bitcoin moves before altcoins, SPY moves before sector ETFs, Polymarket moves before Kalshi on the same event. Lead-lag analysis finds these relationships and quantifies how much predictive signal one market gives about another. Horizon provides three tools: Hayashi-Yoshida asynchronous correlation, cross-correlation lag analysis, and Granger causality testing.
API
Hayashi-Yoshida correlation
Standard correlation requires synchronized timestamps. The Hayashi-Yoshida estimator works on irregularly sampled data — it computes correlation from overlapping time intervals, which is how real tick data arrives.
corr = hz.hayashi_yoshida(ts1, ts2)
# ts1, ts2: list of (timestamp, price) tuples
# Returns: float in [-1, 1]
Cross-correlation lags
Find the lag at which two time series are most correlated. Positive lag means x leads y.
lags = hz.cross_correlation_lags(x, y, max_lag=20)
# Returns: list of (lag, correlation) tuples, sorted by |correlation|
best_lag, best_corr = lags[0]
print(f"x leads y by {best_lag} periods (corr={best_corr:.3f})")
Granger causality
Tests whether past values of x help predict y beyond what past values of y alone provide.
result = hz.granger_causality(x, y, max_lag=10)
# result.f_statistic: float
# result.p_value: float
# result.optimal_lag: int
# result.significant: bool (p_value < 0.05)
Building a lead-lag network
Combine pairwise analysis into a directed graph of market relationships:
markets = ["BTC", "ETH", "SOL", "AVAX", "MATIC"]
edges = []
for i, m1 in enumerate(markets):
for j, m2 in enumerate(markets):
if i == j:
continue
result = hz.granger_causality(returns[m1], returns[m2], max_lag=5)
if result.significant:
edges.append((m1, m2, result.optimal_lag))
print(f"{m1} -> {m2} (lag={result.optimal_lag}, p={result.p_value:.4f})")
# Result: a directed graph where edges indicate predictive relationships
Cross-market signal extraction
Use the lead-lag structure to generate trading signals:
# If BTC leads ETH by 2 periods with high correlation:
lags = hz.cross_correlation_lags(btc_returns, eth_returns, max_lag=10)
best_lag, best_corr = lags[0]
if best_lag > 0 and abs(best_corr) > 0.3:
# BTC moved but ETH hasn't caught up yet
btc_move = btc_returns[-best_lag:]
expected_eth_move = sum(btc_move) * best_corr
# Trade ETH in the direction of expected_eth_move
When to use
- Cross-exchange arbitrage: detect which venue leads on a given contract (e.g., Polymarket vs. Kalshi for the same event).
- Crypto signal propagation: build a directed graph of which tokens lead others and trade the followers.
- Sector rotation: find which sector ETFs lead the broader market and position early.
- Asynchronous data: use Hayashi-Yoshida when your data sources have different update frequencies or irregular timestamps.