> ## Documentation Index
> Fetch the complete documentation index at: https://mathematicalcompany.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Statistical Arb

> Cointegration-based pairs trading with OLS hedge ratio, ADF test, and half-life filtering.

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

# Statistical Arbitrage

A more rigorous version of [Spread Convergence](/arbitrage/spread-convergence). Uses OLS hedge ratio, ADF stationarity testing, and half-life filtering to identify truly cointegrated pairs.

## How It Works

1. Collect price histories for both markets
2. Compute OLS hedge ratio: `beta = cov(a,b) / var(b)`
3. Compute residuals: `r = a - beta * b`
4. Run ADF test on residuals (more negative = more stationary)
5. Check `signal_half_life()` on residuals (reject if outside bounds)
6. Compute z-score of current residual
7. Generate entry/exit/stop signals

## Rust Functions

### cointegration\_test

```python theme={null}
ratio, residuals, adf = hz.cointegration_test(prices_a, prices_b)
# ratio: OLS hedge ratio
# residuals: a - ratio * b
# adf: ADF statistic (more negative = more stationary)
```

### spread\_zscore

```python theme={null}
z = hz.spread_zscore(residuals, lookback=50)
# Z-score of the last residual over a rolling window
```

## StatArbConfig

```python theme={null}
config = hz.StatArbConfig(
    pair=("btc-100k", "eth-5k"),
    feeds=("btc_feed", "eth_feed"),
    lookback=200,
    entry_zscore=2.0,
    exit_zscore=0.5,
    stop_zscore=4.0,
    recalibrate_every=50,
    min_half_life=5.0,
    max_half_life=200.0,
)
```

| Parameter           | Type              | Default  | Description                      |
| ------------------- | ----------------- | -------- | -------------------------------- |
| `pair`              | `tuple[str, str]` | required | Market IDs                       |
| `feeds`             | `tuple[str, str]` | required | Feed names                       |
| `lookback`          | `int`             | `200`    | Price history window             |
| `entry_zscore`      | `float`           | `2.0`    | Entry threshold                  |
| `exit_zscore`       | `float`           | `0.5`    | Exit threshold                   |
| `stop_zscore`       | `float`           | `4.0`    | Stop-loss threshold              |
| `recalibrate_every` | `int`             | `50`     | Ticks between recalibration      |
| `min_half_life`     | `float`           | `5.0`    | Min half-life (reject if faster) |
| `max_half_life`     | `float`           | `200.0`  | Max half-life (reject if slower) |

## Pipeline: stat\_arb

```python theme={null}
scanner = hz.stat_arb(
    config=hz.StatArbConfig(
        pair=("btc-100k", "eth-5k"),
        feeds=("btc_feed", "eth_feed"),
    ),
    size=10.0,
    auto_execute=False,
    cooldown=30.0,
)

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

Stores `StatArbResult` in `ctx.params["last_stat_arb"]`.

## StatArbResult

| Field         | Type              | Description                                                             |
| ------------- | ----------------- | ----------------------------------------------------------------------- |
| `pair`        | `tuple[str, str]` | Market pair                                                             |
| `hedge_ratio` | `float`           | OLS hedge ratio                                                         |
| `zscore`      | `float`           | Current residual z-score                                                |
| `half_life`   | `float`           | Mean-reversion half-life                                                |
| `adf_stat`    | `float`           | ADF test statistic                                                      |
| `signal`      | `str`             | `"long_a_short_b"`, `"long_b_short_a"`, `"exit"`, `"stop"`, 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` |
| \`                  | z        | > stop\_zscore\`  | positioned | `stop` |
| otherwise           | any      | `hold`            |            |        |

## Example

```python theme={null}
import horizon as hz

config = hz.StatArbConfig(
    pair=("gop-senate", "gop-house"),
    feeds=("gop_senate_feed", "gop_house_feed"),
    lookback=300,
    entry_zscore=2.5,
    recalibrate_every=100,
    min_half_life=10.0,
    max_half_life=150.0,
)

scanner = hz.stat_arb(config, size=5.0, auto_execute=True, cooldown=60.0)

hz.run(
    name="stat_arb_pairs",
    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=[scanner],
    interval=1.0,
)
```

<Note>
  ADF critical values (n > 100): 1% = -3.43, 5% = -2.862, 10% = -2.567. More negative values indicate stronger stationarity evidence.
</Note>
