---
title: "Volatility Suite"
description: "Multiple volatility estimators for prediction markets: close-to-close, Parkinson, Garman-Klass, Yang-Zhang, EWMA, and rolling. Rust-native with Python pipeline integration."
icon: "chart-line"
---

# Volatility Suite

Horizon provides six volatility estimators implemented in Rust, plus a `hz.volatility()` pipeline function that computes all of them per cycle and injects a `VolatilitySnapshot` into your strategy context.

<Note>
  Prediction markets trade 24/7 on a 365-day calendar. All estimators use `sqrt(365)` for annualization by default, which differs from the `sqrt(252)` convention used in equity markets.
</Note>

## Overview

<CardGroup cols={2}>
  <Card title="Close-to-Close" icon="chart-simple">
    `hz.estimate_volatility()` - standard deviation of log returns. Simple, widely used.
  </Card>
  <Card title="Parkinson" icon="arrows-up-down">
    `hz.parkinson_vol()` - high/low range estimator. ~5x more efficient than close-to-close.
  </Card>
  <Card title="Garman-Klass" icon="chart-bar">
    `hz.garman_klass_vol()` - OHLC estimator. Most efficient standard estimator.
  </Card>
  <Card title="Yang-Zhang" icon="layer-group">
    `hz.yang_zhang_vol()` - combines overnight jump + Rogers-Satchell. Handles opening gaps.
  </Card>
  <Card title="EWMA" icon="bolt">
    `hz.ewma_vol()` - exponentially weighted, reactive to recent changes.
  </Card>
  <Card title="Rolling" icon="wave-pulse">
    `hz.rolling_vol()` - windowed realized vol series for time-varying analysis.
  </Card>
</CardGroup>

---

## Rust Functions

All functions return `0.0` on insufficient or invalid data. All accept an `annualize` parameter.

### hz.estimate_volatility

Standard deviation of log returns (close-to-close).

```python
import horizon as hz

vol = hz.estimate_volatility(
    prices=[0.50, 0.52, 0.48, 0.51, 0.49],
    annualize=True,  # default: False (backward compatible)
)
```

### hz.parkinson_vol

Parkinson high/low range estimator. More efficient than close-to-close because it uses intra-period range information.

```python
vol = hz.parkinson_vol(
    highs=[0.55, 0.56, 0.54],
    lows=[0.45, 0.44, 0.46],
    annualize=True,  # default: True
)
```

### hz.garman_klass_vol

Garman-Klass OHLC estimator. The most statistically efficient standard estimator using open, high, low, close data.

```python
vol = hz.garman_klass_vol(
    opens=[0.50, 0.52, 0.48],
    highs=[0.55, 0.56, 0.54],
    lows=[0.45, 0.44, 0.46],
    closes=[0.52, 0.48, 0.51],
    annualize=True,  # default: True
)
```

### hz.yang_zhang_vol

Yang-Zhang estimator combining overnight jump variance, open-to-close variance, and Rogers-Satchell range variance. The most robust OHLC estimator, especially when opening gaps are present.

```python
vol = hz.yang_zhang_vol(
    opens=[0.50, 0.52, 0.48, 0.51],
    highs=[0.55, 0.56, 0.54, 0.57],
    lows=[0.45, 0.44, 0.46, 0.43],
    closes=[0.52, 0.48, 0.51, 0.53],
    annualize=True,  # default: True
)
```

<Note>
  Yang-Zhang requires at least 2 bars (for overnight returns). Returns 0.0 with fewer.
</Note>

### hz.ewma_vol

Exponentially weighted moving average volatility. More reactive to recent price changes than equal-weighted estimators.

```python
vol = hz.ewma_vol(
    prices=[0.50, 0.52, 0.48, 0.51, 0.49, 0.53],
    span=20,          # EWMA span (default: 20)
    annualize=True,   # default: True
)
```

### hz.rolling_vol

Rolling windowed realized volatility series. Returns a list of vol values, one per window position.

```python
vols = hz.rolling_vol(
    prices=[0.50, 0.52, 0.48, 0.51, 0.49, 0.53, 0.47],
    window=3,          # rolling window size (default: 20)
    annualize=True,    # default: True
)
# vols is a list of floats, one per window position
```

---

## Pipeline Function

The `hz.volatility()` pipeline function computes all estimators per cycle from a feed and injects a `VolatilitySnapshot` into `ctx.params["vol"]`.

```python
import horizon as hz

def model(ctx):
    vol = ctx.params.get("vol")
    if vol and vol.best > 0:
        spread = vol.best * 2  # scale spread by vol
        return hz.quotes(fair=ctx.feeds["poly"].price, spread=spread)
    return []

hz.run(
    pipeline=[
        hz.volatility("poly", lookback=100, ewma_span=20),
        model,
    ],
    feeds={"poly": hz.PolymarketBook(token_id="0x123...")},
    markets=[hz.Market(id="0x123...", name="Example")],
)
```

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `feed` | `str` | required | Feed name to read prices from |
| `lookback` | `int` | `100` | Maximum price history length per market |
| `ewma_span` | `int` | `20` | EWMA span parameter |
| `rolling_window` | `int` | `20` | Rolling vol window size |
| `annualize` | `bool` | `True` | Multiply by sqrt(365) |

### VolatilitySnapshot

The snapshot injected into `ctx.params["vol"]` is a frozen dataclass:

```python
from horizon import VolatilitySnapshot

snap = VolatilitySnapshot(
    realized=0.15,
    parkinson=0.12,
    garman_klass=0.11,
    yang_zhang=0.13,
    ewma=0.14,
    rolling=0.12,
)

snap.best       # 0.13 (yang_zhang - highest priority non-zero)
snap.as_dict()  # dict with all fields + "best"
```

**Best priority order:** yang_zhang > garman_klass > parkinson > ewma > realized

---

## Choosing an Estimator

| Estimator | Data Needed | Efficiency | Best For |
|-----------|-------------|------------|----------|
| Close-to-close | Prices only | 1x | Tick data, simple strategies |
| Parkinson | High/Low | ~5x | Range-based analysis |
| Garman-Klass | OHLC | ~8x | When you have proper OHLC bars |
| Yang-Zhang | OHLC | ~14x | Markets with opening gaps |
| EWMA | Prices | Adaptive | Reactive volatility tracking |
| Rolling | Prices | 1x/window | Time-varying vol analysis |

The pipeline's `best` property automatically selects the highest-quality available estimate.
