Skip to content

Commit 5de29c4

Browse files
authored
Add instrument_ids and bar_types for BacktestDataConfig (#2478)
1 parent f0192f5 commit 5de29c4

File tree

8 files changed

+260
-170
lines changed

8 files changed

+260
-170
lines changed

examples/backtest/databento_option_greeks.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ def on_stop(self):
198198

199199
# for saving and loading custom data greeks, use True, False then False, True below
200200
stream_data, load_greeks = False, False
201+
# stream_data, load_greeks = True, False
202+
# stream_data, load_greeks = False, True
201203

202204
actors = [
203205
ImportableActorConfig(
@@ -248,17 +250,21 @@ def on_stop(self):
248250
# BacktestRunConfig
249251

250252
data = [
253+
# TODO using instrument_id and bar_spec only, or instrument_ids and bar_spec only, or bar_types only
251254
BacktestDataConfig(
252255
data_cls=Bar,
253256
catalog_path=catalog.path,
254257
instrument_id=InstrumentId.from_str(f"{future_symbols[0]}.GLBX"),
258+
# instrument_ids=[InstrumentId.from_str(f"{future_symbols[0]}.GLBX")],
255259
bar_spec="1-MINUTE-LAST",
260+
# bar_types=[f"{future_symbols[0]}.GLBX-1-MINUTE-LAST-EXTERNAL"],
256261
# start_time=start_time,
257262
# end_time=end_time,
258263
),
259264
BacktestDataConfig(
260265
data_cls=QuoteTick,
261266
catalog_path=catalog.path,
267+
# instrument_ids=[InstrumentId.from_str(f"{option_symbols[0]}.GLBX"), InstrumentId.from_str(f"{option_symbols[1]}.GLBX")],
262268
),
263269
]
264270

@@ -289,7 +295,7 @@ def on_stop(self):
289295
engine=engine_config,
290296
data=data,
291297
venues=venues,
292-
chunk_size=None, # use None when loading custom data
298+
chunk_size=None, # use None when loading custom data, else a value of 10_000 for example
293299
),
294300
]
295301

nautilus_trader/backtest/config.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from nautilus_trader.data.config import DataEngineConfig
3030
from nautilus_trader.execution.config import ExecEngineConfig
3131
from nautilus_trader.model.data import Bar
32+
from nautilus_trader.model.data import BarType
3233
from nautilus_trader.model.identifiers import InstrumentId
3334
from nautilus_trader.model.identifiers import TraderId
3435
from nautilus_trader.risk.config import RiskEngineConfig
@@ -58,9 +59,11 @@ def parse_filters_expr(s: str | None):
5859
def safer_eval(input_string):
5960
allowed_names = {"field": field}
6061
code = compile(input_string, "<string>", "eval")
62+
6163
for name in code.co_names:
6264
if name not in allowed_names:
6365
raise NameError(f"Use of {name} not allowed")
66+
6467
return eval(code, {}, allowed_names) # noqa
6568

6669
return safer_eval(s) # Only allow use of the field object
@@ -173,6 +176,13 @@ class BacktestDataConfig(NautilusConfig, frozen=True):
173176
The metadata for the data catalog query.
174177
bar_spec : str, optional
175178
The bar specification for the data catalog query.
179+
instrument_ids : list[str], optional
180+
The instrument IDs for the data catalog query.
181+
Can be used if instrument_id is not specified.
182+
If bar_spec is specified an equivalent list of bar_types will be constructed.
183+
bar_types : list[str], optional
184+
The bar types for the data catalog query.
185+
Can be used if instrument_id is not specified.
176186
177187
"""
178188

@@ -187,6 +197,8 @@ class BacktestDataConfig(NautilusConfig, frozen=True):
187197
client_id: str | None = None
188198
metadata: dict | None = None
189199
bar_spec: str | None = None
200+
instrument_ids: list[str] | None = None
201+
bar_types: list[str] | None = None
190202

191203
@property
192204
def data_type(self) -> type:
@@ -204,7 +216,7 @@ def data_type(self) -> type:
204216
return self.data_cls
205217

206218
@property
207-
def query(self) -> dict[str, Any]:
219+
def query(self) -> dict[str, Any]: # noqa: C901
208220
"""
209221
Return a catalog query object for the configuration.
210222
@@ -213,15 +225,48 @@ def query(self) -> dict[str, Any]:
213225
dict[str, Any]
214226
215227
"""
216-
if self.data_cls is Bar and self.bar_spec:
217-
bar_type = f"{self.instrument_id}-{self.bar_spec}-EXTERNAL"
218-
filter_expr: str | None = f'field("bar_type") == "{bar_type}"'
228+
filter_expr: str | None = None
229+
230+
if self.data_cls is Bar:
231+
used_bar_types = []
232+
233+
if self.bar_types is None and self.instrument_ids is None:
234+
assert self.instrument_id, "No `instrument_id` for Bar data config"
235+
assert self.bar_spec, "No `bar_spec` for Bar data config"
236+
237+
if self.instrument_id is not None and self.bar_spec is not None:
238+
bar_type = f"{self.instrument_id}-{self.bar_spec}-EXTERNAL"
239+
used_bar_types = [bar_type]
240+
elif self.bar_types is not None:
241+
used_bar_types = self.bar_types
242+
elif self.instrument_ids is not None and self.bar_spec is not None:
243+
for instrument_id in self.instrument_ids:
244+
used_bar_types.append(f"{instrument_id}-{self.bar_spec}-EXTERNAL")
245+
246+
if len(used_bar_types) > 0:
247+
filter_expr = f'(field("bar_type") == "{used_bar_types[0]}")'
248+
249+
for bar_type in used_bar_types[1:]:
250+
filter_expr = f'{filter_expr} | (field("bar_type") == "{bar_type}")'
219251
else:
220252
filter_expr = self.filter_expr
221253

254+
used_instrument_ids = None
255+
256+
if self.instrument_id is not None:
257+
used_instrument_ids = [self.instrument_id]
258+
elif self.instrument_ids is not None:
259+
used_instrument_ids = self.instrument_ids
260+
elif self.bar_types is not None:
261+
bar_types: list[BarType] = [
262+
BarType.from_str(bar_type) if type(bar_type) is str else bar_type
263+
for bar_type in self.bar_types
264+
]
265+
used_instrument_ids = [bar_type.instrument_id for bar_type in bar_types]
266+
222267
return {
223268
"data_cls": self.data_type,
224-
"instrument_ids": [self.instrument_id] if self.instrument_id else None,
269+
"instrument_ids": used_instrument_ids,
225270
"start": self.start_time,
226271
"end": self.end_time,
227272
"filter_expr": parse_filters_expr(filter_expr),
@@ -242,6 +287,7 @@ def start_time_nanos(self) -> int:
242287
"""
243288
if self.start_time is None:
244289
return 0
290+
245291
return dt_to_unix_nanos(self.start_time)
246292

247293
@property
@@ -258,6 +304,7 @@ def end_time_nanos(self) -> int:
258304
"""
259305
if self.end_time is None:
260306
return sys.maxsize
307+
261308
return dt_to_unix_nanos(self.end_time)
262309

263310

nautilus_trader/backtest/engine.pyx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ cdef class BacktestEngine:
672672

673673
if validate:
674674
first = data[0]
675+
675676
if hasattr(first, "instrument_id"):
676677
Condition.is_true(
677678
first.instrument_id in self.kernel.cache.instrument_ids(),

0 commit comments

Comments
 (0)