1use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc, time::Duration};
19
20use nautilus_common::{
21 cache::CacheConfig,
22 clock::Clock,
23 enums::Environment,
24 factories::{
25 ClientConfig, DataClientFactory, ExecutionClientFactory, SimulatedExecutionClientFactory,
26 },
27 logging::logger::LoggerConfig,
28 msgbus::{
29 BusMessage, MessageBusBackingFactory, MessageBusConfig, MessageBusExternalEgress,
30 MessageBusExternalIngress, external_egress_from_backing, external_io_from_backing,
31 },
32};
33use nautilus_core::UUID4;
34use nautilus_data::client::DataClientAdapter;
35use nautilus_execution::engine::ExecutionEngine;
36use nautilus_model::identifiers::TraderId;
37use nautilus_portfolio::config::PortfolioConfig;
38use nautilus_system::{
39 clock_factory::ClockFactory,
40 config::StreamingConfig,
41 event_store::{EventStoreFactory, KernelEventStore},
42 kernel::{NautilusKernel, NautilusKernelDependencies},
43};
44
45use super::{
46 LiveNode,
47 config::{LiveDataEngineConfig, LiveExecEngineConfig, LiveNodeConfig, LiveRiskEngineConfig},
48};
49use crate::{
50 execution::{
51 client::LiveExecutionClient,
52 manager::{ExecutionManager, ExecutionManagerConfig},
53 },
54 runner::AsyncRunner,
55};
56
57#[derive(Debug)]
58enum ExecutionClientFactoryEntry {
59 Adapter(Box<dyn ExecutionClientFactory>),
60 Simulated(Box<dyn SimulatedExecutionClientFactory>),
61}
62
63pub(crate) struct ExternalMessageBusIngress(Box<dyn MessageBusExternalIngress>);
64
65impl Debug for ExternalMessageBusIngress {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.debug_struct(stringify!(ExternalMessageBusIngress))
68 .finish_non_exhaustive()
69 }
70}
71
72#[cfg_attr(
78 feature = "python",
79 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.live", unsendable)
80)]
81pub struct LiveNodeBuilder {
82 name: String,
83 config: LiveNodeConfig,
84 data_client_factories: HashMap<String, Box<dyn DataClientFactory>>,
85 exec_client_factories: HashMap<String, ExecutionClientFactoryEntry>,
86 data_client_configs: HashMap<String, Box<dyn ClientConfig>>,
87 exec_client_configs: HashMap<String, Box<dyn ClientConfig>>,
88 event_store_factory: Option<EventStoreFactory>,
89 clock_factory: Option<ClockFactory>,
90 external_msgbus_factory: Option<Box<dyn MessageBusBackingFactory>>,
91 external_msgbus_egress: Option<Box<dyn MessageBusExternalEgress>>,
92 external_msgbus_ingress: Option<ExternalMessageBusIngress>,
93}
94
95impl Debug for LiveNodeBuilder {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 f.debug_struct(stringify!(LiveNodeBuilder))
98 .field("name", &self.name)
99 .field("config", &self.config)
100 .field("data_client_factories", &self.data_client_factories.keys())
101 .field("exec_client_factories", &self.exec_client_factories.keys())
102 .field("data_client_configs", &self.data_client_configs.keys())
103 .field("exec_client_configs", &self.exec_client_configs.keys())
104 .field("event_store_factory", &self.event_store_factory.is_some())
105 .field("clock_factory", &self.clock_factory.is_some())
106 .field(
107 "external_msgbus_factory",
108 &self.external_msgbus_factory.is_some(),
109 )
110 .field(
111 "external_msgbus_egress",
112 &self.external_msgbus_egress.is_some(),
113 )
114 .field(
115 "external_msgbus_ingress",
116 &self.external_msgbus_ingress.is_some(),
117 )
118 .finish_non_exhaustive()
119 }
120}
121
122impl LiveNodeBuilder {
123 pub fn new(trader_id: TraderId, environment: Environment) -> anyhow::Result<Self> {
129 match environment {
130 Environment::Sandbox | Environment::Live => {}
131 Environment::Backtest => {
132 anyhow::bail!("LiveNode cannot be used with Backtest environment");
133 }
134 }
135
136 let config = LiveNodeConfig {
137 environment,
138 trader_id,
139 ..Default::default()
140 };
141
142 Ok(Self {
143 name: "LiveNode".to_string(),
144 config,
145 data_client_factories: HashMap::new(),
146 exec_client_factories: HashMap::new(),
147 data_client_configs: HashMap::new(),
148 exec_client_configs: HashMap::new(),
149 event_store_factory: None,
150 clock_factory: None,
151 external_msgbus_factory: None,
152 external_msgbus_egress: None,
153 external_msgbus_ingress: None,
154 })
155 }
156
157 pub fn from_config(config: LiveNodeConfig) -> anyhow::Result<Self> {
163 match config.environment {
164 Environment::Sandbox | Environment::Live => {}
165 Environment::Backtest => {
166 anyhow::bail!("LiveNode cannot be used with Backtest environment");
167 }
168 }
169
170 Ok(Self {
171 name: "LiveNode".to_string(),
172 config,
173 data_client_factories: HashMap::new(),
174 exec_client_factories: HashMap::new(),
175 data_client_configs: HashMap::new(),
176 exec_client_configs: HashMap::new(),
177 event_store_factory: None,
178 clock_factory: None,
179 external_msgbus_factory: None,
180 external_msgbus_egress: None,
181 external_msgbus_ingress: None,
182 })
183 }
184
185 #[must_use]
187 pub fn name(&self) -> &str {
188 &self.name
189 }
190
191 #[must_use]
193 pub fn with_name(mut self, name: impl Into<String>) -> Self {
194 self.name = name.into();
195 self
196 }
197
198 #[must_use]
200 pub const fn with_instance_id(mut self, instance_id: UUID4) -> Self {
201 self.config.instance_id = Some(instance_id);
202 self
203 }
204
205 #[must_use]
207 pub const fn with_load_state(mut self, load_state: bool) -> Self {
208 self.config.load_state = load_state;
209 self
210 }
211
212 #[must_use]
214 pub const fn with_save_state(mut self, save_state: bool) -> Self {
215 self.config.save_state = save_state;
216 self
217 }
218
219 #[must_use]
221 pub const fn with_timeout_connection(mut self, timeout_secs: u64) -> Self {
222 self.config.timeout_connection = Duration::from_secs(timeout_secs);
223 self
224 }
225
226 #[must_use]
228 pub const fn with_timeout_reconciliation(mut self, timeout_secs: u64) -> Self {
229 self.config.timeout_reconciliation = Duration::from_secs(timeout_secs);
230 self
231 }
232
233 #[must_use]
235 pub fn with_reconciliation(mut self, reconciliation: bool) -> Self {
236 self.config.exec_engine.reconciliation = reconciliation;
237 self
238 }
239
240 #[must_use]
242 pub fn with_reconciliation_lookback_mins(mut self, mins: u32) -> Self {
243 self.config.exec_engine.reconciliation_lookback_mins = Some(mins);
244 self
245 }
246
247 #[must_use]
249 pub const fn with_timeout_portfolio(mut self, timeout_secs: u64) -> Self {
250 self.config.timeout_portfolio = Duration::from_secs(timeout_secs);
251 self
252 }
253
254 #[must_use]
256 pub const fn with_timeout_disconnection_secs(mut self, timeout_secs: u64) -> Self {
257 self.config.timeout_disconnection = Duration::from_secs(timeout_secs);
258 self
259 }
260
261 #[must_use]
263 pub const fn with_delay_post_stop_secs(mut self, delay_secs: u64) -> Self {
264 self.config.delay_post_stop = Duration::from_secs(delay_secs);
265 self
266 }
267
268 #[must_use]
270 pub const fn with_delay_shutdown_secs(mut self, delay_secs: u64) -> Self {
271 self.config.timeout_shutdown = Duration::from_secs(delay_secs);
272 self
273 }
274
275 #[must_use]
277 pub fn with_clock_factory<F>(mut self, factory: F) -> Self
278 where
279 F: Fn() -> Rc<RefCell<dyn Clock>> + 'static,
280 {
281 self.clock_factory = Some(ClockFactory::new(factory));
282 self
283 }
284
285 #[must_use]
287 pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
288 self.config.cache = Some(config);
289 self
290 }
291
292 #[must_use]
297 pub fn with_msgbus_config(mut self, config: MessageBusConfig) -> Self {
298 self.config.msgbus = Some(config);
299 self
300 }
301
302 #[must_use]
304 pub fn with_portfolio_config(mut self, config: PortfolioConfig) -> Self {
305 self.config.portfolio = Some(config);
306 self
307 }
308
309 #[must_use]
314 pub fn with_streaming_config(mut self, config: StreamingConfig) -> Self {
315 self.config.streaming = Some(config);
316 self
317 }
318
319 #[must_use]
324 pub fn with_data_engine_config(mut self, config: LiveDataEngineConfig) -> Self {
325 self.config.data_engine = config;
326 self
327 }
328
329 #[must_use]
334 pub fn with_risk_engine_config(mut self, config: LiveRiskEngineConfig) -> Self {
335 self.config.risk_engine = config;
336 self
337 }
338
339 #[must_use]
344 pub fn with_exec_engine_config(mut self, config: LiveExecEngineConfig) -> Self {
345 self.config.exec_engine = config;
346 self
347 }
348
349 #[must_use]
358 pub fn with_event_store<F>(mut self, factory: F) -> Self
359 where
360 F: FnOnce(UUID4, Rc<RefCell<dyn Clock>>) -> anyhow::Result<Box<dyn KernelEventStore>>
361 + 'static,
362 {
363 self.event_store_factory = Some(Box::new(factory));
364 self
365 }
366
367 #[must_use]
369 pub fn with_external_msgbus_egress(
370 mut self,
371 external_egress: Box<dyn MessageBusExternalEgress>,
372 ) -> Self {
373 self.external_msgbus_egress = Some(external_egress);
374 self
375 }
376
377 #[must_use]
379 pub fn with_external_msgbus_factory(
380 mut self,
381 factory: Box<dyn MessageBusBackingFactory>,
382 ) -> Self {
383 self.external_msgbus_factory = Some(factory);
384 self
385 }
386
387 #[must_use]
389 pub fn with_external_ingress(
390 mut self,
391 external_ingress: Box<dyn MessageBusExternalIngress>,
392 ) -> Self {
393 self.external_msgbus_ingress = Some(ExternalMessageBusIngress(external_ingress));
394 self
395 }
396
397 #[must_use]
399 pub fn with_logging(mut self, logging: LoggerConfig) -> Self {
400 self.config.logging = logging;
401 self
402 }
403
404 pub fn add_data_client(
410 mut self,
411 name: Option<String>,
412 factory: Box<dyn DataClientFactory>,
413 config: Box<dyn ClientConfig>,
414 ) -> anyhow::Result<Self> {
415 let name = name.unwrap_or_else(|| factory.name().to_string());
416
417 if self.data_client_factories.contains_key(&name) {
418 anyhow::bail!("Data client '{name}' is already registered");
419 }
420
421 self.data_client_factories.insert(name.clone(), factory);
422 self.data_client_configs.insert(name, config);
423 Ok(self)
424 }
425
426 pub fn add_exec_client(
432 mut self,
433 name: Option<String>,
434 factory: Box<dyn ExecutionClientFactory>,
435 config: Box<dyn ClientConfig>,
436 ) -> anyhow::Result<Self> {
437 let name = name.unwrap_or_else(|| factory.name().to_string());
438
439 if self.exec_client_factories.contains_key(&name) {
440 anyhow::bail!("Execution client '{name}' is already registered");
441 }
442
443 self.exec_client_factories
444 .insert(name.clone(), ExecutionClientFactoryEntry::Adapter(factory));
445 self.exec_client_configs.insert(name, config);
446 Ok(self)
447 }
448
449 pub fn add_simulated_exec_client(
458 mut self,
459 name: Option<String>,
460 factory: Box<dyn SimulatedExecutionClientFactory>,
461 config: Box<dyn ClientConfig>,
462 ) -> anyhow::Result<Self> {
463 let name = name.unwrap_or_else(|| factory.name().to_string());
464
465 if self.exec_client_factories.contains_key(&name) {
466 anyhow::bail!("Execution client '{name}' is already registered");
467 }
468
469 self.exec_client_factories.insert(
470 name.clone(),
471 ExecutionClientFactoryEntry::Simulated(factory),
472 );
473 self.exec_client_configs.insert(name, config);
474 Ok(self)
475 }
476
477 pub fn build(mut self) -> anyhow::Result<LiveNode> {
488 log::info!(
489 "Building LiveNode with {} data clients and {} execution clients",
490 self.data_client_factories.len(),
491 self.exec_client_factories.len()
492 );
493
494 self.config.validate_runtime_support()?;
495
496 if self.config.event_store.is_some() && self.event_store_factory.is_none() {
497 anyhow::bail!(
498 "LiveNodeConfig.event_store is set but no factory was registered; \
499 call LiveNodeBuilder::with_event_store(...) to install one"
500 );
501 }
502
503 if self.external_msgbus_factory.is_some()
504 && (self.external_msgbus_egress.is_some() || self.external_msgbus_ingress.is_some())
505 {
506 anyhow::bail!(
507 "external message bus factory cannot be combined with injected egress or ingress"
508 );
509 }
510
511 let runner = AsyncRunner::new();
512 runner.bind_senders();
513
514 let kernel = NautilusKernel::new_with_dependencies(
515 self.name.clone(),
516 self.config.clone(),
517 NautilusKernelDependencies::default()
518 .with_clock_factory(self.clock_factory.take())
519 .with_event_store_factory(self.event_store_factory.take()),
520 )?;
521
522 self.install_external_msgbus_factory(&kernel)?;
523
524 if let Some(external_egress) = self.external_msgbus_egress.take() {
525 let config = self.config.msgbus.clone().unwrap_or_default();
526 nautilus_common::msgbus::get_message_bus()
527 .borrow_mut()
528 .set_external_egress_config(external_egress, &config)?;
529 }
530
531 for (name, factory) in self.data_client_factories {
532 if let Some(config) = self.data_client_configs.remove(&name) {
533 log::debug!("Creating data client {name}");
534
535 let client = factory.create(
536 &name,
537 config.as_ref(),
538 kernel.cache().into(),
539 kernel.clock(),
540 )?;
541 let client_id = client.client_id();
542 let venue = client.venue();
543
544 let adapter = DataClientAdapter::new(
545 client_id, venue, true, true, client,
548 );
549
550 kernel
551 .data_engine
552 .borrow_mut()
553 .register_client(adapter, venue);
554
555 log::info!("Registered DataClient-{client_id}");
556 } else {
557 log::warn!("No config found for data client factory {name}");
558 }
559 }
560
561 let mut exec_clients = Vec::new();
562
563 for (name, factory) in self.exec_client_factories {
564 if let Some(config) = self.exec_client_configs.remove(&name) {
565 log::debug!("Creating execution client {name}");
566
567 let client = match factory {
568 ExecutionClientFactoryEntry::Adapter(factory) => {
569 factory.create(&name, config.as_ref(), kernel.cache().into())?
570 }
571 ExecutionClientFactoryEntry::Simulated(factory) => {
572 factory.create(&name, config.as_ref(), kernel.cache())?
573 }
574 };
575 let client = LiveExecutionClient::new(client);
576 let client_id = client.client_id();
577 let venue = client.venue();
578
579 kernel
580 .exec_engine
581 .borrow_mut()
582 .register_client(Box::new(client.clone()))?;
583 ExecutionEngine::subscribe_venue_instruments(&kernel.exec_engine, venue);
584 exec_clients.push(client);
585
586 log::info!("Registered ExecutionClient-{client_id}");
587 } else {
588 log::warn!("No config found for execution client factory {name}");
589 }
590 }
591
592 let exec_manager_config = ExecutionManagerConfig::from(&self.config.exec_engine)
593 .with_trader_id(self.config.trader_id);
594 let exec_manager = ExecutionManager::new(
595 kernel.clock.clone(),
596 kernel.cache.clone(),
597 exec_manager_config,
598 );
599
600 let node = LiveNode::new_from_builder(
601 kernel,
602 runner,
603 self.config,
604 exec_manager,
605 exec_clients,
606 self.external_msgbus_ingress,
607 );
608 node.load_configured_plugins()?;
609
610 log::info!("Built successfully");
611
612 Ok(node)
613 }
614
615 fn install_external_msgbus_factory(&mut self, kernel: &NautilusKernel) -> anyhow::Result<()> {
616 let Some(factory) = self.external_msgbus_factory.take() else {
617 return Ok(());
618 };
619
620 let config = self.config.msgbus.clone().unwrap_or_default();
621 let has_external_streams = config
622 .external_streams
623 .as_ref()
624 .is_some_and(|streams| !streams.is_empty());
625 config.validate()?;
626 let backing = factory.create(self.config.trader_id, kernel.instance_id, config)?;
627
628 if has_external_streams {
629 let (external_egress, external_ingress) = external_io_from_backing(backing);
630 self.external_msgbus_egress = Some(external_egress);
631 self.external_msgbus_ingress = Some(ExternalMessageBusIngress(external_ingress));
632 } else {
633 self.external_msgbus_egress = Some(external_egress_from_backing(backing));
634 }
635
636 Ok(())
637 }
638}
639
640impl ExternalMessageBusIngress {
641 pub(crate) fn is_closed(&self) -> bool {
642 self.0.is_closed()
643 }
644
645 pub(crate) fn take_receiver(
646 &mut self,
647 ) -> anyhow::Result<tokio::sync::mpsc::Receiver<BusMessage>> {
648 self.0.take_receiver()
649 }
650
651 pub(crate) fn close(&mut self) {
652 self.0.close();
653 }
654}