Data Catalog with Databento
Set up a Nautilus Parquet data catalog with market data from Databento. The catalog provides efficient storage and querying for backtests and research.
Prerequisites
- Python 3.12+
- NautilusTrader latest release installed (
pip install nautilus_trader) - databento Python client library (
pip install databento) - Databento account with API key set as
DATABENTO_API_KEY
Request data
Initialize a Databento historical client. The client reads your API key from
the DATABENTO_API_KEY environment variable by default.
import databento as db
client = db.Historical() # Uses the DATABENTO_API_KEY environment variableEvery historical streaming request from timeseries.get_range incurs a cost (even for the same data), so:
- Check the cost before making a request
- Avoid requesting the same data twice
- Write responses to disk as zstd compressed DBN files
Use the metadata get_cost endpoint to quote the cost before each request. Only request data that does not already exist on disk.
The response is in USD, displayed as fractional cents.
The following request is for a small amount of data (as used in this Medium article Building high-frequency trading signals in Python with Databento and sklearn) to demonstrate the workflow.
from pathlib import Path
from databento import DBNStoreWe'll prepare a directory for the raw Databento DBN format data, which we'll use for the rest of the tutorial.
DATABENTO_DATA_DIR = Path("databento")
DATABENTO_DATA_DIR.mkdir(exist_ok=True)# Request cost quote (USD) - this endpoint is 'free'
client.metadata.get_cost(
dataset="GLBX.MDP3",
symbols=["ES.n.0"],
stype_in="continuous",
schema="mbp-10",
start="2023-12-06T14:30:00",
end="2023-12-06T20:30:00",
)Use the historical API to request the data used in the Medium article.
path = DATABENTO_DATA_DIR / "es-front-glbx-mbp10.dbn.zst"
if not path.exists():
# Request data
client.timeseries.get_range(
dataset="GLBX.MDP3",
symbols=["ES.n.0"],
stype_in="continuous",
schema="mbp-10",
start="2023-12-06T14:30:00",
end="2023-12-06T20:30:00",
path=path, # <-- Passing a `path` writes the data to disk
)Read the data from disk and convert to a pandas.DataFrame
data = DBNStore.from_file(path)
df = data.to_df()
dfWrite to data catalog
import shutil
from pathlib import Path
from nautilus_trader.adapters.databento.loaders import DatabentoDataLoader
from nautilus_trader.model import InstrumentId
from nautilus_trader.persistence.catalog import ParquetDataCatalogCATALOG_PATH = Path.cwd() / "catalog"
# Clear if it already exists
if CATALOG_PATH.exists():
shutil.rmtree(CATALOG_PATH)
CATALOG_PATH.mkdir()
# Create a catalog instance
catalog = ParquetDataCatalog(CATALOG_PATH)Use a DatabentoDataLoader to decode and load the data into Nautilus objects.
loader = DatabentoDataLoader()Load Rust PyO3 objects by setting as_legacy_cython=False.
Passing an instrument_id is optional but speeds up loading by skipping symbology mapping. If provided, use the Nautilus symbol.venue format (e.g., "ES.GLBX").
path = DATABENTO_DATA_DIR / "es-front-glbx-mbp10.dbn.zst"
# Option 1 (recommended): Let the loader infer the instrument ID from DBN metadata
depth10 = loader.from_dbn_file(
path=path,
as_legacy_cython=False,
)
# Option 2: Explicitly specify a valid Nautilus instrument ID (symbol.venue format)
# instrument_id = InstrumentId.from_str("ESZ3.GLBX") # E-mini S&P December 2023 futures on Globex
# depth10 = loader.from_dbn_file(
# path=path,
# instrument_id=instrument_id,
# as_legacy_cython=False,
# )# Write data to catalog (this takes ~20 seconds or ~250,000/second for writing MBP-10 at the moment)
catalog.write_data(depth10)# Test reading from catalog
depths = catalog.order_book_depth10()
len(depths)Preparing a month of AAPL trades
Now we'll expand on this workflow by preparing a month of AAPL trades on the Nasdaq exchange using the Databento trade schema, which will translate to Nautilus TradeTick objects.
# Request cost quote (USD) - this endpoint is 'free'
client.metadata.get_cost(
dataset="XNAS.ITCH",
symbols=["AAPL"],
schema="trades",
start="2024-01",
)Pass a path parameter when requesting historical data to write it to disk.
path = DATABENTO_DATA_DIR / "aapl-xnas-202401.trades.dbn.zst"
if not path.exists():
# Request data
client.timeseries.get_range(
dataset="XNAS.ITCH",
symbols=["AAPL"],
schema="trades",
start="2024-01",
path=path, # <-- Passing a `path` parameter
)Read the data from disk and convert to a pandas.DataFrame
data = DBNStore.from_file(path)
df = data.to_df()
dfWe'll use an InstrumentId of "AAPL.XNAS", where XNAS is the ISO 10383 MIC (Market Identifier Code) for the Nasdaq venue.
Passing an instrument_id speeds up loading by skipping symbology mapping. Setting as_legacy_cython=False is more efficient when writing to the catalog.
instrument_id = InstrumentId.from_str("AAPL.XNAS")
trades = loader.from_dbn_file(
path=path,
instrument_id=instrument_id,
as_legacy_cython=False,
)Here we organize data as one file per month. A file per day works equally well.
# Write data to catalog
catalog.write_data(trades)trades = catalog.trade_ticks([instrument_id])len(trades)Loading External Data
Load CSV market data into the Parquet data catalog, then run a backtest with BacktestNode. This is a common workflow when you have historical data from an...
Configure a Live Trading Node
Set up a TradingNode for live market connectivity. For the node lifecycle, see Live trading. For command outcomes, see Execution. For state recovery, see...