Skip to content

Commit be93e2b

Browse files
committed
Add support for data download during backtest and refactor of catalog
1 parent d81a4d2 commit be93e2b

40 files changed

+2624
-1425
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
# ---
2+
# jupyter:
3+
# jupytext:
4+
# formats: py:percent
5+
# text_representation:
6+
# extension: .py
7+
# format_name: percent
8+
# format_version: '1.3'
9+
# jupytext_version: 1.17.1
10+
# kernelspec:
11+
# display_name: Python 3 (ipykernel)
12+
# language: python
13+
# name: python3
14+
# ---
15+
16+
# %% [markdown]
17+
# # Databento Data Client with Backtest Node
18+
#
19+
# This example demonstrates how to use the Databento data client with a backtest node.
20+
21+
# %% [markdown]
22+
# ## Imports
23+
24+
# %%
25+
# Note: Use the python extension jupytext to be able to open this python file in jupyter as a notebook
26+
27+
# %%
28+
import asyncio
29+
30+
import nautilus_trader.adapters.databento.data_utils as db_data_utils
31+
from nautilus_trader.adapters.databento.config import DatabentoDataClientConfig
32+
from nautilus_trader.adapters.databento.factories import DatabentoLiveDataClientFactory
33+
from nautilus_trader.backtest.config import BacktestEngineConfig
34+
from nautilus_trader.backtest.config import BacktestRunConfig
35+
from nautilus_trader.backtest.config import BacktestVenueConfig
36+
from nautilus_trader.backtest.node import BacktestNode
37+
from nautilus_trader.common.config import LoggingConfig
38+
from nautilus_trader.common.enums import LogColor
39+
from nautilus_trader.config import ImportableStrategyConfig
40+
from nautilus_trader.config import StrategyConfig
41+
from nautilus_trader.core.datetime import unix_nanos_to_iso8601
42+
from nautilus_trader.live.config import RoutingConfig
43+
from nautilus_trader.model.data import Bar
44+
from nautilus_trader.model.data import BarType
45+
from nautilus_trader.model.enums import OrderSide
46+
from nautilus_trader.model.identifiers import InstrumentId
47+
from nautilus_trader.model.objects import Quantity
48+
from nautilus_trader.persistence.config import DataCatalogConfig
49+
from nautilus_trader.trading.strategy import Strategy
50+
51+
52+
# %%
53+
# We need to use nest_asyncio in a jupyter notebook to be able to run async code as sync for market data
54+
# requests in a backtest
55+
try:
56+
asyncio.get_running_loop()
57+
except RuntimeError:
58+
pass # No loop running
59+
else:
60+
import nest_asyncio
61+
62+
nest_asyncio.apply()
63+
64+
# %% [markdown]
65+
# ## Parameters
66+
67+
# %%
68+
# Set the data path for Databento data
69+
# DATA_PATH = "/path/to/your/data" # Use your own value here
70+
# db_data_utils.DATA_PATH = DATA_PATH
71+
72+
catalog_folder = "futures_catalog"
73+
catalog = db_data_utils.load_catalog(catalog_folder)
74+
75+
future_symbols = ["ESM4"]
76+
77+
# Small amount of data for testing
78+
start_time = "2024-05-09T10:00"
79+
end_time = "2024-05-09T10:10"
80+
81+
# A valid databento key can be entered here (or as an env variable of the same name)
82+
# DATABENTO_API_KEY = None
83+
# db_data_utils.init_databento_client(DATABENTO_API_KEY)
84+
85+
# # # Ensure data is available
86+
# futures_data = databento_data(
87+
# future_symbols,
88+
# start_time,
89+
# end_time,
90+
# "definition", # "ohlcv-1m"
91+
# "futures",
92+
# catalog_folder,
93+
# )
94+
95+
# %% [markdown]
96+
# ## Strategy
97+
98+
99+
# %%
100+
class FuturesStrategyConfig(StrategyConfig, frozen=True):
101+
"""
102+
Configuration for the FuturesStrategy.
103+
"""
104+
105+
future_id: InstrumentId
106+
107+
108+
class FuturesStrategy(Strategy):
109+
"""
110+
A simple futures trading strategy that subscribes to bar data.
111+
"""
112+
113+
def __init__(self, config: FuturesStrategyConfig) -> None:
114+
super().__init__(config=config)
115+
self.bar_type: BarType | None = None
116+
self.position_opened = False
117+
118+
def on_start(self) -> None:
119+
self.bar_type = BarType.from_str(f"{self.config.future_id}-1-MINUTE-LAST-EXTERNAL")
120+
121+
# Request instrument
122+
now = self.clock.utc_now()
123+
self.request_instrument(self.bar_type.instrument_id, end=now, update_catalog=True)
124+
# instrument = self.cache.instrument(self.bar_type.instrument_id)
125+
# self.log.warning(f"{instrument=}")
126+
127+
# Subscribe to bar data
128+
self.subscribe_bars(self.bar_type, update_catalog=True)
129+
130+
self.user_log(f"Strategy started, subscribed to {self.bar_type}")
131+
132+
def on_bar(self, bar: Bar) -> None:
133+
self.user_log(
134+
f"Bar received: ts_init={unix_nanos_to_iso8601(bar.ts_init)}, close={bar.close}",
135+
)
136+
137+
# Simple strategy: open a position on the first bar
138+
if not self.position_opened:
139+
self.user_log("Opening a position")
140+
self.submit_market_order(self.config.future_id, 1)
141+
self.position_opened = True
142+
143+
def submit_market_order(self, instrument_id: InstrumentId, quantity: int) -> None:
144+
order = self.order_factory.market(
145+
instrument_id=instrument_id,
146+
order_side=(OrderSide.BUY if quantity > 0 else OrderSide.SELL),
147+
quantity=Quantity.from_int(abs(quantity)),
148+
)
149+
self.submit_order(order)
150+
self.user_log(f"Submitted order: {order}")
151+
152+
def user_log(self, msg: str) -> None:
153+
self.log.warning(str(msg), color=LogColor.GREEN)
154+
155+
def on_stop(self) -> None:
156+
self.unsubscribe_bars(self.bar_type)
157+
self.user_log("Strategy stopped")
158+
159+
160+
# %% [markdown]
161+
# ## Backtest Configuration
162+
163+
# %%
164+
# Create BacktestEngineConfig
165+
strategies = [
166+
ImportableStrategyConfig(
167+
strategy_path=FuturesStrategy.fully_qualified_name(),
168+
config_path=FuturesStrategyConfig.fully_qualified_name(),
169+
config={
170+
"future_id": InstrumentId.from_str(f"{future_symbols[0]}.XCME"),
171+
},
172+
),
173+
]
174+
175+
logging = LoggingConfig(
176+
bypass_logging=False,
177+
log_colors=True,
178+
log_level="WARN",
179+
log_level_file="WARN",
180+
log_directory=".",
181+
log_file_format=None,
182+
log_file_name="databento_backtest_with_data_client",
183+
clear_log_file=True,
184+
print_config=False,
185+
use_pyo3=False,
186+
)
187+
188+
# Configure the data catalog
189+
catalogs = [
190+
DataCatalogConfig(
191+
path=catalog.path,
192+
),
193+
]
194+
195+
engine_config = BacktestEngineConfig(
196+
logging=logging,
197+
strategies=strategies,
198+
catalogs=catalogs,
199+
)
200+
201+
# Create BacktestRunConfig
202+
venues = [
203+
BacktestVenueConfig(
204+
name="XCME",
205+
oms_type="NETTING",
206+
account_type="MARGIN",
207+
base_currency="USD",
208+
starting_balances=["1_000_000 USD"],
209+
),
210+
]
211+
212+
data_clients: dict = {
213+
"databento-001": DatabentoDataClientConfig(
214+
api_key=None, # 'DATABENTO_API_KEY' env var is used
215+
routing=RoutingConfig(
216+
default=True,
217+
# venues=frozenset(["XCME"]),
218+
),
219+
),
220+
}
221+
222+
config = BacktestRunConfig(
223+
engine=engine_config,
224+
venues=venues,
225+
data=[], # Empty data list since we're using data clients
226+
start=start_time,
227+
end=end_time,
228+
data_clients=data_clients,
229+
)
230+
231+
configs = [config]
232+
233+
# Create the backtest node
234+
node = BacktestNode(configs=configs)
235+
236+
# Register the Databento data client factory
237+
node.add_data_client_factory("databento", DatabentoLiveDataClientFactory)
238+
239+
# Build the node (this will create and register the data clients)
240+
node.build()
241+
242+
# node.get_engine(configs[0].id).kernel.data_engine.default_client
243+
# node.get_engine(configs[0].id).kernel.data_engine.routing_map
244+
245+
# %%
246+
# Run the backtest
247+
node.run()
248+
249+
# %%
250+
# # Display results
251+
# engine = node.get_engine(configs[0].id)
252+
# engine.trader.generate_order_fills_report()
253+
# engine.trader.generate_positions_report()
254+
# engine.trader.generate_account_report(Venue("GLBX"))
255+
256+
# %%
257+
# # Clean up
258+
# node.dispose()
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# ---
2+
# jupyter:
3+
# jupytext:
4+
# formats: py:percent
5+
# text_representation:
6+
# extension: .py
7+
# format_name: percent
8+
# format_version: '1.3'
9+
# jupytext_version: 1.17.1
10+
# kernelspec:
11+
# display_name: Python 3 (ipykernel)
12+
# language: python
13+
# name: python3
14+
# ---
15+
16+
# %% [markdown]
17+
# # Databento Data Client with Backtest Node
18+
#
19+
# This example demonstrates how to use the Databento data client with a backtest node.
20+
21+
# %% [markdown]
22+
# ## Imports
23+
24+
# %%
25+
# Note: Use the python extension jupytext to be able to open this python file in jupyter as a notebook
26+
27+
# %%
28+
import asyncio
29+
30+
import nautilus_trader.adapters.databento.data_utils as db_data_utils
31+
from nautilus_trader.adapters.databento.config import DatabentoDataClientConfig
32+
from nautilus_trader.adapters.databento.factories import DatabentoLiveDataClientFactory
33+
from nautilus_trader.backtest.node import BacktestNode
34+
from nautilus_trader.core.datetime import time_object_to_dt
35+
from nautilus_trader.model.data import BarType
36+
from nautilus_trader.model.identifiers import InstrumentId
37+
from nautilus_trader.persistence.config import DataCatalogConfig
38+
39+
40+
# from nautilus_trader.live.config import RoutingConfig
41+
42+
43+
# %%
44+
# We need to use nest_asyncio in a jupyter notebook to be able to run async code as sync for market data
45+
# requests in a backtest
46+
try:
47+
asyncio.get_running_loop()
48+
except RuntimeError:
49+
pass # No loop running
50+
else:
51+
import nest_asyncio
52+
53+
nest_asyncio.apply()
54+
55+
# %% [markdown]
56+
# ## Parameters
57+
58+
# %%
59+
# Set the data path for Databento data
60+
# DATA_PATH = "/path/to/your/data" # Use your own value here
61+
# db_data_utils.DATA_PATH = DATA_PATH
62+
63+
catalog_folder = "download_catalog"
64+
catalog = db_data_utils.load_catalog(catalog_folder)
65+
66+
# Small amount of data for testing
67+
start_time_1 = "2024-05-08T10:00"
68+
start_time_2 = "2024-05-09T10:00"
69+
end_time = "2024-05-09T10:05"
70+
end_time_2 = "2024-05-09T10:07"
71+
end_time_3 = "2024-05-09T10:07:01"
72+
73+
# %% [markdown]
74+
# ## Strategy
75+
76+
# %%
77+
# Configure the data catalog
78+
catalog_config = DataCatalogConfig(path=catalog.path)
79+
80+
data_clients: dict = {
81+
"databento-001": DatabentoDataClientConfig(),
82+
}
83+
# api_key=None, # 'DATABENTO_API_KEY' env var
84+
# routing=RoutingConfig(
85+
# default=False,
86+
# venues=frozenset(["XCME"]),
87+
# ),
88+
89+
# Create the backtest node
90+
node = BacktestNode([])
91+
92+
# Register the Databento data client factory
93+
node.add_data_client_factory("databento", DatabentoLiveDataClientFactory)
94+
95+
# Build download engine
96+
node.setup_download_engine(catalog_config, data_clients)
97+
98+
# %%
99+
node.download_data(
100+
"request_instrument",
101+
instrument_id=InstrumentId.from_str("ESM4.XCME"),
102+
start=time_object_to_dt(start_time_1),
103+
end=time_object_to_dt(end_time),
104+
)
105+
106+
node.download_data(
107+
"request_bars",
108+
bar_type=BarType.from_str("ESM4.XCME-1-MINUTE-LAST-EXTERNAL"),
109+
start=time_object_to_dt(start_time_2),
110+
end=time_object_to_dt(end_time_3),
111+
)
112+
113+
# %%
114+
# # Clean up
115+
node.dispose()

0 commit comments

Comments
 (0)