---
title: "Cross-Impact Matrix"
description: "Estimate how trading in one market moves prices in correlated markets. Rust-native cross-impact estimation for multi-market strategies."
icon: "table-cells"
---

<Warning>
  **Ultra Feature.** Requires an Ultra subscription. [Get started at api.mathematicalcompany.com](https://api.mathematicalcompany.com)
</Warning>

<Tip>
  **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.
</Tip>

# 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.

<CardGroup cols={2}>
  <Card title="Cross-Impact Estimation" icon="table-cells">
    `hz.estimate_cross_impact()` computes the N x N impact matrix from return and volume histories.
  </Card>
  <Card title="Streaming Estimator" icon="rotate">
    `CrossImpactEstimator` maintains a rolling window and updates the matrix incrementally as new data arrives.
  </Card>
  <Card title="Impact Decomposition" icon="diagram-project">
    Separates self-impact (diagonal) from cross-impact (off-diagonal) to identify contagion channels.
  </Card>
  <Card title="Pipeline Integration" icon="plug">
    `hz.cross_impact_monitor()` injects the live impact matrix into your strategy context each cycle.
  </Card>
</CardGroup>

---

## 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.

```python
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"]
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `returns_matrix` | `list[list[float]]` | T x N matrix of log returns (T observations, N markets) |
| `volume_matrix` | `list[list[float]]` | T x N matrix of signed volume (positive = net buy pressure) |
| `names` | `list[str]` | Market names, length N |

### CrossImpactMatrix Type

| Field | Type | Description |
|-------|------|-------------|
| `matrix` | `list[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_impact` | `list[float]` | Diagonal entries (Kyle's lambda for each market) |
| `names` | `list[str]` | Market names in the same order as rows/columns |
| `condition_number` | `float` | Condition number of the matrix (high values indicate instability) |

<Note>
  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.
</Note>

---

## CrossImpactEstimator

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

```python
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)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `n_markets` | `int` | Number of markets to track |
| `lookback` | `int` | Maximum number of observations to retain (FIFO eviction) |

### Methods

| Method | Parameters | Returns | Description |
|--------|-----------|---------|-------------|
| `add_observation` | `returns: list[float], volumes: list[float]` | `None` | Append one time step of returns and volumes |
| `estimate` | `names: list[str]` | `CrossImpactMatrix` or `None` | Compute the matrix from buffered data. Returns `None` if insufficient observations |

---

## Reading the Matrix

```python
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"]`.

```python
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

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `feed` | `str` | required | Feed name to read prices and volumes from |
| `lookback` | `int` | `200` | Rolling window size for the estimator |
| `update_every` | `int` | `10` | Recompute the matrix every N cycles |

---

## Mathematical Background

<AccordionGroup>
  <Accordion title="Cross-Impact Regression">
    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.
  </Accordion>
  <Accordion title="Why Cross-Impact Matters">
    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
  </Accordion>
  <Accordion title="Condition Number">
    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.
  </Accordion>
</AccordionGroup>

<Warning>
  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.
</Warning>
