1use std::{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::{
28 engine::config::ExecutionEngineConfig,
29 models::{
30 fee::FeeModelAny,
31 fill::FillModelAny,
32 latency::{LatencyModel, LatencyModelAny},
33 },
34};
35use nautilus_model::{
36 accounts::margin_model::MarginModelAny,
37 data::{BarSpecification, BarType},
38 enums::{AccountType, BookType, OmsType, OtoTriggerMode},
39 identifiers::{ClientId, InstrumentId, TraderId, Venue},
40 types::{Currency, Money, Price},
41};
42use nautilus_portfolio::config::PortfolioConfig;
43use nautilus_risk::engine::config::RiskEngineConfig;
44use nautilus_system::config::{NautilusKernelConfig, StreamingConfig};
45use rust_decimal::Decimal;
46use ustr::Ustr;
47
48use crate::modules::{SimulationModule, SimulationModuleAny};
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum NautilusDataType {
53 QuoteTick,
54 TradeTick,
55 Bar,
56 OrderBookDelta,
57 OrderBookDepth10,
58 MarkPriceUpdate,
59 IndexPriceUpdate,
60 FundingRateUpdate,
61 InstrumentStatus,
62 OptionGreeks,
63 InstrumentClose,
64}
65
66impl Display for NautilusDataType {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 std::fmt::Debug::fmt(self, f)
69 }
70}
71
72impl FromStr for NautilusDataType {
73 type Err = anyhow::Error;
74
75 fn from_str(s: &str) -> anyhow::Result<Self> {
76 match s {
77 stringify!(QuoteTick) => Ok(Self::QuoteTick),
78 stringify!(TradeTick) => Ok(Self::TradeTick),
79 stringify!(Bar) => Ok(Self::Bar),
80 stringify!(OrderBookDelta) => Ok(Self::OrderBookDelta),
81 stringify!(OrderBookDepth10) => Ok(Self::OrderBookDepth10),
82 stringify!(MarkPriceUpdate) => Ok(Self::MarkPriceUpdate),
83 stringify!(IndexPriceUpdate) => Ok(Self::IndexPriceUpdate),
84 stringify!(FundingRateUpdate) => Ok(Self::FundingRateUpdate),
85 stringify!(InstrumentStatus) => Ok(Self::InstrumentStatus),
86 stringify!(OptionGreeks) => Ok(Self::OptionGreeks),
87 stringify!(InstrumentClose) => Ok(Self::InstrumentClose),
88 _ => anyhow::bail!("Invalid `NautilusDataType`: '{s}'"),
89 }
90 }
91}
92
93#[derive(Debug, Clone, bon::Builder)]
95#[cfg_attr(
96 feature = "python",
97 pyo3::pyclass(
98 module = "nautilus_trader.core.nautilus_pyo3.backtest",
99 from_py_object,
100 unsendable
101 )
102)]
103#[cfg_attr(
104 feature = "python",
105 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.backtest")
106)]
107pub struct BacktestEngineConfig {
108 #[builder(default = Environment::Backtest)]
110 pub environment: Environment,
111 #[builder(default)]
113 pub trader_id: TraderId,
114 #[builder(default)]
116 pub load_state: bool,
117 #[builder(default)]
119 pub save_state: bool,
120 #[builder(default)]
124 pub shutdown_on_error: bool,
125 #[builder(default)]
127 pub logging: LoggerConfig,
128 pub instance_id: Option<UUID4>,
130 #[builder(default = Duration::from_secs(60))]
132 pub timeout_connection: Duration,
133 #[builder(default = Duration::from_secs(30))]
135 pub timeout_reconciliation: Duration,
136 #[builder(default = Duration::from_secs(10))]
138 pub timeout_portfolio: Duration,
139 #[builder(default = Duration::from_secs(10))]
141 pub timeout_disconnection: Duration,
142 #[builder(default = Duration::from_secs(10))]
144 pub delay_post_stop: Duration,
145 #[builder(default = Duration::from_secs(5))]
147 pub timeout_shutdown: Duration,
148 pub cache: Option<CacheConfig>,
154 pub msgbus: Option<MessageBusConfig>,
156 pub data_engine: Option<DataEngineConfig>,
158 pub risk_engine: Option<RiskEngineConfig>,
160 pub exec_engine: Option<ExecutionEngineConfig>,
162 pub portfolio: Option<PortfolioConfig>,
164 pub streaming: Option<StreamingConfig>,
166 #[builder(default)]
168 pub bypass_logging: bool,
169 #[builder(default = true)]
171 pub run_analysis: bool,
172}
173
174impl NautilusKernelConfig for BacktestEngineConfig {
175 fn environment(&self) -> Environment {
176 self.environment
177 }
178
179 fn trader_id(&self) -> TraderId {
180 self.trader_id
181 }
182
183 fn load_state(&self) -> bool {
184 self.load_state
185 }
186
187 fn save_state(&self) -> bool {
188 self.save_state
189 }
190
191 fn shutdown_on_error(&self) -> bool {
192 self.shutdown_on_error
193 }
194
195 fn logging(&self) -> LoggerConfig {
196 self.logging.clone()
197 }
198
199 fn instance_id(&self) -> Option<UUID4> {
200 self.instance_id
201 }
202
203 fn timeout_connection(&self) -> Duration {
204 self.timeout_connection
205 }
206
207 fn timeout_reconciliation(&self) -> Duration {
208 self.timeout_reconciliation
209 }
210
211 fn timeout_portfolio(&self) -> Duration {
212 self.timeout_portfolio
213 }
214
215 fn timeout_disconnection(&self) -> Duration {
216 self.timeout_disconnection
217 }
218
219 fn delay_post_stop(&self) -> Duration {
220 self.delay_post_stop
221 }
222
223 fn timeout_shutdown(&self) -> Duration {
224 self.timeout_shutdown
225 }
226
227 fn cache(&self) -> Option<CacheConfig> {
228 self.cache.clone()
229 }
230
231 fn msgbus(&self) -> Option<MessageBusConfig> {
232 self.msgbus.clone()
233 }
234
235 fn data_engine(&self) -> Option<DataEngineConfig> {
236 self.data_engine.clone()
237 }
238
239 fn risk_engine(&self) -> Option<RiskEngineConfig> {
240 self.risk_engine.clone()
241 }
242
243 fn exec_engine(&self) -> Option<ExecutionEngineConfig> {
244 self.exec_engine.clone()
245 }
246
247 fn portfolio(&self) -> Option<PortfolioConfig> {
248 self.portfolio
249 }
250
251 fn streaming(&self) -> Option<StreamingConfig> {
252 self.streaming.clone()
253 }
254}
255
256impl Default for BacktestEngineConfig {
257 fn default() -> Self {
258 Self::builder().build()
259 }
260}
261
262#[derive(bon::Builder)]
271#[allow(missing_debug_implementations)]
272pub struct SimulatedVenueConfig {
273 pub venue: Venue,
274 pub oms_type: OmsType,
275 pub account_type: AccountType,
276 pub book_type: BookType,
277 pub starting_balances: Vec<Money>,
278 pub base_currency: Option<Currency>,
279 pub default_leverage: Option<Decimal>,
282 #[builder(default)]
283 pub leverages: AHashMap<InstrumentId, Decimal>,
284 pub margin_model: Option<MarginModelAny>,
285 #[builder(default)]
286 pub modules: Vec<Box<dyn SimulationModule>>,
287 #[builder(default)]
288 pub fill_model: FillModelAny,
289 #[builder(default)]
290 pub fee_model: FeeModelAny,
291 pub latency_model: Option<Box<dyn LatencyModel>>,
292 #[builder(default = false)]
293 pub routing: bool,
294 #[builder(default = true)]
295 pub reject_stop_orders: bool,
296 #[builder(default = true)]
297 pub support_gtd_orders: bool,
298 #[builder(default = true)]
299 pub support_contingent_orders: bool,
300 #[builder(default = true)]
301 pub use_position_ids: bool,
302 #[builder(default = false)]
303 pub use_random_ids: bool,
304 #[builder(default = true)]
305 pub use_reduce_only: bool,
306 #[builder(default = true)]
307 pub use_message_queue: bool,
308 #[builder(default = false)]
309 pub use_market_order_acks: bool,
310 #[builder(default = true)]
311 pub bar_execution: bool,
312 #[builder(default = false)]
313 pub bar_adaptive_high_low_ordering: bool,
314 #[builder(default = true)]
315 pub trade_execution: bool,
316 #[builder(default = false)]
317 pub liquidity_consumption: bool,
318 #[builder(default = false)]
319 pub allow_cash_borrowing: bool,
320 #[builder(default = false)]
321 pub frozen_account: bool,
322 #[builder(default = false)]
323 pub queue_position: bool,
324 #[builder(default = false)]
325 pub oto_full_trigger: bool,
326 #[builder(default = 0)]
327 pub price_protection_points: u32,
328 #[builder(default)]
330 pub settlement_prices: AHashMap<InstrumentId, Price>,
331 #[builder(default = false)]
333 pub liquidation_enabled: bool,
334 #[builder(default = 1.0)]
337 pub liquidation_trigger_ratio: f64,
338 #[builder(default = true)]
340 pub liquidation_cancel_open_orders: bool,
341}
342
343#[derive(Debug, Clone, bon::Builder)]
345#[cfg_attr(
346 feature = "python",
347 pyo3::pyclass(
348 module = "nautilus_trader.core.nautilus_pyo3.backtest",
349 from_py_object,
350 unsendable
351 )
352)]
353#[cfg_attr(
354 feature = "python",
355 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.backtest")
356)]
357pub struct BacktestVenueConfig {
358 name: Ustr,
360 oms_type: OmsType,
362 account_type: AccountType,
364 book_type: BookType,
366 #[builder(default)]
368 starting_balances: Vec<String>,
369 #[builder(default)]
371 routing: bool,
372 #[builder(default)]
374 frozen_account: bool,
375 #[builder(default = true)]
377 reject_stop_orders: bool,
378 #[builder(default = true)]
380 support_gtd_orders: bool,
381 #[builder(default = true)]
384 support_contingent_orders: bool,
385 #[builder(default = true)]
387 use_position_ids: bool,
388 #[builder(default)]
391 use_random_ids: bool,
392 #[builder(default = true)]
394 use_reduce_only: bool,
395 #[builder(default = true)]
397 bar_execution: bool,
398 #[builder(default)]
405 bar_adaptive_high_low_ordering: bool,
406 #[builder(default = true)]
408 trade_execution: bool,
409 #[builder(default)]
411 use_market_order_acks: bool,
412 #[builder(default)]
414 liquidity_consumption: bool,
415 #[builder(default)]
417 allow_cash_borrowing: bool,
418 #[builder(default)]
420 queue_position: bool,
421 #[builder(default)]
423 oto_trigger_mode: OtoTriggerMode,
424 base_currency: Option<Currency>,
426 #[builder(default = Decimal::ONE)]
428 default_leverage: Decimal,
429 leverages: Option<AHashMap<InstrumentId, Decimal>>,
431 margin_model: Option<MarginModelAny>,
433 #[builder(default)]
435 modules: Vec<SimulationModuleAny>,
436 fill_model: Option<FillModelAny>,
438 latency_model: Option<LatencyModelAny>,
440 fee_model: Option<FeeModelAny>,
442 #[builder(default)]
445 price_protection_points: u32,
446 settlement_prices: Option<AHashMap<InstrumentId, f64>>,
448 #[builder(default)]
450 liquidation_enabled: bool,
451 #[builder(default = 1.0)]
454 liquidation_trigger_ratio: f64,
455 #[builder(default = true)]
457 liquidation_cancel_open_orders: bool,
458}
459
460impl BacktestVenueConfig {
461 #[must_use]
462 pub fn name(&self) -> Ustr {
463 self.name
464 }
465
466 #[must_use]
467 pub fn oms_type(&self) -> OmsType {
468 self.oms_type
469 }
470
471 #[must_use]
472 pub fn account_type(&self) -> AccountType {
473 self.account_type
474 }
475
476 #[must_use]
477 pub fn book_type(&self) -> BookType {
478 self.book_type
479 }
480
481 #[must_use]
482 pub fn starting_balances(&self) -> &[String] {
483 &self.starting_balances
484 }
485
486 #[must_use]
487 pub fn routing(&self) -> bool {
488 self.routing
489 }
490
491 #[must_use]
492 pub fn frozen_account(&self) -> bool {
493 self.frozen_account
494 }
495
496 #[must_use]
497 pub fn reject_stop_orders(&self) -> bool {
498 self.reject_stop_orders
499 }
500
501 #[must_use]
502 pub fn support_gtd_orders(&self) -> bool {
503 self.support_gtd_orders
504 }
505
506 #[must_use]
507 pub fn support_contingent_orders(&self) -> bool {
508 self.support_contingent_orders
509 }
510
511 #[must_use]
512 pub fn use_position_ids(&self) -> bool {
513 self.use_position_ids
514 }
515
516 #[must_use]
517 pub fn use_random_ids(&self) -> bool {
518 self.use_random_ids
519 }
520
521 #[must_use]
522 pub fn use_reduce_only(&self) -> bool {
523 self.use_reduce_only
524 }
525
526 #[must_use]
527 pub fn bar_execution(&self) -> bool {
528 self.bar_execution
529 }
530
531 #[must_use]
532 pub fn bar_adaptive_high_low_ordering(&self) -> bool {
533 self.bar_adaptive_high_low_ordering
534 }
535
536 #[must_use]
537 pub fn trade_execution(&self) -> bool {
538 self.trade_execution
539 }
540
541 #[must_use]
542 pub fn use_market_order_acks(&self) -> bool {
543 self.use_market_order_acks
544 }
545
546 #[must_use]
547 pub fn liquidity_consumption(&self) -> bool {
548 self.liquidity_consumption
549 }
550
551 #[must_use]
552 pub fn allow_cash_borrowing(&self) -> bool {
553 self.allow_cash_borrowing
554 }
555
556 #[must_use]
557 pub fn queue_position(&self) -> bool {
558 self.queue_position
559 }
560
561 #[must_use]
562 pub fn oto_trigger_mode(&self) -> OtoTriggerMode {
563 self.oto_trigger_mode
564 }
565
566 #[must_use]
567 pub fn base_currency(&self) -> Option<Currency> {
568 self.base_currency
569 }
570
571 #[must_use]
572 pub fn default_leverage(&self) -> Decimal {
573 self.default_leverage
574 }
575
576 #[must_use]
577 pub fn leverages(&self) -> Option<&AHashMap<InstrumentId, Decimal>> {
578 self.leverages.as_ref()
579 }
580
581 #[must_use]
582 pub fn margin_model(&self) -> Option<&MarginModelAny> {
583 self.margin_model.as_ref()
584 }
585
586 #[must_use]
587 pub fn modules(&self) -> &[SimulationModuleAny] {
588 &self.modules
589 }
590
591 #[must_use]
592 pub fn fill_model(&self) -> Option<&FillModelAny> {
593 self.fill_model.as_ref()
594 }
595
596 #[must_use]
597 pub fn latency_model(&self) -> Option<&LatencyModelAny> {
598 self.latency_model.as_ref()
599 }
600
601 #[must_use]
602 pub fn fee_model(&self) -> Option<&FeeModelAny> {
603 self.fee_model.as_ref()
604 }
605
606 #[must_use]
607 pub fn price_protection_points(&self) -> u32 {
608 self.price_protection_points
609 }
610
611 #[must_use]
612 pub fn settlement_prices(&self) -> Option<&AHashMap<InstrumentId, f64>> {
613 self.settlement_prices.as_ref()
614 }
615
616 #[must_use]
617 pub fn liquidation_enabled(&self) -> bool {
618 self.liquidation_enabled
619 }
620
621 #[must_use]
622 pub fn liquidation_trigger_ratio(&self) -> f64 {
623 self.liquidation_trigger_ratio
624 }
625
626 #[must_use]
627 pub fn liquidation_cancel_open_orders(&self) -> bool {
628 self.liquidation_cancel_open_orders
629 }
630}
631
632#[derive(Debug, Clone, bon::Builder)]
634#[cfg_attr(
635 feature = "python",
636 pyo3::pyclass(
637 module = "nautilus_trader.core.nautilus_pyo3.backtest",
638 from_py_object,
639 unsendable
640 )
641)]
642#[cfg_attr(
643 feature = "python",
644 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.backtest")
645)]
646pub struct BacktestDataConfig {
647 data_type: NautilusDataType,
649 catalog_path: String,
651 catalog_fs_protocol: Option<String>,
653 catalog_fs_storage_options: Option<AHashMap<String, String>>,
655 catalog_fs_rust_storage_options: Option<AHashMap<String, String>>,
657 instrument_id: Option<InstrumentId>,
659 instrument_ids: Option<Vec<InstrumentId>>,
661 start_time: Option<UnixNanos>,
663 end_time: Option<UnixNanos>,
665 filter_expr: Option<String>,
667 client_id: Option<ClientId>,
669 #[allow(dead_code)]
671 metadata: Option<AHashMap<String, String>>,
672 bar_spec: Option<BarSpecification>,
674 bar_types: Option<Vec<String>>,
676 #[builder(default)]
678 optimize_file_loading: bool,
679}
680
681impl BacktestDataConfig {
682 #[must_use]
683 pub const fn data_type(&self) -> NautilusDataType {
684 self.data_type
685 }
686
687 #[must_use]
688 pub fn catalog_path(&self) -> &str {
689 &self.catalog_path
690 }
691
692 #[must_use]
693 pub fn catalog_fs_protocol(&self) -> Option<&str> {
694 self.catalog_fs_protocol.as_deref()
695 }
696
697 #[must_use]
698 pub fn catalog_fs_storage_options(&self) -> Option<&AHashMap<String, String>> {
699 self.catalog_fs_storage_options.as_ref()
700 }
701
702 #[must_use]
703 pub fn catalog_fs_rust_storage_options(&self) -> Option<&AHashMap<String, String>> {
704 self.catalog_fs_rust_storage_options.as_ref()
705 }
706
707 #[must_use]
708 pub fn instrument_id(&self) -> Option<InstrumentId> {
709 self.instrument_id
710 }
711
712 #[must_use]
713 pub fn instrument_ids(&self) -> Option<&[InstrumentId]> {
714 self.instrument_ids.as_deref()
715 }
716
717 #[must_use]
718 pub fn start_time(&self) -> Option<UnixNanos> {
719 self.start_time
720 }
721
722 #[must_use]
723 pub fn end_time(&self) -> Option<UnixNanos> {
724 self.end_time
725 }
726
727 #[must_use]
728 pub fn filter_expr(&self) -> Option<&str> {
729 self.filter_expr.as_deref()
730 }
731
732 #[must_use]
733 pub fn client_id(&self) -> Option<ClientId> {
734 self.client_id
735 }
736
737 #[must_use]
738 pub fn bar_spec(&self) -> Option<BarSpecification> {
739 self.bar_spec
740 }
741
742 #[must_use]
743 pub fn bar_types(&self) -> Option<&[String]> {
744 self.bar_types.as_deref()
745 }
746
747 #[must_use]
748 pub fn optimize_file_loading(&self) -> bool {
749 self.optimize_file_loading
750 }
751
752 #[must_use]
758 pub fn query_identifiers(&self) -> Option<Vec<String>> {
759 if self.data_type == NautilusDataType::Bar {
760 if let Some(bar_types) = &self.bar_types
761 && !bar_types.is_empty()
762 {
763 return Some(bar_types.clone());
764 }
765
766 if let Some(bar_spec) = &self.bar_spec {
768 if let Some(id) = self.instrument_id {
769 return Some(vec![format!("{id}-{bar_spec}-EXTERNAL")]);
770 }
771
772 if let Some(ids) = &self.instrument_ids {
773 let bar_types: Vec<String> = ids
774 .iter()
775 .map(|id| format!("{id}-{bar_spec}-EXTERNAL"))
776 .collect();
777
778 if !bar_types.is_empty() {
779 return Some(bar_types);
780 }
781 }
782 }
783 }
784
785 if let Some(id) = self.instrument_id {
787 return Some(vec![id.to_string()]);
788 }
789
790 if let Some(ids) = &self.instrument_ids {
791 let strs: Vec<String> = ids.iter().map(ToString::to_string).collect();
792 if !strs.is_empty() {
793 return Some(strs);
794 }
795 }
796
797 None
798 }
799
800 pub fn get_instrument_ids(&self) -> anyhow::Result<Vec<InstrumentId>> {
808 if let Some(id) = self.instrument_id {
809 return Ok(vec![id]);
810 }
811
812 if let Some(ids) = &self.instrument_ids {
813 return Ok(ids.clone());
814 }
815
816 if let Some(bar_types) = &self.bar_types {
817 let ids = bar_types
818 .iter()
819 .map(|bt| {
820 bt.parse::<BarType>()
821 .map(|b| b.instrument_id())
822 .map_err(|_| anyhow::anyhow!("Invalid bar type string: '{bt}'"))
823 })
824 .collect::<anyhow::Result<Vec<_>>>()?;
825 return Ok(ids);
826 }
827 Ok(Vec::new())
828 }
829}
830
831#[derive(Debug, Clone, bon::Builder)]
834#[cfg_attr(
835 feature = "python",
836 pyo3::pyclass(
837 module = "nautilus_trader.core.nautilus_pyo3.backtest",
838 from_py_object,
839 unsendable
840 )
841)]
842#[cfg_attr(
843 feature = "python",
844 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.backtest")
845)]
846pub struct BacktestRunConfig {
847 #[builder(default = UUID4::new().to_string())]
849 id: String,
850 venues: Vec<BacktestVenueConfig>,
852 data: Vec<BacktestDataConfig>,
854 #[builder(default)]
856 engine: BacktestEngineConfig,
857 chunk_size: Option<usize>,
860 #[builder(default)]
862 raise_exception: bool,
863 #[builder(default = true)]
867 dispose_on_completion: bool,
868 start: Option<UnixNanos>,
871 end: Option<UnixNanos>,
874}
875
876impl BacktestRunConfig {
877 #[must_use]
878 pub fn id(&self) -> &str {
879 &self.id
880 }
881
882 #[must_use]
883 pub fn venues(&self) -> &[BacktestVenueConfig] {
884 &self.venues
885 }
886
887 #[must_use]
888 pub fn data(&self) -> &[BacktestDataConfig] {
889 &self.data
890 }
891
892 #[must_use]
893 pub fn engine(&self) -> &BacktestEngineConfig {
894 &self.engine
895 }
896
897 #[must_use]
898 pub fn chunk_size(&self) -> Option<usize> {
899 self.chunk_size
900 }
901
902 #[must_use]
903 pub fn raise_exception(&self) -> bool {
904 self.raise_exception
905 }
906
907 #[must_use]
908 pub fn dispose_on_completion(&self) -> bool {
909 self.dispose_on_completion
910 }
911
912 #[must_use]
913 pub fn start(&self) -> Option<UnixNanos> {
914 self.start
915 }
916
917 #[must_use]
918 pub fn end(&self) -> Option<UnixNanos> {
919 self.end
920 }
921}