1use std::{collections::HashMap, time::Duration};
19
20use nautilus_common::{
21 cache::CacheConfig, enums::Environment, logging::logger::LoggerConfig,
22 msgbus::database::MessageBusConfig,
23};
24use nautilus_core::{UUID4, UnixNanos};
25use nautilus_data::engine::config::DataEngineConfig;
26use nautilus_execution::engine::config::ExecutionEngineConfig;
27use nautilus_model::{
28 data::BarSpecification,
29 enums::{AccountType, BookType, OmsType, OtoTriggerMode},
30 identifiers::{ClientId, InstrumentId, TraderId},
31 types::Currency,
32};
33use nautilus_portfolio::config::PortfolioConfig;
34use nautilus_risk::engine::config::RiskEngineConfig;
35use pyo3::{Py, PyAny, Python};
36use rust_decimal::Decimal;
37use ustr::Ustr;
38
39use super::engine::{
40 pyobject_to_fee_model_any, pyobject_to_fill_model_any, pyobject_to_latency_model_any,
41 pyobject_to_margin_model_any, pyobject_to_simulation_module_any,
42};
43use crate::config::{
44 BacktestDataConfig, BacktestEngineConfig, BacktestRunConfig, BacktestVenueConfig,
45 NautilusDataType,
46};
47
48#[pyo3_stub_gen::derive::gen_stub_pymethods]
49#[pyo3::pymethods]
50impl BacktestEngineConfig {
51 #[new]
53 #[pyo3(signature = (
54 trader_id = None,
55 load_state = None,
56 save_state = None,
57 bypass_logging = None,
58 run_analysis = None,
59 timeout_connection = None,
60 timeout_reconciliation = None,
61 timeout_portfolio = None,
62 timeout_disconnection = None,
63 delay_post_stop = None,
64 timeout_shutdown = None,
65 logging = None,
66 instance_id = None,
67 cache = None,
68 msgbus = None,
69 data_engine = None,
70 risk_engine = None,
71 exec_engine = None,
72 portfolio = None,
73 ))]
74 #[expect(clippy::too_many_arguments)]
75 fn py_new(
76 trader_id: Option<TraderId>,
77 load_state: Option<bool>,
78 save_state: Option<bool>,
79 bypass_logging: Option<bool>,
80 run_analysis: Option<bool>,
81 timeout_connection: Option<u64>,
82 timeout_reconciliation: Option<u64>,
83 timeout_portfolio: Option<u64>,
84 timeout_disconnection: Option<u64>,
85 delay_post_stop: Option<u64>,
86 timeout_shutdown: Option<u64>,
87 logging: Option<LoggerConfig>,
88 instance_id: Option<UUID4>,
89 cache: Option<CacheConfig>,
90 msgbus: Option<MessageBusConfig>,
91 data_engine: Option<DataEngineConfig>,
92 risk_engine: Option<RiskEngineConfig>,
93 exec_engine: Option<ExecutionEngineConfig>,
94 portfolio: Option<PortfolioConfig>,
95 ) -> Self {
96 let defaults = Self::default();
97 Self {
98 environment: Environment::Backtest,
99 trader_id: trader_id.unwrap_or_default(),
100 load_state: load_state.unwrap_or(defaults.load_state),
101 save_state: save_state.unwrap_or(defaults.save_state),
102 bypass_logging: bypass_logging.unwrap_or(defaults.bypass_logging),
103 run_analysis: run_analysis.unwrap_or(defaults.run_analysis),
104 timeout_connection: Duration::from_secs(timeout_connection.unwrap_or(60)),
105 timeout_reconciliation: Duration::from_secs(timeout_reconciliation.unwrap_or(30)),
106 timeout_portfolio: Duration::from_secs(timeout_portfolio.unwrap_or(10)),
107 timeout_disconnection: Duration::from_secs(timeout_disconnection.unwrap_or(10)),
108 delay_post_stop: Duration::from_secs(delay_post_stop.unwrap_or(10)),
109 timeout_shutdown: Duration::from_secs(timeout_shutdown.unwrap_or(5)),
110 logging: logging.unwrap_or_default(),
111 instance_id,
112 cache,
113 msgbus,
114 data_engine,
115 risk_engine,
116 exec_engine,
117 portfolio,
118 streaming: None,
119 }
120 }
121
122 #[getter]
123 #[pyo3(name = "trader_id")]
124 fn py_trader_id(&self) -> TraderId {
125 self.trader_id
126 }
127
128 #[getter]
129 #[pyo3(name = "load_state")]
130 const fn py_load_state(&self) -> bool {
131 self.load_state
132 }
133
134 #[getter]
135 #[pyo3(name = "save_state")]
136 const fn py_save_state(&self) -> bool {
137 self.save_state
138 }
139
140 #[getter]
141 #[pyo3(name = "bypass_logging")]
142 const fn py_bypass_logging(&self) -> bool {
143 self.bypass_logging
144 }
145
146 #[getter]
147 #[pyo3(name = "run_analysis")]
148 const fn py_run_analysis(&self) -> bool {
149 self.run_analysis
150 }
151
152 #[getter]
153 #[pyo3(name = "cache")]
154 fn py_cache(&self) -> Option<CacheConfig> {
155 self.cache.clone()
156 }
157
158 #[getter]
159 #[pyo3(name = "msgbus")]
160 fn py_msgbus(&self) -> Option<MessageBusConfig> {
161 self.msgbus.clone()
162 }
163
164 #[getter]
165 #[pyo3(name = "data_engine")]
166 fn py_data_engine(&self) -> Option<DataEngineConfig> {
167 self.data_engine.clone()
168 }
169
170 #[getter]
171 #[pyo3(name = "risk_engine")]
172 fn py_risk_engine(&self) -> Option<RiskEngineConfig> {
173 self.risk_engine.clone()
174 }
175
176 #[getter]
177 #[pyo3(name = "exec_engine")]
178 fn py_exec_engine(&self) -> Option<ExecutionEngineConfig> {
179 self.exec_engine.clone()
180 }
181
182 #[getter]
183 #[pyo3(name = "portfolio")]
184 const fn py_portfolio(&self) -> Option<PortfolioConfig> {
185 self.portfolio
186 }
187
188 fn __repr__(&self) -> String {
189 format!("{self:?}")
190 }
191}
192
193#[pyo3_stub_gen::derive::gen_stub_pymethods]
194#[pyo3::pymethods]
195impl BacktestVenueConfig {
196 #[new]
198 #[pyo3(signature = (
199 name,
200 oms_type,
201 account_type,
202 book_type,
203 starting_balances,
204 routing = None,
205 frozen_account = None,
206 reject_stop_orders = None,
207 support_gtd_orders = None,
208 support_contingent_orders = None,
209 use_position_ids = None,
210 use_random_ids = None,
211 use_reduce_only = None,
212 bar_execution = None,
213 bar_adaptive_high_low_ordering = None,
214 trade_execution = None,
215 use_market_order_acks = None,
216 liquidity_consumption = None,
217 allow_cash_borrowing = None,
218 queue_position = None,
219 oto_trigger_mode = None,
220 base_currency = None,
221 default_leverage = None,
222 leverages = None,
223 margin_model = None,
224 modules = None,
225 fill_model = None,
226 latency_model = None,
227 fee_model = None,
228 price_protection_points = None,
229 settlement_prices = None,
230 ))]
231 #[expect(clippy::too_many_arguments)]
232 fn py_new(
233 name: &str,
234 oms_type: OmsType,
235 account_type: AccountType,
236 book_type: BookType,
237 starting_balances: Vec<String>,
238 routing: Option<bool>,
239 frozen_account: Option<bool>,
240 reject_stop_orders: Option<bool>,
241 support_gtd_orders: Option<bool>,
242 support_contingent_orders: Option<bool>,
243 use_position_ids: Option<bool>,
244 use_random_ids: Option<bool>,
245 use_reduce_only: Option<bool>,
246 bar_execution: Option<bool>,
247 bar_adaptive_high_low_ordering: Option<bool>,
248 trade_execution: Option<bool>,
249 use_market_order_acks: Option<bool>,
250 liquidity_consumption: Option<bool>,
251 allow_cash_borrowing: Option<bool>,
252 queue_position: Option<bool>,
253 oto_trigger_mode: Option<OtoTriggerMode>,
254 base_currency: Option<Currency>,
255 default_leverage: Option<Decimal>,
256 leverages: Option<HashMap<InstrumentId, Decimal>>,
257 margin_model: Option<Py<PyAny>>,
258 modules: Option<Vec<Py<PyAny>>>,
259 fill_model: Option<Py<PyAny>>,
260 latency_model: Option<Py<PyAny>>,
261 fee_model: Option<Py<PyAny>>,
262 price_protection_points: Option<u32>,
263 settlement_prices: Option<HashMap<InstrumentId, f64>>,
264 ) -> pyo3::PyResult<Self> {
265 let margin_model = margin_model
266 .map(|obj| Python::attach(|py| pyobject_to_margin_model_any(py, obj.bind(py))))
267 .transpose()?;
268 let modules = modules
269 .map(|objs| {
270 objs.into_iter()
271 .map(|obj| {
272 Python::attach(|py| pyobject_to_simulation_module_any(py, obj.bind(py)))
273 })
274 .collect::<pyo3::PyResult<Vec<_>>>()
275 })
276 .transpose()?
277 .unwrap_or_default();
278 let fill_model = fill_model
279 .map(|obj| Python::attach(|py| pyobject_to_fill_model_any(py, obj.bind(py))))
280 .transpose()?;
281 let latency_model = latency_model
282 .map(|obj| Python::attach(|py| pyobject_to_latency_model_any(py, obj.bind(py))))
283 .transpose()?;
284 let fee_model = fee_model
285 .map(|obj| Python::attach(|py| pyobject_to_fee_model_any(py, obj.bind(py))))
286 .transpose()?;
287
288 Ok(Self::builder()
289 .name(Ustr::from(name))
290 .oms_type(oms_type)
291 .account_type(account_type)
292 .book_type(book_type)
293 .starting_balances(starting_balances)
294 .maybe_routing(routing)
295 .maybe_frozen_account(frozen_account)
296 .maybe_reject_stop_orders(reject_stop_orders)
297 .maybe_support_gtd_orders(support_gtd_orders)
298 .maybe_support_contingent_orders(support_contingent_orders)
299 .maybe_use_position_ids(use_position_ids)
300 .maybe_use_random_ids(use_random_ids)
301 .maybe_use_reduce_only(use_reduce_only)
302 .maybe_bar_execution(bar_execution)
303 .maybe_bar_adaptive_high_low_ordering(bar_adaptive_high_low_ordering)
304 .maybe_trade_execution(trade_execution)
305 .maybe_use_market_order_acks(use_market_order_acks)
306 .maybe_liquidity_consumption(liquidity_consumption)
307 .maybe_allow_cash_borrowing(allow_cash_borrowing)
308 .maybe_queue_position(queue_position)
309 .maybe_oto_trigger_mode(oto_trigger_mode)
310 .maybe_base_currency(base_currency)
311 .maybe_default_leverage(default_leverage)
312 .maybe_leverages(leverages.map(|m| m.into_iter().collect()))
313 .maybe_margin_model(margin_model)
314 .modules(modules)
315 .maybe_fill_model(fill_model)
316 .maybe_latency_model(latency_model)
317 .maybe_fee_model(fee_model)
318 .maybe_price_protection_points(price_protection_points)
319 .maybe_settlement_prices(settlement_prices.map(|m| m.into_iter().collect()))
320 .build())
321 }
322
323 #[getter]
324 #[pyo3(name = "name")]
325 fn py_name(&self) -> &str {
326 self.name().as_str()
327 }
328
329 #[getter]
330 #[pyo3(name = "oms_type")]
331 fn py_oms_type(&self) -> OmsType {
332 self.oms_type()
333 }
334
335 #[getter]
336 #[pyo3(name = "account_type")]
337 fn py_account_type(&self) -> AccountType {
338 self.account_type()
339 }
340
341 #[getter]
342 #[pyo3(name = "book_type")]
343 fn py_book_type(&self) -> BookType {
344 self.book_type()
345 }
346
347 #[getter]
348 #[pyo3(name = "starting_balances")]
349 fn py_starting_balances(&self) -> Vec<String> {
350 self.starting_balances().to_vec()
351 }
352
353 #[getter]
354 #[pyo3(name = "bar_execution")]
355 fn py_bar_execution(&self) -> bool {
356 self.bar_execution()
357 }
358
359 #[getter]
360 #[pyo3(name = "trade_execution")]
361 fn py_trade_execution(&self) -> bool {
362 self.trade_execution()
363 }
364
365 fn __repr__(&self) -> String {
366 format!("{self:?}")
367 }
368}
369
370#[pyo3_stub_gen::derive::gen_stub_pymethods]
371#[pyo3::pymethods]
372impl BacktestDataConfig {
373 #[new]
375 #[pyo3(signature = (
376 data_type,
377 catalog_path,
378 catalog_fs_protocol = None,
379 catalog_fs_storage_options = None,
380 catalog_fs_rust_storage_options = None,
381 instrument_id = None,
382 instrument_ids = None,
383 start_time = None,
384 end_time = None,
385 filter_expr = None,
386 client_id = None,
387 metadata = None,
388 bar_spec = None,
389 bar_types = None,
390 optimize_file_loading = None,
391 ))]
392 #[expect(clippy::too_many_arguments)]
393 fn py_new(
394 data_type: &str,
395 catalog_path: String,
396 catalog_fs_protocol: Option<String>,
397 catalog_fs_storage_options: Option<HashMap<String, String>>,
398 catalog_fs_rust_storage_options: Option<HashMap<String, String>>,
399 instrument_id: Option<InstrumentId>,
400 instrument_ids: Option<Vec<InstrumentId>>,
401 start_time: Option<u64>,
402 end_time: Option<u64>,
403 filter_expr: Option<String>,
404 client_id: Option<ClientId>,
405 metadata: Option<HashMap<String, String>>,
406 bar_spec: Option<BarSpecification>,
407 bar_types: Option<Vec<String>>,
408 optimize_file_loading: Option<bool>,
409 ) -> pyo3::PyResult<Self> {
410 let data_type = data_type
411 .parse::<NautilusDataType>()
412 .map_err(nautilus_core::python::to_pyvalue_err)?;
413 Ok(Self::builder()
414 .data_type(data_type)
415 .catalog_path(catalog_path)
416 .maybe_catalog_fs_protocol(catalog_fs_protocol)
417 .maybe_catalog_fs_storage_options(
418 catalog_fs_storage_options.map(|m| m.into_iter().collect()),
419 )
420 .maybe_catalog_fs_rust_storage_options(
421 catalog_fs_rust_storage_options.map(|m| m.into_iter().collect()),
422 )
423 .maybe_instrument_id(instrument_id)
424 .maybe_instrument_ids(instrument_ids)
425 .maybe_start_time(start_time.map(UnixNanos::from))
426 .maybe_end_time(end_time.map(UnixNanos::from))
427 .maybe_filter_expr(filter_expr)
428 .maybe_client_id(client_id)
429 .maybe_metadata(metadata.map(|m| m.into_iter().collect()))
430 .maybe_bar_spec(bar_spec)
431 .maybe_bar_types(bar_types)
432 .maybe_optimize_file_loading(optimize_file_loading)
433 .build())
434 }
435
436 #[getter]
437 #[pyo3(name = "data_type")]
438 fn py_data_type(&self) -> String {
439 self.data_type().to_string()
440 }
441
442 #[getter]
443 #[pyo3(name = "catalog_path")]
444 fn py_catalog_path(&self) -> &str {
445 self.catalog_path()
446 }
447
448 #[getter]
449 #[pyo3(name = "instrument_id")]
450 fn py_instrument_id(&self) -> Option<InstrumentId> {
451 self.instrument_id()
452 }
453
454 fn __repr__(&self) -> String {
455 format!("{self:?}")
456 }
457}
458
459#[pyo3_stub_gen::derive::gen_stub_pymethods]
460#[pyo3::pymethods]
461impl BacktestRunConfig {
462 #[new]
465 #[pyo3(signature = (
466 venues,
467 data,
468 engine = None,
469 id = None,
470 chunk_size = None,
471 raise_exception = None,
472 dispose_on_completion = None,
473 start = None,
474 end = None,
475 ))]
476 #[expect(clippy::too_many_arguments)]
477 fn py_new(
478 venues: Vec<BacktestVenueConfig>,
479 data: Vec<BacktestDataConfig>,
480 engine: Option<BacktestEngineConfig>,
481 id: Option<String>,
482 chunk_size: Option<usize>,
483 raise_exception: Option<bool>,
484 dispose_on_completion: Option<bool>,
485 start: Option<u64>,
486 end: Option<u64>,
487 ) -> Self {
488 Self::builder()
489 .venues(venues)
490 .data(data)
491 .maybe_engine(engine)
492 .maybe_id(id)
493 .maybe_chunk_size(chunk_size)
494 .maybe_raise_exception(raise_exception)
495 .maybe_dispose_on_completion(dispose_on_completion)
496 .maybe_start(start.map(UnixNanos::from))
497 .maybe_end(end.map(UnixNanos::from))
498 .build()
499 }
500
501 #[getter]
502 #[pyo3(name = "id")]
503 fn py_id(&self) -> &str {
504 self.id()
505 }
506
507 fn __repr__(&self) -> String {
508 format!("{self:?}")
509 }
510}