1use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc, time::Duration};
19
20use nautilus_common::{
21 cache::{CacheConfig, database::CacheDatabaseFactory},
22 clients::ExecutionClient,
23 clock::Clock,
24 enums::Environment,
25 factories::{
26 ClientConfig, DataClientFactory, ExecutionClientFactory, SimulatedExecutionClientFactory,
27 },
28 logging::logger::LoggerConfig,
29 msgbus::{
30 BusMessage, MessageBusBackingFactory, MessageBusConfig, MessageBusExternalEgress,
31 MessageBusExternalIngress, external_egress_from_backing, external_io_from_backing,
32 },
33};
34use nautilus_core::UUID4;
35use nautilus_data::client::DataClientAdapter;
36use nautilus_execution::engine::ExecutionEngine;
37use nautilus_model::identifiers::{TraderId, Venue};
38use nautilus_portfolio::config::PortfolioConfig;
39#[cfg(feature = "python")]
40use nautilus_system::trader::Trader;
41use nautilus_system::{
42 clock_factory::ClockFactory,
43 config::StreamingConfig,
44 event_store::{EventStoreFactory, KernelEventStore},
45 kernel::{NautilusKernel, NautilusKernelDependencies},
46};
47use nautilus_trading::ImportableControllerConfig;
48
49use super::{
50 LiveNode,
51 config::{
52 LiveDataEngineConfig, LiveExecutionEngineConfig, LiveNodeConfig, LiveRiskEngineConfig,
53 RoutingConfig, validate_live_environment,
54 },
55};
56use crate::{
57 execution::{
58 client::LiveExecutionClient,
59 manager::{ExecutionManager, ExecutionManagerConfig},
60 },
61 runner::AsyncRunner,
62 socket::SocketReconnectRegistry,
63};
64
65#[derive(Debug)]
66enum ExecutionClientFactoryEntry {
67 Adapter(Box<dyn ExecutionClientFactory>),
68 Simulated(Box<dyn SimulatedExecutionClientFactory>),
69}
70
71pub(crate) struct ExternalMessageBusIngress(Box<dyn MessageBusExternalIngress>);
72
73impl Debug for ExternalMessageBusIngress {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct(stringify!(ExternalMessageBusIngress))
76 .finish_non_exhaustive()
77 }
78}
79
80#[cfg_attr(
86 feature = "python",
87 pyo3::pyclass(module = "nautilus_trader.live", unsendable)
88)]
89pub struct LiveNodeBuilder {
90 name: String,
91 config: LiveNodeConfig,
92 data_client_factories: HashMap<String, Box<dyn DataClientFactory>>,
93 exec_client_factories: HashMap<String, ExecutionClientFactoryEntry>,
94 data_client_configs: HashMap<String, Box<dyn ClientConfig>>,
95 exec_client_configs: HashMap<String, Box<dyn ClientConfig>>,
96 data_client_routing: HashMap<String, RoutingConfig>,
97 exec_client_routing: HashMap<String, RoutingConfig>,
98 event_store_factory: Option<EventStoreFactory>,
99 clock_factory: Option<ClockFactory>,
100 cache_database_factory: Option<Box<dyn CacheDatabaseFactory>>,
101 external_msgbus_factory: Option<Box<dyn MessageBusBackingFactory>>,
102 external_msgbus_egress: Option<Box<dyn MessageBusExternalEgress>>,
103 external_msgbus_ingress: Option<ExternalMessageBusIngress>,
104}
105
106impl Debug for LiveNodeBuilder {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.debug_struct(stringify!(LiveNodeBuilder))
109 .field("name", &self.name)
110 .field("config", &self.config)
111 .field("data_client_factories", &self.data_client_factories.keys())
112 .field("exec_client_factories", &self.exec_client_factories.keys())
113 .field("data_client_configs", &self.data_client_configs.keys())
114 .field("exec_client_configs", &self.exec_client_configs.keys())
115 .field("event_store_factory", &self.event_store_factory.is_some())
116 .field("clock_factory", &self.clock_factory.is_some())
117 .field(
118 "cache_database_factory",
119 &self.cache_database_factory.is_some(),
120 )
121 .field(
122 "external_msgbus_factory",
123 &self.external_msgbus_factory.is_some(),
124 )
125 .field(
126 "external_msgbus_egress",
127 &self.external_msgbus_egress.is_some(),
128 )
129 .field(
130 "external_msgbus_ingress",
131 &self.external_msgbus_ingress.is_some(),
132 )
133 .finish_non_exhaustive()
134 }
135}
136
137impl LiveNodeBuilder {
138 pub fn new(trader_id: TraderId, environment: Environment) -> anyhow::Result<Self> {
144 validate_live_environment(environment)?;
145
146 let config = LiveNodeConfig {
147 environment,
148 trader_id,
149 ..Default::default()
150 };
151
152 Ok(Self {
153 name: "LiveNode".to_string(),
154 config,
155 data_client_factories: HashMap::new(),
156 exec_client_factories: HashMap::new(),
157 data_client_configs: HashMap::new(),
158 exec_client_configs: HashMap::new(),
159 data_client_routing: HashMap::new(),
160 exec_client_routing: HashMap::new(),
161 event_store_factory: None,
162 clock_factory: None,
163 cache_database_factory: None,
164 external_msgbus_factory: None,
165 external_msgbus_egress: None,
166 external_msgbus_ingress: None,
167 })
168 }
169
170 pub fn from_config(config: LiveNodeConfig) -> anyhow::Result<Self> {
176 validate_live_environment(config.environment)?;
177
178 Ok(Self {
179 name: "LiveNode".to_string(),
180 config,
181 data_client_factories: HashMap::new(),
182 exec_client_factories: HashMap::new(),
183 data_client_configs: HashMap::new(),
184 exec_client_configs: HashMap::new(),
185 data_client_routing: HashMap::new(),
186 exec_client_routing: HashMap::new(),
187 event_store_factory: None,
188 clock_factory: None,
189 cache_database_factory: None,
190 external_msgbus_factory: None,
191 external_msgbus_egress: None,
192 external_msgbus_ingress: None,
193 })
194 }
195
196 #[must_use]
198 pub fn name(&self) -> &str {
199 &self.name
200 }
201
202 #[must_use]
204 pub fn with_name(mut self, name: impl Into<String>) -> Self {
205 self.name = name.into();
206 self
207 }
208
209 #[must_use]
211 pub const fn with_instance_id(mut self, instance_id: UUID4) -> Self {
212 self.config.instance_id = Some(instance_id);
213 self
214 }
215
216 #[must_use]
218 pub const fn with_load_state(mut self, load_state: bool) -> Self {
219 self.config.load_state = load_state;
220 self
221 }
222
223 #[must_use]
225 pub const fn with_save_state(mut self, save_state: bool) -> Self {
226 self.config.save_state = save_state;
227 self
228 }
229
230 #[must_use]
239 pub fn with_controller(mut self, controller: ImportableControllerConfig) -> Self {
240 self.config.controller = Some(controller);
241 self
242 }
243
244 #[must_use]
246 pub const fn with_timeout_connection(mut self, timeout_secs: u64) -> Self {
247 self.config.timeout_connection = Duration::from_secs(timeout_secs);
248 self
249 }
250
251 #[must_use]
253 pub const fn with_timeout_reconciliation(mut self, timeout_secs: u64) -> Self {
254 self.config.timeout_reconciliation = Duration::from_secs(timeout_secs);
255 self
256 }
257
258 #[must_use]
260 pub fn with_reconciliation(mut self, reconciliation: bool) -> Self {
261 self.config.exec_engine.reconciliation = reconciliation;
262 self
263 }
264
265 #[must_use]
267 pub fn with_reconciliation_lookback_mins(mut self, mins: u32) -> Self {
268 self.config.exec_engine.reconciliation_lookback_mins = Some(mins);
269 self
270 }
271
272 #[must_use]
274 pub const fn with_timeout_portfolio(mut self, timeout_secs: u64) -> Self {
275 self.config.timeout_portfolio = Duration::from_secs(timeout_secs);
276 self
277 }
278
279 #[must_use]
281 pub const fn with_timeout_disconnection_secs(mut self, timeout_secs: u64) -> Self {
282 self.config.timeout_disconnection = Duration::from_secs(timeout_secs);
283 self
284 }
285
286 #[must_use]
288 pub const fn with_delay_post_stop_secs(mut self, delay_secs: u64) -> Self {
289 self.config.delay_post_stop = Duration::from_secs(delay_secs);
290 self
291 }
292
293 #[must_use]
295 pub const fn with_delay_shutdown_secs(mut self, delay_secs: u64) -> Self {
296 self.config.timeout_shutdown = Duration::from_secs(delay_secs);
297 self
298 }
299
300 #[must_use]
302 pub fn with_clock_factory<F>(mut self, factory: F) -> Self
303 where
304 F: Fn() -> Rc<RefCell<dyn Clock>> + 'static,
305 {
306 self.clock_factory = Some(ClockFactory::new(factory));
307 self
308 }
309
310 #[must_use]
312 pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
313 self.config.cache = Some(config);
314 self
315 }
316
317 #[must_use]
322 pub fn with_cache_database_factory(mut self, factory: Box<dyn CacheDatabaseFactory>) -> Self {
323 self.cache_database_factory = Some(factory);
324 self
325 }
326
327 #[must_use]
332 pub fn with_msgbus_config(mut self, config: MessageBusConfig) -> Self {
333 self.config.msgbus = Some(config);
334 self
335 }
336
337 #[must_use]
339 pub fn with_portfolio_config(mut self, config: PortfolioConfig) -> Self {
340 self.config.portfolio = Some(config);
341 self
342 }
343
344 #[must_use]
349 pub fn with_streaming_config(mut self, config: StreamingConfig) -> Self {
350 self.config.streaming = Some(config);
351 self
352 }
353
354 #[must_use]
359 pub fn with_data_engine_config(mut self, config: LiveDataEngineConfig) -> Self {
360 self.config.data_engine = config;
361 self
362 }
363
364 #[must_use]
369 pub fn with_risk_engine_config(mut self, config: LiveRiskEngineConfig) -> Self {
370 self.config.risk_engine = config;
371 self
372 }
373
374 #[must_use]
379 pub fn with_exec_engine_config(mut self, config: LiveExecutionEngineConfig) -> Self {
380 self.config.exec_engine = config;
381 self
382 }
383
384 #[must_use]
393 pub fn with_event_store<F>(mut self, factory: F) -> Self
394 where
395 F: FnOnce(UUID4, Rc<RefCell<dyn Clock>>) -> anyhow::Result<Box<dyn KernelEventStore>>
396 + 'static,
397 {
398 self.event_store_factory = Some(Box::new(factory));
399 self
400 }
401
402 #[must_use]
404 pub fn with_external_msgbus_egress(
405 mut self,
406 external_egress: Box<dyn MessageBusExternalEgress>,
407 ) -> Self {
408 self.external_msgbus_egress = Some(external_egress);
409 self
410 }
411
412 #[must_use]
414 pub fn with_external_msgbus_factory(
415 mut self,
416 factory: Box<dyn MessageBusBackingFactory>,
417 ) -> Self {
418 self.external_msgbus_factory = Some(factory);
419 self
420 }
421
422 #[must_use]
424 pub fn with_external_ingress(
425 mut self,
426 external_ingress: Box<dyn MessageBusExternalIngress>,
427 ) -> Self {
428 self.external_msgbus_ingress = Some(ExternalMessageBusIngress(external_ingress));
429 self
430 }
431
432 #[must_use]
434 pub fn with_logging(mut self, logging: LoggerConfig) -> Self {
435 self.config.logging = logging;
436 self
437 }
438
439 pub fn add_data_client(
445 self,
446 name: Option<String>,
447 factory: Box<dyn DataClientFactory>,
448 config: Box<dyn ClientConfig>,
449 ) -> anyhow::Result<Self> {
450 self.add_data_client_with_routing(name, factory, config, RoutingConfig::default())
451 }
452
453 pub fn add_data_client_with_routing(
459 mut self,
460 name: Option<String>,
461 factory: Box<dyn DataClientFactory>,
462 config: Box<dyn ClientConfig>,
463 routing: RoutingConfig,
464 ) -> anyhow::Result<Self> {
465 let name = name.unwrap_or_else(|| factory.name().to_string());
466
467 if self.data_client_factories.contains_key(&name) {
468 anyhow::bail!("Data client '{name}' is already registered");
469 }
470
471 self.data_client_factories.insert(name.clone(), factory);
472 self.data_client_configs.insert(name.clone(), config);
473 self.data_client_routing.insert(name, routing);
474 Ok(self)
475 }
476
477 pub fn add_exec_client(
486 self,
487 name: Option<String>,
488 factory: Box<dyn ExecutionClientFactory>,
489 config: Box<dyn ClientConfig>,
490 ) -> anyhow::Result<Self> {
491 self.add_exec_client_with_routing(name, factory, config, RoutingConfig::default())
492 }
493
494 pub fn add_exec_client_with_routing(
500 mut self,
501 name: Option<String>,
502 factory: Box<dyn ExecutionClientFactory>,
503 config: Box<dyn ClientConfig>,
504 routing: RoutingConfig,
505 ) -> anyhow::Result<Self> {
506 let name = name.unwrap_or_else(|| factory.name().to_string());
507
508 if self.exec_client_factories.contains_key(&name) {
509 anyhow::bail!("Execution client '{name}' is already registered");
510 }
511
512 self.exec_client_factories
513 .insert(name.clone(), ExecutionClientFactoryEntry::Adapter(factory));
514 self.exec_client_configs.insert(name.clone(), config);
515 self.exec_client_routing.insert(name, routing);
516 Ok(self)
517 }
518
519 pub fn add_simulated_exec_client(
528 mut self,
529 name: Option<String>,
530 factory: Box<dyn SimulatedExecutionClientFactory>,
531 config: Box<dyn ClientConfig>,
532 ) -> anyhow::Result<Self> {
533 let name = name.unwrap_or_else(|| factory.name().to_string());
534
535 if self.exec_client_factories.contains_key(&name) {
536 anyhow::bail!("Execution client '{name}' is already registered");
537 }
538
539 self.exec_client_factories.insert(
540 name.clone(),
541 ExecutionClientFactoryEntry::Simulated(factory),
542 );
543 self.exec_client_configs.insert(name, config);
544 Ok(self)
545 }
546
547 pub fn build(mut self) -> anyhow::Result<LiveNode> {
558 log::info!(
559 "Building LiveNode with {} data clients and {} execution clients",
560 self.data_client_factories.len(),
561 self.exec_client_factories.len()
562 );
563
564 self.config.validate_runtime_support()?;
565
566 if self.config.event_store.is_some() && self.event_store_factory.is_none() {
567 anyhow::bail!(
568 "LiveNodeConfig.event_store is set but no factory was registered; \
569 call LiveNodeBuilder::with_event_store(...) to install one"
570 );
571 }
572
573 if self.external_msgbus_factory.is_some()
574 && (self.external_msgbus_egress.is_some() || self.external_msgbus_ingress.is_some())
575 {
576 anyhow::bail!(
577 "external message bus factory cannot be combined with injected egress or ingress"
578 );
579 }
580
581 let runner = AsyncRunner::new();
582 runner.bind_senders();
583
584 let socket_registry = SocketReconnectRegistry::default();
585
586 let kernel = NautilusKernel::new_with_dependencies(
587 self.name.clone(),
588 self.config.clone(),
589 NautilusKernelDependencies::default()
590 .with_clock_factory(self.clock_factory.take())
591 .with_event_store_factory(self.event_store_factory.take()),
592 )?;
593 #[cfg(feature = "python")]
594 if let Some(controller) = self.config.controller.as_ref() {
595 Trader::add_controller_from_importable_config(&kernel.trader, controller)?;
596 }
597 #[cfg(not(feature = "python"))]
598 if let Some(controller) = self.config.controller.as_ref() {
599 anyhow::bail!(
600 "LiveNodeConfig.controller for importable controller '{}' requires the python feature",
601 controller.controller_path
602 );
603 }
604
605 self.install_external_msgbus_factory(&kernel)?;
606
607 if let Some(external_egress) = self.external_msgbus_egress.take() {
608 let config = self.config.msgbus.clone().unwrap_or_default();
609 nautilus_common::msgbus::get_message_bus()
610 .borrow_mut()
611 .set_external_egress_config(external_egress, &config)?;
612 }
613
614 for (name, factory) in self.data_client_factories {
615 if let Some(config) = self.data_client_configs.remove(&name) {
616 log::debug!("Creating data client {name}");
617
618 let client = socket_registry.scope(|| {
619 factory.create(
620 &name,
621 config.as_ref(),
622 kernel.cache().into(),
623 kernel.clock(),
624 )
625 })?;
626 let client_id = client.client_id();
627 let venue = client.venue();
628 socket_registry.register_client(client_id);
629
630 let adapter = DataClientAdapter::new(
631 client_id, venue, true, true, client,
634 );
635
636 let routing = self.data_client_routing.remove(&name).unwrap_or_default();
637
638 {
639 let mut data_engine = kernel.data_engine.borrow_mut();
640 data_engine.register_client(adapter, venue);
641
642 if routing.default {
643 data_engine.set_default_client(client_id)?;
644 }
645
646 if let Some(venues) = &routing.venues {
647 for venue_str in venues {
648 data_engine.register_venue_routing(
649 client_id,
650 Venue::new(venue_str.as_str()),
651 )?;
652 }
653 }
654 }
655
656 log::info!("Registered DataClient-{client_id}");
657 } else {
658 log::warn!("No config found for data client factory {name}");
659 }
660 }
661
662 let mut exec_clients = Vec::new();
663
664 for (name, factory) in self.exec_client_factories {
665 if let Some(config) = self.exec_client_configs.remove(&name) {
666 log::debug!("Creating execution client {name}");
667
668 let client = socket_registry.scope(|| match factory {
669 ExecutionClientFactoryEntry::Adapter(factory) => factory.create(
670 self.config.trader_id,
671 &name,
672 config.as_ref(),
673 kernel.cache().into(),
674 ),
675 ExecutionClientFactoryEntry::Simulated(factory) => factory.create(
676 self.config.trader_id,
677 &name,
678 config.as_ref(),
679 kernel.cache(),
680 ),
681 })?;
682 let client = LiveExecutionClient::new(client);
683 let client_id = client.client_id();
684 let venue = client.venue();
685 socket_registry.register_client(client_id);
686
687 let routing = self.exec_client_routing.remove(&name).unwrap_or_default();
688
689 {
690 let mut exec_engine = kernel.exec_engine.borrow_mut();
691 exec_engine.register_client(Box::new(client.clone()))?;
692
693 if routing.default {
694 exec_engine.set_default_client(client_id)?;
695 }
696
697 if let Some(venues) = &routing.venues {
698 for venue_str in venues {
699 exec_engine.register_venue_routing(
700 client_id,
701 Venue::new(venue_str.as_str()),
702 )?;
703 }
704 }
705 }
706 ExecutionEngine::subscribe_venue_instruments(&kernel.exec_engine, venue);
707 exec_clients.push(client);
708
709 log::info!("Registered ExecutionClient-{client_id}");
710 } else {
711 log::warn!("No config found for execution client factory {name}");
712 }
713 }
714
715 let exec_manager_config = ExecutionManagerConfig::from(&self.config.exec_engine)
716 .with_trader_id(self.config.trader_id);
717 let mut exec_manager = ExecutionManager::new(
718 kernel.clock.clone(),
719 kernel.cache.clone(),
720 exec_manager_config,
721 )?;
722
723 for client in &exec_clients {
724 exec_manager.set_position_reconciliation_tolerance(
725 client.account_id(),
726 client.position_reconciliation_tolerance(),
727 );
728 }
729
730 let node = LiveNode::new_from_builder(
731 kernel,
732 runner,
733 self.config,
734 exec_manager,
735 exec_clients,
736 socket_registry,
737 self.cache_database_factory,
738 self.external_msgbus_ingress,
739 );
740 node.load_configured_plugins()?;
741
742 log::info!("Built successfully");
743
744 Ok(node)
745 }
746
747 fn install_external_msgbus_factory(&mut self, kernel: &NautilusKernel) -> anyhow::Result<()> {
748 let Some(factory) = self.external_msgbus_factory.take() else {
749 return Ok(());
750 };
751
752 let config = self.config.msgbus.clone().unwrap_or_default();
753 let has_external_streams = config
754 .external_streams
755 .as_ref()
756 .is_some_and(|streams| !streams.is_empty());
757 config.validate()?;
758 let backing = factory.create(self.config.trader_id, kernel.instance_id, config)?;
759
760 if has_external_streams {
761 let (external_egress, external_ingress) = external_io_from_backing(backing);
762 self.external_msgbus_egress = Some(external_egress);
763 self.external_msgbus_ingress = Some(ExternalMessageBusIngress(external_ingress));
764 } else {
765 self.external_msgbus_egress = Some(external_egress_from_backing(backing));
766 }
767
768 Ok(())
769 }
770}
771
772impl ExternalMessageBusIngress {
773 pub(crate) fn is_closed(&self) -> bool {
774 self.0.is_closed()
775 }
776
777 pub(crate) fn take_receiver(
778 &mut self,
779 ) -> anyhow::Result<tokio::sync::mpsc::Receiver<BusMessage>> {
780 self.0.take_receiver()
781 }
782
783 pub(crate) fn close(&mut self) {
784 self.0.close();
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use std::collections::HashMap;
791
792 use nautilus_common::enums::Environment;
793 use nautilus_model::identifiers::TraderId;
794 use nautilus_trading::ImportableControllerConfig;
795 use rstest::rstest;
796
797 use super::LiveNodeBuilder;
798
799 #[rstest]
800 fn test_with_controller_sets_config_controller() {
801 let controller = ImportableControllerConfig {
802 controller_path: "module:Controller".to_string(),
803 config_path: "module:ControllerConfig".to_string(),
804 config: HashMap::new(),
805 };
806
807 let builder = LiveNodeBuilder::new(TraderId::from("TRADER-001"), Environment::Live)
808 .unwrap()
809 .with_controller(controller);
810
811 assert!(builder.config.controller.is_some());
812 }
813}