1use std::{collections::HashMap, fmt::Display, str::FromStr, time::Duration};
19
20use ahash::AHashMap;
21use nautilus_common::{
22 cache::CacheConfig, enums::Environment, logging::logger::LoggerConfig,
23 msgbus::database::MessageBusConfig,
24};
25use nautilus_core::{UUID4, UnixNanos};
26use nautilus_data::engine::config::DataEngineConfig;
27use nautilus_execution::engine::config::ExecutionEngineConfig;
28use nautilus_model::{
29 data::{BarSpecification, BarType},
30 enums::{AccountType, BookType, OmsType},
31 identifiers::{ClientId, InstrumentId, TraderId},
32 types::Currency,
33};
34use nautilus_portfolio::config::PortfolioConfig;
35use nautilus_risk::engine::config::RiskEngineConfig;
36use nautilus_system::config::{NautilusKernelConfig, StreamingConfig};
37use ustr::Ustr;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum NautilusDataType {
42 QuoteTick,
43 TradeTick,
44 Bar,
45 OrderBookDelta,
46 OrderBookDepth10,
47 MarkPriceUpdate,
48 IndexPriceUpdate,
49 InstrumentClose,
50}
51
52impl Display for NautilusDataType {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 std::fmt::Debug::fmt(self, f)
55 }
56}
57
58impl FromStr for NautilusDataType {
59 type Err = anyhow::Error;
60
61 fn from_str(s: &str) -> anyhow::Result<Self> {
62 match s {
63 stringify!(QuoteTick) => Ok(Self::QuoteTick),
64 stringify!(TradeTick) => Ok(Self::TradeTick),
65 stringify!(Bar) => Ok(Self::Bar),
66 stringify!(OrderBookDelta) => Ok(Self::OrderBookDelta),
67 stringify!(OrderBookDepth10) => Ok(Self::OrderBookDepth10),
68 stringify!(MarkPriceUpdate) => Ok(Self::MarkPriceUpdate),
69 stringify!(IndexPriceUpdate) => Ok(Self::IndexPriceUpdate),
70 stringify!(InstrumentClose) => Ok(Self::InstrumentClose),
71 _ => anyhow::bail!("Invalid `NautilusDataType`: '{s}'"),
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78#[cfg_attr(
79 feature = "python",
80 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.backtest", from_py_object)
81)]
82pub struct BacktestEngineConfig {
83 pub environment: Environment,
85 pub trader_id: TraderId,
87 pub load_state: bool,
89 pub save_state: bool,
91 pub logging: LoggerConfig,
93 pub instance_id: Option<UUID4>,
95 pub timeout_connection: Duration,
97 pub timeout_reconciliation: Duration,
99 pub timeout_portfolio: Duration,
101 pub timeout_disconnection: Duration,
103 pub delay_post_stop: Duration,
105 pub timeout_shutdown: Duration,
107 pub cache: Option<CacheConfig>,
109 pub msgbus: Option<MessageBusConfig>,
111 pub data_engine: Option<DataEngineConfig>,
113 pub risk_engine: Option<RiskEngineConfig>,
115 pub exec_engine: Option<ExecutionEngineConfig>,
117 pub portfolio: Option<PortfolioConfig>,
119 pub streaming: Option<StreamingConfig>,
121 pub bypass_logging: bool,
123 pub run_analysis: bool,
125}
126
127impl BacktestEngineConfig {
128 #[must_use]
129 #[allow(clippy::too_many_arguments)]
130 pub fn new(
131 environment: Environment,
132 trader_id: TraderId,
133 load_state: Option<bool>,
134 save_state: Option<bool>,
135 bypass_logging: Option<bool>,
136 run_analysis: Option<bool>,
137 timeout_connection: Option<u64>,
138 timeout_reconciliation: Option<u64>,
139 timeout_portfolio: Option<u64>,
140 timeout_disconnection: Option<u64>,
141 delay_post_stop: Option<u64>,
142 timeout_shutdown: Option<u64>,
143 logging: Option<LoggerConfig>,
144 instance_id: Option<UUID4>,
145 cache: Option<CacheConfig>,
146 msgbus: Option<MessageBusConfig>,
147 data_engine: Option<DataEngineConfig>,
148 risk_engine: Option<RiskEngineConfig>,
149 exec_engine: Option<ExecutionEngineConfig>,
150 portfolio: Option<PortfolioConfig>,
151 streaming: Option<StreamingConfig>,
152 ) -> Self {
153 Self {
154 environment,
155 trader_id,
156 load_state: load_state.unwrap_or(false),
157 save_state: save_state.unwrap_or(false),
158 logging: logging.unwrap_or_default(),
159 instance_id,
160 timeout_connection: Duration::from_secs(timeout_connection.unwrap_or(60)),
161 timeout_reconciliation: Duration::from_secs(timeout_reconciliation.unwrap_or(30)),
162 timeout_portfolio: Duration::from_secs(timeout_portfolio.unwrap_or(10)),
163 timeout_disconnection: Duration::from_secs(timeout_disconnection.unwrap_or(10)),
164 delay_post_stop: Duration::from_secs(delay_post_stop.unwrap_or(10)),
165 timeout_shutdown: Duration::from_secs(timeout_shutdown.unwrap_or(5)),
166 cache,
167 msgbus,
168 data_engine,
169 risk_engine,
170 exec_engine,
171 portfolio,
172 streaming,
173 bypass_logging: bypass_logging.unwrap_or(false),
174 run_analysis: run_analysis.unwrap_or(true),
175 }
176 }
177}
178
179impl NautilusKernelConfig for BacktestEngineConfig {
180 fn environment(&self) -> Environment {
181 self.environment
182 }
183
184 fn trader_id(&self) -> TraderId {
185 self.trader_id
186 }
187
188 fn load_state(&self) -> bool {
189 self.load_state
190 }
191
192 fn save_state(&self) -> bool {
193 self.save_state
194 }
195
196 fn logging(&self) -> LoggerConfig {
197 self.logging.clone()
198 }
199
200 fn instance_id(&self) -> Option<UUID4> {
201 self.instance_id
202 }
203
204 fn timeout_connection(&self) -> Duration {
205 self.timeout_connection
206 }
207
208 fn timeout_reconciliation(&self) -> Duration {
209 self.timeout_reconciliation
210 }
211
212 fn timeout_portfolio(&self) -> Duration {
213 self.timeout_portfolio
214 }
215
216 fn timeout_disconnection(&self) -> Duration {
217 self.timeout_disconnection
218 }
219
220 fn delay_post_stop(&self) -> Duration {
221 self.delay_post_stop
222 }
223
224 fn timeout_shutdown(&self) -> Duration {
225 self.timeout_shutdown
226 }
227
228 fn cache(&self) -> Option<CacheConfig> {
229 self.cache.clone()
230 }
231
232 fn msgbus(&self) -> Option<MessageBusConfig> {
233 self.msgbus.clone()
234 }
235
236 fn data_engine(&self) -> Option<DataEngineConfig> {
237 self.data_engine.clone()
238 }
239
240 fn risk_engine(&self) -> Option<RiskEngineConfig> {
241 self.risk_engine.clone()
242 }
243
244 fn exec_engine(&self) -> Option<ExecutionEngineConfig> {
245 self.exec_engine.clone()
246 }
247
248 fn portfolio(&self) -> Option<PortfolioConfig> {
249 self.portfolio.clone()
250 }
251
252 fn streaming(&self) -> Option<StreamingConfig> {
253 self.streaming.clone()
254 }
255}
256
257impl Default for BacktestEngineConfig {
258 fn default() -> Self {
259 Self {
260 environment: Environment::Backtest,
261 trader_id: TraderId::default(),
262 load_state: false,
263 save_state: false,
264 logging: LoggerConfig::default(),
265 instance_id: None,
266 timeout_connection: Duration::from_secs(60),
267 timeout_reconciliation: Duration::from_secs(30),
268 timeout_portfolio: Duration::from_secs(10),
269 timeout_disconnection: Duration::from_secs(10),
270 delay_post_stop: Duration::from_secs(10),
271 timeout_shutdown: Duration::from_secs(5),
272 cache: None,
273 msgbus: None,
274 data_engine: None,
275 risk_engine: None,
276 exec_engine: None,
277 portfolio: None,
278 streaming: None,
279 bypass_logging: false,
280 run_analysis: true,
281 }
282 }
283}
284
285#[cfg(feature = "python")]
286#[pyo3::pymethods]
287impl BacktestEngineConfig {
288 #[new]
289 #[pyo3(signature = (
290 trader_id = None,
291 load_state = None,
292 save_state = None,
293 bypass_logging = None,
294 run_analysis = None,
295 timeout_connection = None,
296 timeout_reconciliation = None,
297 timeout_portfolio = None,
298 timeout_disconnection = None,
299 delay_post_stop = None,
300 timeout_shutdown = None,
301 logging = None,
302 instance_id = None,
303 ))]
304 #[allow(clippy::too_many_arguments)]
305 fn py_new(
306 trader_id: Option<TraderId>,
307 load_state: Option<bool>,
308 save_state: Option<bool>,
309 bypass_logging: Option<bool>,
310 run_analysis: Option<bool>,
311 timeout_connection: Option<u64>,
312 timeout_reconciliation: Option<u64>,
313 timeout_portfolio: Option<u64>,
314 timeout_disconnection: Option<u64>,
315 delay_post_stop: Option<u64>,
316 timeout_shutdown: Option<u64>,
317 logging: Option<LoggerConfig>,
318 instance_id: Option<UUID4>,
319 ) -> Self {
320 Self::new(
321 Environment::Backtest,
322 trader_id.unwrap_or_default(),
323 load_state,
324 save_state,
325 bypass_logging,
326 run_analysis,
327 timeout_connection,
328 timeout_reconciliation,
329 timeout_portfolio,
330 timeout_disconnection,
331 delay_post_stop,
332 timeout_shutdown,
333 logging,
334 instance_id,
335 None, None, None, None, None, None, None, )
343 }
344
345 #[getter]
346 #[pyo3(name = "trader_id")]
347 fn py_trader_id(&self) -> TraderId {
348 self.trader_id
349 }
350
351 #[getter]
352 #[pyo3(name = "load_state")]
353 const fn py_load_state(&self) -> bool {
354 self.load_state
355 }
356
357 #[getter]
358 #[pyo3(name = "save_state")]
359 const fn py_save_state(&self) -> bool {
360 self.save_state
361 }
362
363 #[getter]
364 #[pyo3(name = "bypass_logging")]
365 const fn py_bypass_logging(&self) -> bool {
366 self.bypass_logging
367 }
368
369 #[getter]
370 #[pyo3(name = "run_analysis")]
371 const fn py_run_analysis(&self) -> bool {
372 self.run_analysis
373 }
374
375 fn __repr__(&self) -> String {
376 format!("{self:?}")
377 }
378}
379
380#[derive(Debug, Clone)]
382#[cfg_attr(
383 feature = "python",
384 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.backtest", from_py_object)
385)]
386pub struct BacktestVenueConfig {
387 name: Ustr,
389 oms_type: OmsType,
391 account_type: AccountType,
393 book_type: BookType,
395 starting_balances: Vec<String>,
397 routing: bool,
399 frozen_account: bool,
401 reject_stop_orders: bool,
403 support_gtd_orders: bool,
405 support_contingent_orders: bool,
408 use_position_ids: bool,
410 use_random_ids: bool,
412 use_reduce_only: bool,
414 bar_execution: bool,
416 bar_adaptive_high_low_ordering: bool,
423 trade_execution: bool,
425 use_market_order_acks: bool,
427 liquidity_consumption: bool,
429 allow_cash_borrowing: bool,
431 base_currency: Option<Currency>,
433 default_leverage: Option<f64>,
435 leverages: Option<AHashMap<InstrumentId, f64>>,
437 price_protection_points: u32,
440}
441
442impl BacktestVenueConfig {
443 #[allow(clippy::too_many_arguments)]
444 #[must_use]
445 pub fn new(
446 name: Ustr,
447 oms_type: OmsType,
448 account_type: AccountType,
449 book_type: BookType,
450 routing: Option<bool>,
451 frozen_account: Option<bool>,
452 reject_stop_orders: Option<bool>,
453 support_gtd_orders: Option<bool>,
454 support_contingent_orders: Option<bool>,
455 use_position_ids: Option<bool>,
456 use_random_ids: Option<bool>,
457 use_reduce_only: Option<bool>,
458 bar_execution: Option<bool>,
459 bar_adaptive_high_low_ordering: Option<bool>,
460 trade_execution: Option<bool>,
461 use_market_order_acks: Option<bool>,
462 liquidity_consumption: Option<bool>,
463 allow_cash_borrowing: Option<bool>,
464 starting_balances: Vec<String>,
465 base_currency: Option<Currency>,
466 default_leverage: Option<f64>,
467 leverages: Option<AHashMap<InstrumentId, f64>>,
468 price_protection_points: Option<u32>,
469 ) -> Self {
470 Self {
471 name,
472 oms_type,
473 account_type,
474 book_type,
475 routing: routing.unwrap_or(false),
476 frozen_account: frozen_account.unwrap_or(false),
477 reject_stop_orders: reject_stop_orders.unwrap_or(true),
478 support_gtd_orders: support_gtd_orders.unwrap_or(true),
479 support_contingent_orders: support_contingent_orders.unwrap_or(true),
480 use_position_ids: use_position_ids.unwrap_or(true),
481 use_random_ids: use_random_ids.unwrap_or(false),
482 use_reduce_only: use_reduce_only.unwrap_or(true),
483 bar_execution: bar_execution.unwrap_or(true),
484 bar_adaptive_high_low_ordering: bar_adaptive_high_low_ordering.unwrap_or(false),
485 trade_execution: trade_execution.unwrap_or(true),
486 use_market_order_acks: use_market_order_acks.unwrap_or(false),
487 liquidity_consumption: liquidity_consumption.unwrap_or(false),
488 allow_cash_borrowing: allow_cash_borrowing.unwrap_or(false),
489 starting_balances,
490 base_currency,
491 default_leverage,
492 leverages,
493 price_protection_points: price_protection_points.unwrap_or(0),
494 }
495 }
496
497 #[must_use]
498 pub fn name(&self) -> Ustr {
499 self.name
500 }
501
502 #[must_use]
503 pub fn oms_type(&self) -> OmsType {
504 self.oms_type
505 }
506
507 #[must_use]
508 pub fn account_type(&self) -> AccountType {
509 self.account_type
510 }
511
512 #[must_use]
513 pub fn book_type(&self) -> BookType {
514 self.book_type
515 }
516
517 #[must_use]
518 pub fn starting_balances(&self) -> &[String] {
519 &self.starting_balances
520 }
521
522 #[must_use]
523 pub fn routing(&self) -> bool {
524 self.routing
525 }
526
527 #[must_use]
528 pub fn frozen_account(&self) -> bool {
529 self.frozen_account
530 }
531
532 #[must_use]
533 pub fn reject_stop_orders(&self) -> bool {
534 self.reject_stop_orders
535 }
536
537 #[must_use]
538 pub fn support_gtd_orders(&self) -> bool {
539 self.support_gtd_orders
540 }
541
542 #[must_use]
543 pub fn support_contingent_orders(&self) -> bool {
544 self.support_contingent_orders
545 }
546
547 #[must_use]
548 pub fn use_position_ids(&self) -> bool {
549 self.use_position_ids
550 }
551
552 #[must_use]
553 pub fn use_random_ids(&self) -> bool {
554 self.use_random_ids
555 }
556
557 #[must_use]
558 pub fn use_reduce_only(&self) -> bool {
559 self.use_reduce_only
560 }
561
562 #[must_use]
563 pub fn bar_execution(&self) -> bool {
564 self.bar_execution
565 }
566
567 #[must_use]
568 pub fn bar_adaptive_high_low_ordering(&self) -> bool {
569 self.bar_adaptive_high_low_ordering
570 }
571
572 #[must_use]
573 pub fn trade_execution(&self) -> bool {
574 self.trade_execution
575 }
576
577 #[must_use]
578 pub fn use_market_order_acks(&self) -> bool {
579 self.use_market_order_acks
580 }
581
582 #[must_use]
583 pub fn liquidity_consumption(&self) -> bool {
584 self.liquidity_consumption
585 }
586
587 #[must_use]
588 pub fn allow_cash_borrowing(&self) -> bool {
589 self.allow_cash_borrowing
590 }
591
592 #[must_use]
593 pub fn base_currency(&self) -> Option<Currency> {
594 self.base_currency
595 }
596
597 #[must_use]
598 pub fn default_leverage(&self) -> Option<f64> {
599 self.default_leverage
600 }
601
602 #[must_use]
603 pub fn leverages(&self) -> Option<&AHashMap<InstrumentId, f64>> {
604 self.leverages.as_ref()
605 }
606
607 #[must_use]
608 pub fn price_protection_points(&self) -> u32 {
609 self.price_protection_points
610 }
611}
612
613#[cfg(feature = "python")]
614#[pyo3::pymethods]
615impl BacktestVenueConfig {
616 #[new]
617 #[pyo3(signature = (
618 name,
619 oms_type,
620 account_type,
621 book_type,
622 starting_balances,
623 routing = None,
624 frozen_account = None,
625 reject_stop_orders = None,
626 support_gtd_orders = None,
627 support_contingent_orders = None,
628 use_position_ids = None,
629 use_random_ids = None,
630 use_reduce_only = None,
631 bar_execution = None,
632 bar_adaptive_high_low_ordering = None,
633 trade_execution = None,
634 use_market_order_acks = None,
635 liquidity_consumption = None,
636 allow_cash_borrowing = None,
637 base_currency = None,
638 default_leverage = None,
639 leverages = None,
640 price_protection_points = None,
641 ))]
642 #[allow(clippy::too_many_arguments)]
643 fn py_new(
644 name: &str,
645 oms_type: OmsType,
646 account_type: AccountType,
647 book_type: BookType,
648 starting_balances: Vec<String>,
649 routing: Option<bool>,
650 frozen_account: Option<bool>,
651 reject_stop_orders: Option<bool>,
652 support_gtd_orders: Option<bool>,
653 support_contingent_orders: Option<bool>,
654 use_position_ids: Option<bool>,
655 use_random_ids: Option<bool>,
656 use_reduce_only: Option<bool>,
657 bar_execution: Option<bool>,
658 bar_adaptive_high_low_ordering: Option<bool>,
659 trade_execution: Option<bool>,
660 use_market_order_acks: Option<bool>,
661 liquidity_consumption: Option<bool>,
662 allow_cash_borrowing: Option<bool>,
663 base_currency: Option<Currency>,
664 default_leverage: Option<f64>,
665 leverages: Option<HashMap<InstrumentId, f64>>,
666 price_protection_points: Option<u32>,
667 ) -> Self {
668 let leverages = leverages.map(|m| m.into_iter().collect());
669 Self::new(
670 Ustr::from(name),
671 oms_type,
672 account_type,
673 book_type,
674 routing,
675 frozen_account,
676 reject_stop_orders,
677 support_gtd_orders,
678 support_contingent_orders,
679 use_position_ids,
680 use_random_ids,
681 use_reduce_only,
682 bar_execution,
683 bar_adaptive_high_low_ordering,
684 trade_execution,
685 use_market_order_acks,
686 liquidity_consumption,
687 allow_cash_borrowing,
688 starting_balances,
689 base_currency,
690 default_leverage,
691 leverages,
692 price_protection_points,
693 )
694 }
695
696 #[getter]
697 #[pyo3(name = "name")]
698 fn py_name(&self) -> &str {
699 self.name.as_str()
700 }
701
702 #[getter]
703 #[pyo3(name = "oms_type")]
704 const fn py_oms_type(&self) -> OmsType {
705 self.oms_type
706 }
707
708 #[getter]
709 #[pyo3(name = "account_type")]
710 const fn py_account_type(&self) -> AccountType {
711 self.account_type
712 }
713
714 #[getter]
715 #[pyo3(name = "book_type")]
716 const fn py_book_type(&self) -> BookType {
717 self.book_type
718 }
719
720 #[getter]
721 #[pyo3(name = "starting_balances")]
722 fn py_starting_balances(&self) -> Vec<String> {
723 self.starting_balances.clone()
724 }
725
726 #[getter]
727 #[pyo3(name = "bar_execution")]
728 const fn py_bar_execution(&self) -> bool {
729 self.bar_execution
730 }
731
732 #[getter]
733 #[pyo3(name = "trade_execution")]
734 const fn py_trade_execution(&self) -> bool {
735 self.trade_execution
736 }
737
738 fn __repr__(&self) -> String {
739 format!("{self:?}")
740 }
741}
742
743#[derive(Debug, Clone)]
745#[cfg_attr(
746 feature = "python",
747 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.backtest", from_py_object)
748)]
749pub struct BacktestDataConfig {
750 data_type: NautilusDataType,
752 catalog_path: String,
754 catalog_fs_protocol: Option<String>,
756 catalog_fs_storage_options: Option<AHashMap<String, String>>,
758 instrument_id: Option<InstrumentId>,
760 instrument_ids: Option<Vec<InstrumentId>>,
762 start_time: Option<UnixNanos>,
764 end_time: Option<UnixNanos>,
766 filter_expr: Option<String>,
768 client_id: Option<ClientId>,
770 #[allow(dead_code)]
772 metadata: Option<AHashMap<String, String>>,
773 bar_spec: Option<BarSpecification>,
775 bar_types: Option<Vec<String>>,
777 optimize_file_loading: bool,
779}
780
781impl BacktestDataConfig {
782 #[allow(clippy::too_many_arguments)]
783 #[must_use]
784 pub fn new(
785 data_type: NautilusDataType,
786 catalog_path: String,
787 catalog_fs_protocol: Option<String>,
788 catalog_fs_storage_options: Option<AHashMap<String, String>>,
789 instrument_id: Option<InstrumentId>,
790 instrument_ids: Option<Vec<InstrumentId>>,
791 start_time: Option<UnixNanos>,
792 end_time: Option<UnixNanos>,
793 filter_expr: Option<String>,
794 client_id: Option<ClientId>,
795 metadata: Option<AHashMap<String, String>>,
796 bar_spec: Option<BarSpecification>,
797 bar_types: Option<Vec<String>>,
798 optimize_file_loading: Option<bool>,
799 ) -> Self {
800 Self {
801 data_type,
802 catalog_path,
803 catalog_fs_protocol,
804 catalog_fs_storage_options,
805 instrument_id,
806 instrument_ids,
807 start_time,
808 end_time,
809 filter_expr,
810 client_id,
811 metadata,
812 bar_spec,
813 bar_types,
814 optimize_file_loading: optimize_file_loading.unwrap_or(false),
815 }
816 }
817
818 #[must_use]
819 pub const fn data_type(&self) -> NautilusDataType {
820 self.data_type
821 }
822
823 #[must_use]
824 pub fn catalog_path(&self) -> &str {
825 &self.catalog_path
826 }
827
828 #[must_use]
829 pub fn catalog_fs_protocol(&self) -> Option<&str> {
830 self.catalog_fs_protocol.as_deref()
831 }
832
833 #[must_use]
834 pub fn catalog_fs_storage_options(&self) -> Option<&AHashMap<String, String>> {
835 self.catalog_fs_storage_options.as_ref()
836 }
837
838 #[must_use]
839 pub fn instrument_id(&self) -> Option<InstrumentId> {
840 self.instrument_id
841 }
842
843 #[must_use]
844 pub fn instrument_ids(&self) -> Option<&[InstrumentId]> {
845 self.instrument_ids.as_deref()
846 }
847
848 #[must_use]
849 pub fn start_time(&self) -> Option<UnixNanos> {
850 self.start_time
851 }
852
853 #[must_use]
854 pub fn end_time(&self) -> Option<UnixNanos> {
855 self.end_time
856 }
857
858 #[must_use]
859 pub fn filter_expr(&self) -> Option<&str> {
860 self.filter_expr.as_deref()
861 }
862
863 #[must_use]
864 pub fn client_id(&self) -> Option<ClientId> {
865 self.client_id
866 }
867
868 #[must_use]
869 pub fn bar_spec(&self) -> Option<BarSpecification> {
870 self.bar_spec
871 }
872
873 #[must_use]
874 pub fn bar_types(&self) -> Option<&[String]> {
875 self.bar_types.as_deref()
876 }
877
878 #[must_use]
879 pub fn optimize_file_loading(&self) -> bool {
880 self.optimize_file_loading
881 }
882
883 #[must_use]
889 pub fn query_identifiers(&self) -> Option<Vec<String>> {
890 if self.data_type == NautilusDataType::Bar {
891 if let Some(bar_types) = &self.bar_types
892 && !bar_types.is_empty()
893 {
894 return Some(bar_types.clone());
895 }
896
897 if let Some(bar_spec) = &self.bar_spec {
899 if let Some(id) = self.instrument_id {
900 return Some(vec![format!("{id}-{bar_spec}-EXTERNAL")]);
901 }
902
903 if let Some(ids) = &self.instrument_ids {
904 let bar_types: Vec<String> = ids
905 .iter()
906 .map(|id| format!("{id}-{bar_spec}-EXTERNAL"))
907 .collect();
908
909 if !bar_types.is_empty() {
910 return Some(bar_types);
911 }
912 }
913 }
914 }
915
916 if let Some(id) = self.instrument_id {
918 return Some(vec![id.to_string()]);
919 }
920
921 if let Some(ids) = &self.instrument_ids {
922 let strs: Vec<String> = ids.iter().map(ToString::to_string).collect();
923 if !strs.is_empty() {
924 return Some(strs);
925 }
926 }
927
928 None
929 }
930
931 pub fn get_instrument_ids(&self) -> anyhow::Result<Vec<InstrumentId>> {
939 if let Some(id) = self.instrument_id {
940 return Ok(vec![id]);
941 }
942
943 if let Some(ids) = &self.instrument_ids {
944 return Ok(ids.clone());
945 }
946
947 if let Some(bar_types) = &self.bar_types {
948 let ids = bar_types
949 .iter()
950 .map(|bt| {
951 bt.parse::<BarType>()
952 .map(|b| b.instrument_id())
953 .map_err(|_| anyhow::anyhow!("Invalid bar type string: '{bt}'"))
954 })
955 .collect::<anyhow::Result<Vec<_>>>()?;
956 return Ok(ids);
957 }
958 Ok(Vec::new())
959 }
960}
961
962#[cfg(feature = "python")]
963#[pyo3::pymethods]
964impl BacktestDataConfig {
965 #[new]
966 #[pyo3(signature = (
967 data_type,
968 catalog_path,
969 catalog_fs_protocol = None,
970 catalog_fs_storage_options = None,
971 instrument_id = None,
972 instrument_ids = None,
973 start_time = None,
974 end_time = None,
975 filter_expr = None,
976 client_id = None,
977 metadata = None,
978 bar_spec = None,
979 bar_types = None,
980 optimize_file_loading = None,
981 ))]
982 #[allow(clippy::too_many_arguments)]
983 fn py_new(
984 data_type: &str,
985 catalog_path: String,
986 catalog_fs_protocol: Option<String>,
987 catalog_fs_storage_options: Option<HashMap<String, String>>,
988 instrument_id: Option<InstrumentId>,
989 instrument_ids: Option<Vec<InstrumentId>>,
990 start_time: Option<u64>,
991 end_time: Option<u64>,
992 filter_expr: Option<String>,
993 client_id: Option<ClientId>,
994 metadata: Option<HashMap<String, String>>,
995 bar_spec: Option<BarSpecification>,
996 bar_types: Option<Vec<String>>,
997 optimize_file_loading: Option<bool>,
998 ) -> pyo3::PyResult<Self> {
999 let data_type = data_type
1000 .parse::<NautilusDataType>()
1001 .map_err(nautilus_core::python::to_pyvalue_err)?;
1002 let catalog_fs_storage_options =
1003 catalog_fs_storage_options.map(|m| m.into_iter().collect());
1004 let metadata = metadata.map(|m| m.into_iter().collect());
1005 Ok(Self::new(
1006 data_type,
1007 catalog_path,
1008 catalog_fs_protocol,
1009 catalog_fs_storage_options,
1010 instrument_id,
1011 instrument_ids,
1012 start_time.map(UnixNanos::from),
1013 end_time.map(UnixNanos::from),
1014 filter_expr,
1015 client_id,
1016 metadata,
1017 bar_spec,
1018 bar_types,
1019 optimize_file_loading,
1020 ))
1021 }
1022
1023 #[getter]
1024 #[pyo3(name = "data_type")]
1025 fn py_data_type(&self) -> String {
1026 self.data_type.to_string()
1027 }
1028
1029 #[getter]
1030 #[pyo3(name = "catalog_path")]
1031 fn py_catalog_path(&self) -> &str {
1032 &self.catalog_path
1033 }
1034
1035 #[getter]
1036 #[pyo3(name = "instrument_id")]
1037 fn py_instrument_id(&self) -> Option<InstrumentId> {
1038 self.instrument_id
1039 }
1040
1041 fn __repr__(&self) -> String {
1042 format!("{self:?}")
1043 }
1044}
1045
1046#[derive(Debug, Clone)]
1049#[cfg_attr(
1050 feature = "python",
1051 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.backtest", from_py_object)
1052)]
1053pub struct BacktestRunConfig {
1054 id: String,
1056 venues: Vec<BacktestVenueConfig>,
1058 data: Vec<BacktestDataConfig>,
1060 engine: BacktestEngineConfig,
1062 chunk_size: Option<usize>,
1065 dispose_on_completion: bool,
1069 start: Option<UnixNanos>,
1072 end: Option<UnixNanos>,
1075}
1076
1077impl BacktestRunConfig {
1078 #[allow(clippy::too_many_arguments)]
1079 #[must_use]
1080 pub fn new(
1081 id: Option<String>,
1082 venues: Vec<BacktestVenueConfig>,
1083 data: Vec<BacktestDataConfig>,
1084 engine: BacktestEngineConfig,
1085 chunk_size: Option<usize>,
1086 dispose_on_completion: Option<bool>,
1087 start: Option<UnixNanos>,
1088 end: Option<UnixNanos>,
1089 ) -> Self {
1090 Self {
1091 id: id.unwrap_or_else(|| UUID4::new().to_string()),
1092 venues,
1093 data,
1094 engine,
1095 chunk_size,
1096 dispose_on_completion: dispose_on_completion.unwrap_or(true),
1097 start,
1098 end,
1099 }
1100 }
1101
1102 #[must_use]
1103 pub fn id(&self) -> &str {
1104 &self.id
1105 }
1106
1107 #[must_use]
1108 pub fn venues(&self) -> &[BacktestVenueConfig] {
1109 &self.venues
1110 }
1111
1112 #[must_use]
1113 pub fn data(&self) -> &[BacktestDataConfig] {
1114 &self.data
1115 }
1116
1117 #[must_use]
1118 pub fn engine(&self) -> &BacktestEngineConfig {
1119 &self.engine
1120 }
1121
1122 #[must_use]
1123 pub fn chunk_size(&self) -> Option<usize> {
1124 self.chunk_size
1125 }
1126
1127 #[must_use]
1128 pub fn dispose_on_completion(&self) -> bool {
1129 self.dispose_on_completion
1130 }
1131
1132 #[must_use]
1133 pub fn start(&self) -> Option<UnixNanos> {
1134 self.start
1135 }
1136
1137 #[must_use]
1138 pub fn end(&self) -> Option<UnixNanos> {
1139 self.end
1140 }
1141}
1142
1143#[cfg(feature = "python")]
1144#[pyo3::pymethods]
1145impl BacktestRunConfig {
1146 #[new]
1147 #[pyo3(signature = (
1148 venues,
1149 data,
1150 engine = None,
1151 id = None,
1152 chunk_size = None,
1153 dispose_on_completion = None,
1154 start = None,
1155 end = None,
1156 ))]
1157 #[allow(clippy::too_many_arguments)]
1158 fn py_new(
1159 venues: Vec<BacktestVenueConfig>,
1160 data: Vec<BacktestDataConfig>,
1161 engine: Option<BacktestEngineConfig>,
1162 id: Option<String>,
1163 chunk_size: Option<usize>,
1164 dispose_on_completion: Option<bool>,
1165 start: Option<u64>,
1166 end: Option<u64>,
1167 ) -> Self {
1168 Self::new(
1169 id,
1170 venues,
1171 data,
1172 engine.unwrap_or_default(),
1173 chunk_size,
1174 dispose_on_completion,
1175 start.map(UnixNanos::from),
1176 end.map(UnixNanos::from),
1177 )
1178 }
1179
1180 #[getter]
1181 #[pyo3(name = "id")]
1182 fn py_id(&self) -> &str {
1183 &self.id
1184 }
1185
1186 fn __repr__(&self) -> String {
1187 format!("{self:?}")
1188 }
1189}