Skip to main content
Ultra Feature. Requires an Ultra subscription. Get started at api.mathematicalcompany.com
What is this? When you trade in one market, correlated markets move too. The cross-impact matrix quantifies this spillover: how much does a $1 trade in market A move the price of market B? Use it to estimate the true cost of multi-market trades and to avoid inadvertently moving prices against your other positions.

Cross-Impact Matrix

When you trade in one prediction market, correlated markets move. The cross-impact matrix quantifies this relationship: how much does a $1 trade in market A move the price of market B? Horizon estimates this matrix from synchronized return and volume data, then exposes it for cost-aware execution and portfolio construction.

Cross-Impact Estimation

hz.estimate_cross_impact() computes the N x N impact matrix from return and volume histories.

Streaming Estimator

CrossImpactEstimator maintains a rolling window and updates the matrix incrementally as new data arrives.

Impact Decomposition

Separates self-impact (diagonal) from cross-impact (off-diagonal) to identify contagion channels.

Pipeline Integration

hz.cross_impact_monitor() injects the live impact matrix into your strategy context each cycle.

hz.estimate_cross_impact

Estimate the cross-impact matrix from synchronized return and volume matrices. The estimator regresses price changes in each market against signed volume flow in all markets.
import horizon as hz

# returns_matrix: T observations x N markets (log returns)
returns_matrix = [
    [ 0.01, -0.02,  0.005],
    [ 0.02,  0.01, -0.01 ],
    [-0.01,  0.03,  0.02 ],
    [ 0.005,-0.01,  0.01 ],
    [ 0.015, 0.005,-0.005],
]

# volume_matrix: T observations x N markets (signed volume: positive = net buy)
volume_matrix = [
    [ 100, -200,  50],
    [ 300,  150, -80],
    [-150,  400, 200],
    [  50, -100, 120],
    [ 200,   80, -60],
]

names = ["election", "fed-rate", "recession"]

result = hz.estimate_cross_impact(returns_matrix, volume_matrix, names)
print(result.matrix)        # 3x3 impact matrix
print(result.self_impact)   # diagonal: [lambda_1, lambda_2, lambda_3]
print(result.names)         # ["election", "fed-rate", "recession"]
ParameterTypeDescription
returns_matrixlist[list[float]]T x N matrix of log returns (T observations, N markets)
volume_matrixlist[list[float]]T x N matrix of signed volume (positive = net buy pressure)
nameslist[str]Market names, length N

CrossImpactMatrix Type

FieldTypeDescription
matrixlist[list[float]]N x N cross-impact matrix. Entry (i, j) is the price impact on market i per unit volume in market j
self_impactlist[float]Diagonal entries (Kyle’s lambda for each market)
nameslist[str]Market names in the same order as rows/columns
condition_numberfloatCondition number of the matrix (high values indicate instability)
The matrix is generally asymmetric: trading in the election market may move the recession market more than the reverse. Large off-diagonal entries indicate strong cross-market contagion.

CrossImpactEstimator

A streaming estimator that maintains a rolling window of observations and recomputes the cross-impact matrix as new data arrives.
import horizon as hz

estimator = hz.CrossImpactEstimator(n_markets=3, lookback=100)

# Add observations one at a time
estimator.add_observation(
    returns=[0.01, -0.02, 0.005],
    volumes=[100, -200, 50],
)

estimator.add_observation(
    returns=[0.02, 0.01, -0.01],
    volumes=[300, 150, -80],
)

# Estimate when you have enough data (at least n_markets + 1 observations)
result = estimator.estimate(names=["election", "fed-rate", "recession"])
if result is not None:
    print(result.matrix)
    print(result.self_impact)
ParameterTypeDescription
n_marketsintNumber of markets to track
lookbackintMaximum number of observations to retain (FIFO eviction)

Methods

MethodParametersReturnsDescription
add_observationreturns: list[float], volumes: list[float]NoneAppend one time step of returns and volumes
estimatenames: list[str]CrossImpactMatrix or NoneCompute the matrix from buffered data. Returns None if insufficient observations

Reading the Matrix

import horizon as hz

result = hz.estimate_cross_impact(returns_matrix, volume_matrix, names)

# Self-impact: how much does trading market i move its own price?
for i, name in enumerate(result.names):
    print(f"{name} self-impact (lambda): {result.self_impact[i]:.6f}")

# Cross-impact: how much does trading market j move market i?
for i in range(len(result.names)):
    for j in range(len(result.names)):
        if i != j:
            print(f"  {result.names[j]} -> {result.names[i]}: {result.matrix[i][j]:.6f}")

# Condition number check
if result.condition_number > 100:
    print("Warning: matrix is ill-conditioned, estimates may be unreliable")

Pipeline Integration

The hz.cross_impact_monitor() pipeline function recomputes the cross-impact matrix every N cycles and injects it into ctx.params["cross_impact"].
import horizon as hz

def model(ctx):
    ci = ctx.params.get("cross_impact")
    if ci is None:
        return []

    # Use cross-impact to adjust execution size
    # If trading election moves fed-rate significantly, reduce size
    election_idx = ci.names.index("election")
    fed_idx = ci.names.index("fed-rate")
    spillover = abs(ci.matrix[fed_idx][election_idx])

    fair = 0.62
    size = 100.0 * max(0.1, 1.0 - spillover * 50)  # dampen by cross-impact
    return hz.quotes(fair=fair, spread=0.04, size=size)

hz.run(
    name="cross-impact-aware",
    markets=["election", "fed-rate", "recession"],
    pipeline=[
        hz.cross_impact_monitor(
            feed="poly",
            lookback=200,
            update_every=10,  # recompute every 10 cycles
        ),
        model,
    ],
    feeds={"poly": hz.PolymarketBook(token_id="0x123...")},
    interval=5.0,
)

Parameters

ParameterTypeDefaultDescription
feedstrrequiredFeed name to read prices and volumes from
lookbackint200Rolling window size for the estimator
update_everyint10Recompute the matrix every N cycles

Mathematical Background

The cross-impact matrix Lambda is estimated via multivariate OLS regression:delta_p_i(t) = sum_j Lambda_ij * v_j(t) + epsilon_i(t)where delta_p_i(t) is the return of market i at time t, and v_j(t) is the signed volume in market j. The diagonal entries Lambda_ii correspond to Kyle’s lambda (self-impact), and off-diagonal entries Lambda_ij capture how volume in market j moves the price of market i.
In prediction markets, related events share information. A large buy in “Fed cuts rates in June” signals dovish expectations, which should also move “Recession by Q4” and “S&P above 5000.” Ignoring cross-impact leads to underestimating execution cost when trading correlated markets simultaneously. The cross-impact matrix lets you:
  1. Route orders to minimize total market impact across all correlated markets
  2. Avoid inadvertently signaling your view through cross-market contagion
  3. Identify which markets are most informationally connected
A high condition number indicates that the estimated matrix is sensitive to small perturbations in the input data. This typically happens when markets are nearly collinear (e.g., two markets track almost the same event). When the condition number exceeds 100, consider reducing the number of markets or increasing the lookback window.
Cross-impact estimation requires synchronized observations across all markets. If markets trade at very different frequencies, the volume matrix will contain many zeros, degrading the estimate. Use markets with similar activity levels for best results.