Source: https://databento.com/docs/examples/futures/retrieving-oi-and-settlement-prices


# Volume, open interest, and settlement prices

The [statistics schema](https://databento.com/docs/schemas/statistics.md) provides official summary statistics of each instrument that's published by the venue.
Each statistics record contains the value or values associated with only a single statistics type, indicated by the `stat_type` field.
This makes the schema flexible and forwards-compatible, but can make it harder to work with.

## Overview

In this example we will use the [Historical client](https://databento.com/docs/api-historical/client.md) to retrieve daily statistics for a [parent symbol](https://databento.com/docs/conventions/symbology.md#parent).

We'll download statistics data for a parent symbol and use the `StatType` enum to filter for cleared volume, open interest, and settlement price statistics.
Next, we'll filter for statistics on outright futures only, using data found in the [definition schema](https://databento.com/docs/schemas/instrument-definitions.md).
Then, we'll group statistics by symbol and date to create a table for each date and symbol.

In the end, we'll generate a plot which shows open interest and cleared volume over time.

## ts_ref

Many statistics are published daily or refer to a specific trading session.
When provided by the publisher, the trading session associated with the statistic is indicated with the `ts_ref` field.

Depending on the dataset, statistics can be published at various times, so you may need to look at the next UTC date for a given statistic.
For statistics that are published multiple times, you should always refer to the most recently published value.

> **See also**
>
> See our [statistics schedule example](https://databento.com/docs/examples/futures/statistics-schedule.md) which shows how you can determine when the various statistics types are published.

## Example

```python
import databento as db
import matplotlib.pyplot as plt

# Set parameters
dataset = "GLBX.MDP3"
product = "ZC"
start = "2025-05-04"
end = "2025-05-19"

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

# Request definition data and convert to DataFrame
def_df = client.timeseries.get_range(
    dataset=dataset,
    schema="definition",
    symbols=f"{product}.FUT",
    stype_in="parent",
    start=start,
    end=end,
).to_df()

# Filter for outright futures only and keep the last definition
def_df = def_df[def_df["instrument_class"] == db.InstrumentClass.FUTURE]
def_df = def_df[["instrument_id", "raw_symbol", "expiration"]]
def_df = def_df.drop_duplicates(subset=["instrument_id"], keep="last")

# Request statistics data and convert to DataFrame
stats_df = client.timeseries.get_range(
    dataset=dataset,
    schema="statistics",
    symbols=f"{product}.FUT",
    stype_in="parent",
    start=start,
    end=end,
).to_df()

# Filter for daily statistics on outright futures
stats_df = stats_df.merge(def_df, on="instrument_id")
stats_df = stats_df.rename(columns={"raw_symbol": "Symbol"})

# Only keep the date component of `ts_ref`
stats_df["Trade date"] = stats_df["ts_ref"].dt.date

# Create columns for each statistic
stats_df["Settlement price"] = stats_df[stats_df["stat_type"] == db.StatType.SETTLEMENT_PRICE][
    "price"
]
stats_df["Cleared volume"] = stats_df[stats_df["stat_type"] == db.StatType.CLEARED_VOLUME][
    "quantity"
]
stats_df["Open interest"] = stats_df[stats_df["stat_type"] == db.StatType.OPEN_INTEREST]["quantity"]

# Groupby and keep the last published record for each statistic
stats_df = (
    stats_df.groupby(["Trade date", "Symbol"]).agg("last").sort_values(["Trade date", "expiration"])
)

stats_df["Cleared volume"] = stats_df["Cleared volume"].fillna(0).astype(int)
stats_df["Open interest"] = stats_df["Open interest"].fillna(0).astype(int)
stats_df = stats_df[["Settlement price", "Cleared volume", "Open interest"]]

print(stats_df)

# Now, we'll plot aggregated volume and open interest statistics for this product
date_df = stats_df[["Cleared volume", "Open interest"]].groupby("Trade date").agg("sum")

ax_vol = date_df["Cleared volume"].plot.bar(ylabel="Cleared volume")
ax_oi = (
    date_df["Open interest"]
    .reset_index()
    .plot.line(
        ax=ax_vol,
        color="C1",
        marker="o",
        ylabel="Open interest",
        secondary_y=True,
        mark_right=False,
        legend=False,
    )
)

plt.gcf().legend(bbox_to_anchor=(0, 1), loc="upper left", bbox_transform=ax_vol.transAxes)
plt.setp([ax_vol.get_xticklabels(), ax_oi.get_xticklabels()], rotation=45)
plt.title(f"{product} cleared volume and open interest")
plt.tight_layout()
plt.show()
```
## Result

```plaintext
                   Settlement price  Cleared volume  Open interest
Trade date Symbol
2025-05-02 ZCK5              461.25            2624           1966
           ZCN5              469.00          163656         665836
           ZCU5              440.00           60030         314718
           ZCZ5              450.25           93701         432738
           ZCH6              464.50           10615          50264
...                             ...             ...            ...
2025-05-16 ZCN7              475.25              12            139
           ZCU7              453.75               5             12
           ZCZ7              455.50              17            630
           ZCN8              474.50               0              7
           ZCZ8              458.00               0             18

[174 rows x 3 columns]
```
The table is similar to the one found on the [CME website](https://www.cmegroup.com/markets/agriculture/grains/corn.volume.html) that shows these statistics for every active contract.

The plot shows the aggregated volume and open interest for all active contracts for a product.

![Statistics plot](https://databento.com/docs/retrieving-oi-and-settlement-prices-0.svg.md)
