Support

Get options chain for a futures product

Identifying all options for a futures product can be difficult because exchange naming conventions may split the options chain across different root symbols. On CME, this can occur in two ways:

  • Futures and options may use entirely different roots, e.g., CL.FUT and LO.OPT for crude oil.
  • Certain expirations may appear under a different root symbol than their underlying. E1AQ5 C5125 is a weekly option on ESU5 but has the root E1A, so it is included in E1A.OPT instead of ES.OPT.
See also
See also

CME's Weekly Options on Futures page provides more examples of the second scenario above, where weekly contracts are denoted by different root symbols than their underlying futures.

Overview

This example demonstrates how to use the historical API to find all options linked to a futures product, even when they're listed under different root symbols.

We’ll request data using the definition schema for all symbols, where the asset field represents the product's root symbol and the instrument_class field identifies its outright futures. We’ll then retrieve every option whose underlying matches any of those futures for the complete options chain. We'll also use the expiration field to demonstrate how to filter these options by their Days to Expiration (DTE).

Example

from typing import Optional
import databento as db
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.patches import Patch

def get_futures(
    product: str,
    def_df: pd.DataFrame,
) -> pd.DataFrame:
    """
    Get definitions for all outright futures for a product.
    """
    outright_futures = def_df[
        (def_df["instrument_class"] == db.InstrumentClass.FUTURE) &
        (def_df["asset"] == product)
    ]

    return outright_futures


def get_options(
    product: str,
    def_df: pd.DataFrame,
    expiration_filter: Optional[pd.Interval],
) -> pd.DataFrame:
    """
    Get definitions for all options for a product.
    Optionally filter by expiration.
    """
    # Get the symbols for outright futures for the product
    outright_futures = get_futures(product, def_df)["raw_symbol"].to_list()

    # Get all options that have any of the outright futures as an underlying
    options_df = def_df[
        (def_df["underlying"].isin(outright_futures)) &
        (def_df["instrument_class"].isin(["C", "P"]))
    ].copy()

    # Create a `days_to_expiration` field from `ts_recv` and `expiration`
    options_df["days_to_expiration"] = (
        options_df["expiration"].dt.normalize() - options_df.index.normalize()
    ).dt.days

    if expiration_filter is None:
        return options_df

    # Filter for specified expiration range
    filtered_options = options_df[
        options_df["days_to_expiration"].between(
            expiration_filter.left,
            expiration_filter.right,
            inclusive="both",
        )
    ]

    return filtered_options

def plot_volume_by_dte(
    product: str,
    date: str,
    def_df: pd.DataFrame,
    ohlcv_df: pd.DataFrame,
) -> None:
    dte_ranges = [
        (0, 0, "0"),
        (1, 2, "1-2"),
        (3, 4, "3-4"),
        (5, 9, "5-9"),
        (10, 1000, "10+"),
    ]

    # Create pie chart
    fig, ax = plt.subplots(figsize=(10, 6))
    dte_handles = []
    dte_volumes = []

    for idx, (start, end, label) in enumerate(dte_ranges):
        defs = get_options(product, def_df, pd.Interval(start, end, closed="both"))
        vol = ohlcv_df[ohlcv_df["instrument_id"].isin(defs["instrument_id"])]["volume"].sum()
        patch = Patch(facecolor=f"C{idx}", label=f"{label} DTE")

        dte_handles.append(patch)
        dte_volumes.append(vol)

    ax.pie(dte_volumes, autopct="%1.1f%%", startangle=90, pctdistance=1.2)
    ax.set_title(f"{product} options volume on {date} by Days To Expiration (DTE)")
    fig.legend(handles=dte_handles, loc="upper left", bbox_to_anchor=(0.1, 0.9))

    plt.show()

# Set parameters
dataset = "GLBX.MDP3"
product = "ES"
start = "2025-08-04"

# Create a historical client
client = db.Historical("$YOUR_API_KEY")

# Download ohlcv-1d data for ALL_SYMBOLS for one day
ohlcv_df = client.timeseries.get_range(
    dataset=dataset,
    schema="ohlcv-1d",
    symbols="ALL_SYMBOLS",
    start=start,
).to_df()

# Download definition data for ALL_SYMBOLS for one day
def_df = client.timeseries.get_range(
    dataset=dataset,
    schema="definition",
    symbols="ALL_SYMBOLS",
    start=start,
).to_df()


# Create a range of DTE to filter for
# You can filter for 1-3 DTE with pd.Interval(1, 3, closed="both")
zero_dte_options = get_options(product, def_df, pd.Interval(0, 0, closed="both"))
print(zero_dte_options[["raw_symbol", "expiration", "strike_price", "days_to_expiration"]])

# Now, plot of total volume by different DTE ranges
plot_volume_by_dte(product, start, def_df, ohlcv_df)

Result

                            raw_symbol                expiration  strike_price  days_to_expiration
ts_recv
2025-08-04 00:00:00+00:00  E1AQ5 C5125 2025-08-04 20:00:00+00:00        5125.0                   0
2025-08-04 00:00:00+00:00  E1AQ5 P6600 2025-08-04 20:00:00+00:00        6600.0                   0
2025-08-04 00:00:00+00:00  E1AQ5 C4100 2025-08-04 20:00:00+00:00        4100.0                   0
2025-08-04 00:00:00+00:00  E1AQ5 C7200 2025-08-04 20:00:00+00:00        7200.0                   0
2025-08-04 00:00:00+00:00  E1AQ5 C4700 2025-08-04 20:00:00+00:00        4700.0                   0
...                                ...                       ...           ...                 ...
2025-08-04 00:00:00+00:00  E1AQ5 P4400 2025-08-04 20:00:00+00:00        4400.0                   0
2025-08-04 00:00:00+00:00  E1AQ5 P7600 2025-08-04 20:00:00+00:00        7600.0                   0
2025-08-04 00:00:00+00:00  E1AQ5 P5735 2025-08-04 20:00:00+00:00        5735.0                   0
2025-08-04 00:00:00+00:00  E1AQ5 P5715 2025-08-04 20:00:00+00:00        5715.0                   0
2025-08-04 00:00:00+00:00  E1AQ5 P5075 2025-08-04 20:00:00+00:00        5075.0                   0

[626 rows x 4 columns]

You could use the raw_symbol values from this filtered list and pass them into another data request. This is demonstrated below which uses the OHLCV-1d schema to calculate the total volume for all options based on Days to Expiration (DTE).

DTE volume plot