---
title: "Spread Convergence"
description: "Mean-reversion trading between two correlated prediction markets."
icon: "wave-sine"
---

<Note>
  **Pro Feature.** Requires a Pro or Ultra subscription. [Get started at api.mathematicalcompany.com](https://api.mathematicalcompany.com)
</Note>

# Spread Convergence

Track the price spread between two correlated markets. Enter when the spread deviates beyond a z-score threshold, exit when it reverts to the mean.

## How It Works

1. Compute `spread = price_a - price_b` each cycle
2. Maintain a rolling window of spread values
3. Compute z-score of current spread vs historical mean/std
4. Enter when `|z| > entry_zscore`, exit when `|z| < exit_zscore`

```
Spread history: mean=0.02, std=0.01
Current spread: 0.05 → z-score = 3.0
Signal: long_b_short_a (spread too high, expect reversion)
```

## Pipeline: spread_convergence

```python
scanner = hz.spread_convergence(
    market_a="btc-100k",
    market_b="eth-5k",
    feed_a="btc_feed",
    feed_b="eth_feed",
    lookback=100,
    entry_zscore=2.0,
    exit_zscore=0.5,
    max_spread=0.20,
    size=10.0,
    auto_execute=False,
    cooldown=30.0,
)

hz.run(pipeline=[scanner], ...)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `market_a` | `str` | required | First market ID |
| `market_b` | `str` | required | Second market ID |
| `feed_a` | `str` | required | Feed for market A |
| `feed_b` | `str` | required | Feed for market B |
| `lookback` | `int` | `100` | Rolling window size |
| `entry_zscore` | `float` | `2.0` | Z-score to enter |
| `exit_zscore` | `float` | `0.5` | Z-score to exit |
| `max_spread` | `float` | `0.20` | Max absolute spread to consider valid |
| `size` | `float` | `10.0` | Trade size per leg |
| `auto_execute` | `bool` | `False` | Auto-execute signals |
| `cooldown` | `float` | `30.0` | Seconds between trades |

## SpreadSignal

Stored in `ctx.params["last_spread_signal"]`.

| Field | Type | Description |
|-------|------|-------------|
| `market_a` | `str` | First market |
| `market_b` | `str` | Second market |
| `spread` | `float` | Current spread value |
| `spread_mean` | `float` | Rolling mean |
| `spread_std` | `float` | Rolling standard deviation |
| `zscore` | `float` | Current z-score |
| `signal` | `str` | `"long_a_short_b"`, `"long_b_short_a"`, `"exit"`, or `"hold"` |

## Signal Logic

| Condition | Position | Signal |
|-----------|----------|--------|
| `z > entry_zscore` | flat | `long_b_short_a` |
| `z < -entry_zscore` | flat | `long_a_short_b` |
| `|z| < exit_zscore` | positioned | `exit` |
| otherwise | any | `hold` |

## Example

```python
import horizon as hz

# Spread trade between two correlated political markets
spread = hz.spread_convergence(
    market_a="gop-senate",
    market_b="gop-house",
    feed_a="gop_senate_feed",
    feed_b="gop_house_feed",
    lookback=200,
    entry_zscore=2.5,
    exit_zscore=0.3,
    auto_execute=True,
)

hz.run(
    name="spread_trader",
    exchanges=[hz.Polymarket(private_key="0x...")],
    markets=["gop-senate", "gop-house"],
    feeds={
        "gop_senate_feed": hz.PolymarketBook("gop-senate"),
        "gop_house_feed": hz.PolymarketBook("gop-house"),
    },
    pipeline=[spread],
    interval=1.0,
)
```

<Note>
  For more rigorous pairs trading with cointegration testing and half-life filtering, see [Statistical Arb](/arbitrage/stat-arb).
</Note>
