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

# Implied vs realised

> Join IV and RV into one frame and read the variance premium.

Implied vol is what the market charges for optionality; realised vol is what the underlying delivered. The gap
between them, the **variance premium**, is one of the most-watched numbers in options. This guide builds it
from two requests.

## 1. Fetch both series

```python theme={null}
import os

import pandas as pd
import requests

BASE_URL = "https://api.sandmark.com/volatility"
session = requests.Session()
session.headers["X-API-Key"] = os.environ["SANDMARK_API_KEY"]


def get_series(path: str, value: str, **params) -> pd.Series:
    """Fetch one timeseries and return it oldest-first, indexed by UTC timestamp, in vol points."""
    response = session.get(
        f"{BASE_URL}{path}", params={"limit": 5000, **params}, timeout=60
    )
    response.raise_for_status()
    frame = pd.DataFrame(response.json()["data"])
    frame["DATETIME"] = pd.to_datetime(frame["DATETIME"], utc=True)
    return frame.set_index("DATETIME")[value].sort_index() * 100


iv_1m = get_series(
    "/v1/timeseries/iv", "IV",
    asset="DER.BTC_USD", frequency="1h",
    option_type="C", tenor_label="1m", delta_target_pct=50,
)
rv_30d = get_series(
    "/v1/timeseries/rv", "RV",
    asset="DER.BTC_USD", frequency="1h", lookback_days=30,
)
```

## 2. Align them

> [!NOTE]
>
> `get_series` makes one request, so it returns the newest 5,000 rows (about seven months). To cover a longer
> history window, swap in `fetch_all` from [Paging through history](https://docs.sandmark.com/guides/paging.md).

Both series are hourly and stamped at the bar close, so an inner join on the index lines them up. Hours that
are missing from either series drop out instead of being filled.

```python theme={null}
vol = pd.concat({"iv_1m": iv_1m, "rv_30d": rv_30d}, axis=1, join="inner")
vol["premium"] = vol["iv_1m"] - vol["rv_30d"]

print(vol.tail())
print(f"Average premium: {vol['premium'].mean():.2f} vol")
```

```text theme={null}
                               iv_1m     rv_30d   premium
DATETIME
2026-11-06 18:00:00+00:00  37.910316  35.216373  2.693943
2026-11-06 19:00:00+00:00  37.754108  35.201088  2.553020
2026-11-06 20:00:00+00:00  37.528775  35.204043  2.324731
2026-11-06 21:00:00+00:00  37.325808  35.204132  2.121675
2026-11-06 22:00:00+00:00  37.318442  35.186519  2.131923
Average premium: -0.68 vol
```

## 3. Read it two ways

> [tab] Same-time spread
>
> `premium` above compares today's implied with the **trailing** 30 days. It tells you whether options are
> rich or cheap against recent movement, which is what most desks quote day to day.

> [tab] Forecast check
>
> To ask "did implied vol over- or under-forecast?", compare IV at *t* with the RV that was realised over
> the *next* 30 days, which is RV stamped at *t* + 30 days:
>
> ```python theme={null}
> realised_next_30d = vol["rv_30d"].shift(freq="-30D")
> forecast_error = (vol["iv_1m"] - realised_next_30d).dropna()
> ```
>
> Only points at least 30 days old have an answer yet, so this series ends 30 days before today. On a
> monthly plan it will be empty, because your history window is no longer than the lookback.

> [!TIP]
>
> Your history window caps both series. On a monthly plan you get about 730 hourly points, enough for the
> same-time spread. Quarterly and annual plans add the depth needed for regime comparisons and the forecast
> check. See [History window](https://docs.sandmark.com/concepts/history-window.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.