Resample OHLCV from 1-minute to 5-minute
Databento follows a convention in the OHLCV schema to only publish a record when a trade occurs in the interval.
This approach is adopted by most data vendors for two key reasons:
- Multiple interpolation strategies exist and the optimal choice depends on the specific use case. Client-side interpolation keeps the strategy transparent.
- This reduces storage and bandwidth requirements, especially for markets with many illiquid instruments like options.
This example demonstrates one way to interpolate OHLCV-1m data to ensure exactly one row per minute. The interpolation strategy used in this example does the following:
- Forward-fills the close price from the last known value.
- Sets open, high, and low equal to the forward-filled close price.
- Sets volume to 0 for interpolated periods.
Overview
In this example, we'll use the historical client to request data from the OHLCV-1m schema. First, we'll highlight how to interpolate missing rows in the 1-minute data. Next, we'll show how to resample the data to 5-minute bars.
Note that while this example is showing these methods being used individually, you can also chain these methods together.
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
import databento as db
import pandas as pd
def interpolate_ohlcv(
df: pd.DataFrame,
start: pd.Timestamp,
end: pd.Timestamp,
interp_interval: str,
) -> pd.DataFrame:
"""
Interpolate OHLCV records between `start` and `end`, since Databento only sends
an OHLCV record if a trade happens in that interval.
"""
def _interpolate_group(group):
"""Interpolate OHLCV records for each group"""
# Reindex with a complete index using specified start/end times
group = group.reindex(
pd.date_range(
start=start,
end=end,
freq=interp_interval,
inclusive="left",
).rename(group.index.name),
)
# Forward fill close prices (may remain NaN if no prior data exists)
group["close"] = group["close"].ffill()
# For intervals with no trades, set open/high/low equal to the close and volume to 0
group = group.fillna(
{
**{col: group["close"] for col in ["open", "high", "low"]},
"volume": 0,
},
)
group["volume"] = group["volume"].astype(int)
group = group.drop(columns=["rtype", "instrument_id"], errors="ignore")
return group
df = (
df.groupby(["publisher_id", "symbol"])
.apply(_interpolate_group, include_groups=False)
.reset_index(["publisher_id", "symbol"])
.sort_values(["ts_event", "publisher_id", "symbol"])
)
return df
def resample_ohlcv(
df: pd.DataFrame,
resample_interval: str,
) -> pd.DataFrame:
"""Resample OHLCV bars to the specified interval"""
resampled_df = (
df.groupby(["publisher_id", "symbol"])
.resample(resample_interval)
.agg(
{
"open": "first",
"high": "max",
"low": "min",
"close": "last",
"volume": "sum",
},
)
.reset_index(["publisher_id", "symbol"])
.sort_values(["ts_event", "publisher_id", "symbol"])
)
return resampled_df
# Set parameters
start = pd.Timestamp("2024-12-17T09:30:00", tz="US/Eastern")
end = pd.Timestamp("2024-12-17T16:00:00", tz="US/Eastern")
# Create a historical client
client = db.Historical("$YOUR_API_KEY")
# Request OHLCV-1m data for all AAPL options and convert to DataFrame
df = client.timeseries.get_range(
dataset="OPRA.PILLAR",
schema="ohlcv-1m",
symbols="AAPL.OPT",
stype_in="parent",
start=start,
end=end,
).to_df(tz=start.tzinfo)
# Interpolate missing rows.
df1 = interpolate_ohlcv(df, start, end, "1min")
print(df1)
# Resample to 5-minute bars
df2 = resample_ohlcv(df, "5min")
print(df2) Result
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
publisher_id symbol open high low close volume
ts_event
2024-12-17 09:30:00-05:00 20 AAPL 241220C00115000 NaN NaN NaN NaN 0
2024-12-17 09:30:00-05:00 20 AAPL 241220C00155000 NaN NaN NaN NaN 0
2024-12-17 09:30:00-05:00 20 AAPL 241220C00170000 NaN NaN NaN NaN 0
2024-12-17 09:30:00-05:00 20 AAPL 241220C00195000 NaN NaN NaN NaN 0
2024-12-17 09:30:00-05:00 20 AAPL 241220C00200000 NaN NaN NaN NaN 0
... ... ... ... ... ... ... ...
2024-12-17 15:59:00-05:00 61 AAPL 261218C00240000 50.70 50.70 50.70 50.70 0
2024-12-17 15:59:00-05:00 61 AAPL 261218P00050000 0.29 0.29 0.29 0.29 0
2024-12-17 15:59:00-05:00 61 AAPL 270115C00240000 50.14 50.14 50.14 50.14 0
2024-12-17 15:59:00-05:00 61 AAPL 270115C00250000 44.34 44.34 44.34 44.34 0
2024-12-17 15:59:00-05:00 61 AAPL 270115C00440000 3.50 3.50 3.50 3.50 0
[3129360 rows x 7 columns]
publisher_id symbol open high low close volume
ts_event
2024-12-17 09:30:00-05:00 20 AAPL 241220C00220000 30.70 30.70 30.70 30.70 1
2024-12-17 09:30:00-05:00 20 AAPL 241220C00225000 25.95 25.95 25.95 25.95 2
2024-12-17 09:30:00-05:00 20 AAPL 241220C00242500 8.20 8.20 8.20 8.20 1
2024-12-17 09:30:00-05:00 20 AAPL 241220C00245000 6.30 6.30 6.17 6.17 4
2024-12-17 09:30:00-05:00 20 AAPL 241220C00247500 3.70 3.85 3.70 3.85 2
... ... ... ... ... ... ... ...
2024-12-17 15:55:00-05:00 61 AAPL 250124C00255000 5.95 5.95 5.95 5.95 1
2024-12-17 15:55:00-05:00 61 AAPL 250124C00285000 0.30 0.30 0.30 0.30 47
2024-12-17 15:55:00-05:00 61 AAPL 250124P00235000 1.01 1.01 1.01 1.01 1
2024-12-17 15:55:00-05:00 61 AAPL 250321C00225000 33.90 33.90 33.90 33.90 1
2024-12-17 15:55:00-05:00 61 AAPL 250919P00105000 0.33 0.33 0.33 0.33 2
[231713 rows x 7 columns]