Support

Calculate daily statistics for equity options

Overview

In this example, we'll use the Historical client to calculate volume and open interest statistics for an equity options chain.

First, we'll use the definition schema, which contains instrument definitions and properties, to get information such as expiration, strike_price, and instrument_class. Next, we'll use the OHLCV-1d schema to get the total volume for each option. Finally, we'll use the statistics schema to request open interest statistics. OPRA publishes start-of-day open interest statistics before the regular trading hours (RTH) open at 9:30 ET.

Example

import datetime as dt from zoneinfo import ZoneInfo import databento as db import matplotlib.pyplot as plt import pandas as pd from matplotlib.patches import Patch from matplotlib.ticker import MaxNLocator def get_volume_data( client: db.Historical, dataset: str, symbol: str, date: dt.date, ) -> pd.DataFrame: """Get volume data for all options for a given symbol.""" volume_df = client.timeseries.get_range( dataset=dataset, symbols=f"{symbol}.OPT", schema="ohlcv-1d", stype_in="parent", start=date, ).to_df() return volume_df.groupby("symbol")["volume"].sum().reset_index() def get_oi_data( client: db.Historical, dataset: str, symbol: str, date: dt.date, ) -> pd.DataFrame: """Get open interest data for all options for a given symbol.""" # Start-of-day open interest is published before the RTH open at 9:30 ET end_time = dt.time(9, 30, 0, tzinfo=ZoneInfo("America/New_York")) stats_df = client.timeseries.get_range( dataset=dataset, symbols=f"{symbol}.OPT", schema="statistics", stype_in="parent", start=date, end=dt.datetime.combine(date, end_time), ).to_df() stats_df = stats_df[stats_df["stat_type"] == db.StatType.OPEN_INTEREST] stats_df = stats_df.drop_duplicates("symbol", keep="last") stats_df["open_interest"] = stats_df[stats_df["stat_type"] == db.StatType.OPEN_INTEREST]["quantity"] return stats_df[["open_interest", "symbol"]] def get_definition_data( client: db.Historical, dataset: str, symbol: str, date: dt.date, ) -> pd.DataFrame: """Get definition data for all options for a given symbol.""" def_df = client.timeseries.get_range( dataset=dataset, symbols=f"{symbol}.OPT", schema="definition", stype_in="parent", start=date, ).to_df() def_df["days_to_expiration"] = (def_df["expiration"] - def_df.index.normalize()).dt.days def_df["expiration"] = def_df["expiration"].dt.normalize().dt.date return def_df[["symbol", "strike_price", "instrument_class", "expiration", "days_to_expiration"]] def plot_oi_by_strike( df: pd.DataFrame, expiration_date: dt.date, ) -> None: df = df[df["expiration"] == expiration_date] trade_date = df["trade_date"].iloc[0] days_to_expiration = df["days_to_expiration"].iloc[0] df_strikes = df.groupby(["strike_price", "instrument_class"])["open_interest"].sum().unstack() # Plot bar chart fig, ax = plt.subplots(figsize=(14, 6)) df_strikes.plot( ax=ax, kind="bar", xlabel="Strike price", ylabel="Open interest", ) # Adjust X ticks and labels ax.xaxis.set_major_locator(MaxNLocator(15)) ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha="right") # Set title and legend ax.set_title(f"{symbol} open interest \n {trade_date} ({days_to_expiration} DTE)") ax.legend(handles=[Patch(facecolor="C0", label="Call"), Patch(facecolor="C1", label="Put")]) plt.tight_layout() plt.show() # Set parameters dataset = "OPRA.PILLAR" symbol = "NVDA" start = dt.date(2025, 8, 19) # Create a historical client client = db.Historical("$YOUR_API_KEY") # Get strike price, expiration, and instrument class in the definition schema def_df = get_definition_data(client, dataset, symbol, start) # Volume from the OHLCV-1d schema volume_df = get_volume_data(client, dataset, symbol, start) # Start-of-day open interest is published in the morning in the statistics schema stats_df = get_oi_data(client, dataset, symbol, start) # Merge different schemas together df = def_df.merge(volume_df, on="symbol", how="left") df = df.merge(stats_df, on="symbol", how="left") df["trade_date"] = start df["volume"] = df["volume"].fillna(0).astype(int) df["open_interest"] = df["open_interest"].fillna(0).astype(int) # Print out top 10 contracts by open interest print(df.nlargest(10, "open_interest").to_string(index=False)) # Plot open interest by expiration date plot_oi_by_strike(df, dt.date(2025, 8, 22)) 

Result

You could use the resulting data to filter for the contracts with the highest open interest. Additionally, you could plot the open interest distribution for a given expiration.

 symbol strike_price instrument_class expiration days_to_expiration volume open_interest trade_date NVDA 250919C00160000 160.0 C 2025-09-19 31 52206 173864 2025-08-19 NVDA 260618P00075000 75.0 P 2026-06-18 303 1 109796 2025-08-19 NVDA 250919C00170000 170.0 C 2025-09-19 31 4636 106760 2025-08-19 NVDA 250822C00185000 185.0 C 2025-08-22 3 158486 102111 2025-08-19 NVDA 250919C00185000 185.0 C 2025-09-19 31 61860 101843 2025-08-19 NVDA 250919C00190000 190.0 C 2025-09-19 31 59766 92379 2025-08-19 NVDA 250822C00190000 190.0 C 2025-08-22 3 46489 91666 2025-08-19 NVDA 250919C00165000 165.0 C 2025-09-19 31 759 90851 2025-08-19 NVDA 250919C00175000 175.0 C 2025-09-19 31 5862 85548 2025-08-19 NVDA 260116C00100000 100.0 C 2026-01-16 150 161 85414 2025-08-19 

Options venues volume