29
29
from nautilus_trader .data .config import DataEngineConfig
30
30
from nautilus_trader .execution .config import ExecEngineConfig
31
31
from nautilus_trader .model .data import Bar
32
+ from nautilus_trader .model .data import BarType
32
33
from nautilus_trader .model .identifiers import InstrumentId
33
34
from nautilus_trader .model .identifiers import TraderId
34
35
from nautilus_trader .risk .config import RiskEngineConfig
@@ -58,9 +59,11 @@ def parse_filters_expr(s: str | None):
58
59
def safer_eval (input_string ):
59
60
allowed_names = {"field" : field }
60
61
code = compile (input_string , "<string>" , "eval" )
62
+
61
63
for name in code .co_names :
62
64
if name not in allowed_names :
63
65
raise NameError (f"Use of { name } not allowed" )
66
+
64
67
return eval (code , {}, allowed_names ) # noqa
65
68
66
69
return safer_eval (s ) # Only allow use of the field object
@@ -173,6 +176,13 @@ class BacktestDataConfig(NautilusConfig, frozen=True):
173
176
The metadata for the data catalog query.
174
177
bar_spec : str, optional
175
178
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.
176
186
177
187
"""
178
188
@@ -187,6 +197,8 @@ class BacktestDataConfig(NautilusConfig, frozen=True):
187
197
client_id : str | None = None
188
198
metadata : dict | None = None
189
199
bar_spec : str | None = None
200
+ instrument_ids : list [str ] | None = None
201
+ bar_types : list [str ] | None = None
190
202
191
203
@property
192
204
def data_type (self ) -> type :
@@ -204,7 +216,7 @@ def data_type(self) -> type:
204
216
return self .data_cls
205
217
206
218
@property
207
- def query (self ) -> dict [str , Any ]:
219
+ def query (self ) -> dict [str , Any ]: # noqa: C901
208
220
"""
209
221
Return a catalog query object for the configuration.
210
222
@@ -213,15 +225,48 @@ def query(self) -> dict[str, Any]:
213
225
dict[str, Any]
214
226
215
227
"""
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 } ")'
219
251
else :
220
252
filter_expr = self .filter_expr
221
253
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
+
222
267
return {
223
268
"data_cls" : self .data_type ,
224
- "instrument_ids" : [ self . instrument_id ] if self . instrument_id else None ,
269
+ "instrument_ids" : used_instrument_ids ,
225
270
"start" : self .start_time ,
226
271
"end" : self .end_time ,
227
272
"filter_expr" : parse_filters_expr (filter_expr ),
@@ -242,6 +287,7 @@ def start_time_nanos(self) -> int:
242
287
"""
243
288
if self .start_time is None :
244
289
return 0
290
+
245
291
return dt_to_unix_nanos (self .start_time )
246
292
247
293
@property
@@ -258,6 +304,7 @@ def end_time_nanos(self) -> int:
258
304
"""
259
305
if self .end_time is None :
260
306
return sys .maxsize
307
+
261
308
return dt_to_unix_nanos (self .end_time )
262
309
263
310
0 commit comments