1pub mod bar;
32pub mod book;
33pub mod config;
34
35#[cfg(feature = "defi")]
36pub mod pool;
37
38#[cfg(feature = "streaming")]
39mod streaming;
40
41mod commands;
42mod dispatch;
43mod futures;
44mod handlers;
45mod option_chain;
46mod registry;
47mod requests;
48mod responses;
49mod spread_quote;
50mod synthetic;
51mod time_range;
52
53use std::{
54 any::{Any, type_name},
55 cell::RefCell,
56 collections::VecDeque,
57 fmt::{Debug, Display},
58 mem,
59 num::NonZeroUsize,
60 rc::Rc,
61 str::FromStr,
62};
63
64use ::futures::future::join_all;
65use ahash::{AHashMap, AHashSet};
66use anyhow::Context;
67pub use bar::BarAggregatorSubscription;
68use bar::{BarAggregationSubscription, BarAggregatorKey, bar_aggregator_key};
69use book::{
70 BookDeltasKey, BookSnapshotInfos, BookSnapshotKey, BookSnapshotSource, BookSnapshotter,
71 BookSubscription, BookSubscriptionOwner, BookUpdater,
72};
73pub(crate) use commands::{DeferredCommand, DeferredCommandQueue};
74use config::DataEngineConfig;
75use dispatch::{log_error_on_cache_insert, process_engine_bar};
76use futures::{ContinuousFutureRoller, ContinuousFutureSubscriptionState, datetime_to_unix_nanos};
77use handlers::{
78 BAR_AGGREGATOR_PRIORITY, BarBarHandler, BarQuoteHandler, BarTradeHandler, SpreadQuoteHandler,
79};
80use indexmap::IndexMap;
81#[cfg(feature = "defi")]
82use nautilus_common::messages::defi::PoolSnapshotResponse;
83use nautilus_common::{
84 cache::Cache,
85 clock::Clock,
86 logging::{RECV, RES},
87 messages::data::{
88 BarsResponse, BookDeltasResponse, BookDepthResponse, CustomDataResponse, DataCommand,
89 DataResponse, FundingRatesResponse, OptionChainReferencePriceResponse, PARAMS_IS_PARENT,
90 QuotesResponse, RequestBars, RequestCommand, RequestJoin, RequestOptionChainReferencePrice,
91 RequestQuotes, RequestTrades, SubscribeBars, SubscribeBookDeltas, SubscribeBookDepth,
92 SubscribeCommand, SubscribeOptionChain, SubscribeOptionGreeks, SubscribeQuotes,
93 SubscribeTrades, TradesResponse, UnsubscribeBars, UnsubscribeBookDeltas,
94 UnsubscribeBookDepth, UnsubscribeBookSnapshots, UnsubscribeCommand,
95 UnsubscribeInstrumentStatus, UnsubscribeOptionChain, UnsubscribeOptionGreeks,
96 UnsubscribeQuotes, UnsubscribeTrades, is_parent_subscription,
97 },
98 msgbus::{
99 self, BusPayloadType, ShareableMessageHandler, TypedHandler, TypedIntoHandler,
100 switchboard::{self, MessagingSwitchboard},
101 },
102 runner::get_data_cmd_sender,
103 timer::{TimeEvent, TimeEventCallback},
104};
105use nautilus_core::{
106 DurationNanos, Params, UUID4, UnixNanos, WeakCell,
107 correctness::{FAILED, check_key_in_map, check_key_not_in_map, check_predicate_true},
108 datetime::NANOSECONDS_IN_DAY,
109};
110#[cfg(feature = "defi")]
111use nautilus_model::defi::DefiData;
112use nautilus_model::{
113 data::{
114 Bar, BarType, CustomData, Data, DataRef, DataType, FundingRateUpdate, HasTsInit,
115 IndexPriceUpdate, InstrumentClose, InstrumentStatus, MarkPriceUpdate, OrderBookDelta,
116 OrderBookDeltas, OrderBookDepth, QuoteTick, TradeTick,
117 option_chain::{OptionGreeks, StrikeRange},
118 },
119 enums::{
120 AggregationSource, BarAggregation, BookType, InstrumentClass, MarketStatusAction,
121 PriceType, RecordFlag,
122 },
123 identifiers::{
124 ClientId, GENERIC_SPREAD_ID_SEPARATOR, InstrumentId, OptionSeriesId, Venue,
125 parse_generic_spread_id_legs,
126 },
127 instruments::{Instrument, InstrumentAny, SyntheticInstrument},
128 orderbook::OrderBook,
129 types::{Price, Quantity},
130};
131use option_chain::{
132 OptionChainBootstrapper, OptionChainGreeksBootstrap, PendingOptionChainRequest,
133};
134use requests::{
135 ContinuousFutureRequest, ContinuousFutureRequestState, ContinuousFutureSegment,
136 ContinuousFutureSource, RequestBarAggregation, continuous_future_parent_request_id,
137 continuous_future_request_from_bars, continuous_future_subscription_from_bars,
138 has_continuous_future_params, request_bar_aggregation_from_params, request_params,
139 response_params,
140};
141use responses::{
142 empty_response_like, log_if_empty_response, parent_request_window, rebind_response_correlation,
143 rebuild_pipeline_response,
144};
145use spread_quote::SpreadQuoteState;
146#[cfg(feature = "streaming")]
147use streaming::CatalogMap;
148use time_range::{
149 TimeRangePipelineState, has_time_range_pipeline_params, is_time_range_pipeline_variant,
150};
151use ustr::Ustr;
152
153#[cfg(feature = "defi")]
154#[allow(unused_imports)] use crate::defi::engine as _;
156#[cfg(feature = "defi")]
157use crate::engine::pool::PoolUpdater;
158use crate::{
159 aggregation::{
160 BarAggregator, RenkoBarAggregator, SpreadQuoteAggregator, TickBarAggregator,
161 TickImbalanceBarAggregator, TickRunsBarAggregator, TimeBarAggregator, ValueBarAggregator,
162 ValueImbalanceBarAggregator, ValueRunsBarAggregator, VolumeBarAggregator,
163 VolumeImbalanceBarAggregator, VolumeRunsBarAggregator,
164 },
165 client::DataClientAdapter,
166 option_chains::OptionChainManager,
167 subscription::{SubscriptionKey, SubscriptionRegistry, SubscriptionRelease},
168};
169
170#[derive(Debug)]
172pub struct DataEngine {
173 clock: Rc<RefCell<dyn Clock>>,
174 cache: Rc<RefCell<Cache>>,
175 config: DataEngineConfig,
176 msgbus_priority: u32,
177 external_clients: AHashSet<ClientId>,
178 subscriptions_external: SubscriptionRegistry<(ClientId, SubscriptionKey), SubscribeCommand>,
179 clients: IndexMap<ClientId, DataClientAdapter>,
180 default_client_id: Option<ClientId>,
181 routing_map: IndexMap<Venue, ClientId>,
182 book_intervals: AHashMap<NonZeroUsize, BookSnapshotInfos>,
183 book_snapshot_counts: IndexMap<BookSnapshotKey, usize>,
184 book_snapshot_sources: AHashMap<InstrumentId, BookSnapshotSource>,
185 book_deltas_counts: IndexMap<BookDeltasKey, usize>,
186 book_depth_counts: IndexMap<BookDeltasKey, usize>,
187 book_updaters: AHashMap<InstrumentId, Rc<BookUpdater>>,
188 book_subscriptions: AHashMap<InstrumentId, BookSubscription>,
189 book_subscription_owners: AHashMap<InstrumentId, Vec<Rc<BookSubscriptionOwner>>>,
190 book_snapshotters: AHashMap<NonZeroUsize, Rc<BookSnapshotter>>,
191 bar_aggregators: IndexMap<BarAggregatorKey, Rc<RefCell<Box<dyn BarAggregator>>>>,
192 bar_aggregator_handlers: AHashMap<BarAggregatorKey, Vec<BarAggregatorSubscription>>,
193 subscriptions_bar_aggregation: AHashMap<BarType, BarAggregationSubscription>,
194 request_bar_aggregations: AHashMap<UUID4, RequestBarAggregation>,
195 request_pipeline_parent_request: AHashMap<UUID4, RequestCommand>,
196 request_pipeline_n_components: AHashMap<UUID4, usize>,
197 request_pipeline_parent_request_id: AHashMap<UUID4, UUID4>,
198 request_pipeline_responses: AHashMap<UUID4, Vec<DataResponse>>,
199 time_range_pipeline_requests: AHashMap<UUID4, TimeRangePipelineState>,
200 time_range_pipeline_parent_request_id: AHashMap<UUID4, UUID4>,
201 parent_join_request_id: AHashMap<UUID4, UUID4>,
202 pending_join_requests: AHashMap<UUID4, RequestJoin>,
203 continuous_future_requests: AHashMap<UUID4, ContinuousFutureRequestState>,
204 continuous_future_subscriptions: AHashMap<BarType, ContinuousFutureSubscriptionState>,
205 continuous_future_roller: Option<Rc<ContinuousFutureRoller>>,
206 spread_quote_states: AHashMap<InstrumentId, SpreadQuoteState>,
207 option_chain_managers: AHashMap<OptionSeriesId, Rc<RefCell<OptionChainManager>>>,
208 option_chain_instrument_index: AHashMap<InstrumentId, OptionSeriesId>,
209 deferred_cmd_queue: DeferredCommandQueue,
210 option_chain_bootstrapper: Option<Rc<OptionChainBootstrapper>>,
211 pending_option_chain_requests: AHashMap<UUID4, PendingOptionChainRequest>,
212 option_chain_greeks_bootstraps: AHashMap<OptionSeriesId, OptionChainGreeksBootstrap>,
213 synthetic_quote_feeds: AHashMap<InstrumentId, Vec<SyntheticInstrument>>,
214 synthetic_trade_feeds: AHashMap<InstrumentId, Vec<SyntheticInstrument>>,
215 subscribed_synthetic_quotes: AHashMap<InstrumentId, usize>,
216 subscribed_synthetic_trades: AHashMap<InstrumentId, usize>,
217 buffered_deltas_map: AHashMap<InstrumentId, OrderBookDeltas>,
218 deltas_frame: Vec<OrderBookDelta>,
219 command_count: u64,
220 data_count: u64,
221 request_count: u64,
222 response_count: u64,
223 #[cfg(feature = "streaming")]
224 catalogs: CatalogMap,
225 #[cfg(feature = "defi")]
226 pub(crate) pool_updaters: AHashMap<InstrumentId, Rc<PoolUpdater>>,
227 #[cfg(feature = "defi")]
228 pub(crate) pool_snapshot_pending: AHashMap<InstrumentId, UUID4>,
229 #[cfg(feature = "defi")]
230 pub(crate) pool_event_buffers: AHashMap<InstrumentId, Vec<DefiData>>,
231}
232
233impl DataEngine {
234 #[must_use]
236 pub fn new(
237 clock: Rc<RefCell<dyn Clock>>,
238 cache: Rc<RefCell<Cache>>,
239 config: Option<DataEngineConfig>,
240 ) -> Self {
241 let config = config.unwrap_or_default();
242 let external_clients: AHashSet<ClientId> = config
243 .external_clients
244 .clone()
245 .unwrap_or_default()
246 .into_iter()
247 .collect();
248
249 Self {
250 clock,
251 cache,
252 config,
253 msgbus_priority: 10, external_clients,
255 subscriptions_external: SubscriptionRegistry::default(),
256 clients: IndexMap::new(),
257 default_client_id: None,
258 routing_map: IndexMap::new(),
259 book_intervals: AHashMap::new(),
260 book_snapshot_counts: IndexMap::new(),
261 book_snapshot_sources: AHashMap::new(),
262 book_deltas_counts: IndexMap::new(),
263 book_depth_counts: IndexMap::new(),
264 book_updaters: AHashMap::new(),
265 book_subscriptions: AHashMap::new(),
266 book_subscription_owners: AHashMap::new(),
267 book_snapshotters: AHashMap::new(),
268 bar_aggregators: IndexMap::new(),
269 bar_aggregator_handlers: AHashMap::new(),
270 subscriptions_bar_aggregation: AHashMap::new(),
271 request_bar_aggregations: AHashMap::new(),
272 request_pipeline_parent_request: AHashMap::new(),
273 request_pipeline_n_components: AHashMap::new(),
274 request_pipeline_parent_request_id: AHashMap::new(),
275 request_pipeline_responses: AHashMap::new(),
276 time_range_pipeline_requests: AHashMap::new(),
277 time_range_pipeline_parent_request_id: AHashMap::new(),
278 parent_join_request_id: AHashMap::new(),
279 pending_join_requests: AHashMap::new(),
280 continuous_future_requests: AHashMap::new(),
281 continuous_future_subscriptions: AHashMap::new(),
282 continuous_future_roller: None,
283 spread_quote_states: AHashMap::new(),
284 option_chain_managers: AHashMap::new(),
285 option_chain_instrument_index: AHashMap::new(),
286 deferred_cmd_queue: Rc::new(RefCell::new(VecDeque::new())),
287 option_chain_bootstrapper: None,
288 pending_option_chain_requests: AHashMap::new(),
289 option_chain_greeks_bootstraps: AHashMap::new(),
290 synthetic_quote_feeds: AHashMap::new(),
291 synthetic_trade_feeds: AHashMap::new(),
292 subscribed_synthetic_quotes: AHashMap::new(),
293 subscribed_synthetic_trades: AHashMap::new(),
294 buffered_deltas_map: AHashMap::new(),
295 deltas_frame: Vec::new(),
296 command_count: 0,
297 data_count: 0,
298 request_count: 0,
299 response_count: 0,
300 #[cfg(feature = "streaming")]
301 catalogs: CatalogMap::new(),
302 #[cfg(feature = "defi")]
303 pool_updaters: AHashMap::new(),
304 #[cfg(feature = "defi")]
305 pool_snapshot_pending: AHashMap::new(),
306 #[cfg(feature = "defi")]
307 pool_event_buffers: AHashMap::new(),
308 }
309 }
310
311 #[must_use]
313 pub fn clock(&self) -> &Rc<RefCell<dyn Clock>> {
314 &self.clock
315 }
316
317 #[must_use]
319 pub fn cache(&self) -> &Rc<RefCell<Cache>> {
320 &self.cache
321 }
322
323 #[must_use]
325 pub const fn config(&self) -> &DataEngineConfig {
326 &self.config
327 }
328
329 #[cfg(feature = "defi")]
330 #[must_use]
331 pub(crate) const fn msgbus_priority(&self) -> u32 {
332 self.msgbus_priority
333 }
334
335 pub fn register_msgbus_handlers(engine: &Rc<RefCell<Self>>) {
337 let weak = WeakCell::from(Rc::downgrade(engine));
338 engine.borrow_mut().continuous_future_roller =
339 Some(Rc::new(ContinuousFutureRoller::new(engine)));
340 engine.borrow_mut().option_chain_bootstrapper =
341 Some(Rc::new(OptionChainBootstrapper::new(engine)));
342
343 let weak1 = weak.clone();
344 msgbus::register_data_command_endpoint(
345 MessagingSwitchboard::data_engine_execute(),
346 TypedIntoHandler::from(move |cmd: DataCommand| {
347 if let Some(rc) = weak1.upgrade() {
348 rc.borrow_mut().execute(cmd);
349 }
350 }),
351 );
352
353 msgbus::register_data_command_endpoint(
354 MessagingSwitchboard::data_engine_queue_execute(),
355 TypedIntoHandler::from(move |cmd: DataCommand| {
356 get_data_cmd_sender().execute(cmd);
357 }),
358 );
359
360 let weak2 = weak.clone();
362 msgbus::register_any(
363 MessagingSwitchboard::data_engine_process(),
364 ShareableMessageHandler::from_any(move |data: &dyn Any| {
365 if let Some(rc) = weak2.upgrade() {
366 rc.borrow_mut().process(data);
367 }
368 }),
369 );
370
371 let weak3 = weak.clone();
373 msgbus::register_data_endpoint(
374 MessagingSwitchboard::data_engine_process_data(),
375 TypedIntoHandler::from(move |data: Data| {
376 if let Some(rc) = weak3.upgrade() {
377 rc.borrow_mut().process_data(data);
378 }
379 }),
380 );
381
382 #[cfg(feature = "defi")]
384 {
385 let weak4 = weak.clone();
386 msgbus::register_defi_data_endpoint(
387 MessagingSwitchboard::data_engine_process_defi_data(),
388 TypedIntoHandler::from(move |data: DefiData| {
389 if let Some(rc) = weak4.upgrade() {
390 rc.borrow_mut().process_defi_data(data);
391 }
392 }),
393 );
394 }
395
396 let weak5 = weak;
397 msgbus::register_data_response_endpoint(
398 MessagingSwitchboard::data_engine_response(),
399 TypedIntoHandler::from(move |resp: DataResponse| {
400 if let Some(rc) = weak5.upgrade() {
401 rc.borrow_mut().response(resp);
402 }
403 }),
404 );
405 }
406
407 #[must_use]
409 pub const fn command_count(&self) -> u64 {
410 self.command_count
411 }
412
413 #[must_use]
415 pub const fn data_count(&self) -> u64 {
416 self.data_count
417 }
418
419 #[cfg(feature = "defi")]
420 pub(crate) const fn increment_data_count(&mut self) {
421 self.data_count += 1;
422 }
423
424 #[must_use]
426 pub const fn request_count(&self) -> u64 {
427 self.request_count
428 }
429
430 #[must_use]
432 pub const fn response_count(&self) -> u64 {
433 self.response_count
434 }
435
436 #[must_use]
438 pub fn request_pipeline_count(&self) -> usize {
439 self.request_pipeline_parent_request.len()
440 }
441
442 #[must_use]
444 pub fn time_range_pipeline_count(&self) -> usize {
445 self.time_range_pipeline_requests.len()
446 }
447
448 #[must_use]
450 pub fn pending_join_request_count(&self) -> usize {
451 self.pending_join_requests.len()
452 }
453
454 pub fn start(&mut self) {
456 for client in self.get_clients_mut() {
457 if client.start().is_err() {
458 log::error!("Failed to start data client {}", client.client_id());
459 }
460 }
461
462 for ((_, request_id), aggregator) in &self.bar_aggregators {
463 let is_subscription = request_id.is_none() && !aggregator.borrow().is_historical();
466 if is_subscription && aggregator.borrow().bar_type().spec().is_time_aggregated() {
467 aggregator
468 .borrow_mut()
469 .start_timer(Some(Rc::clone(aggregator)));
470 }
471 }
472
473 for state in self.spread_quote_states.values() {
474 state
475 .aggregator
476 .borrow_mut()
477 .start_timer(Some(Rc::clone(&state.aggregator)));
478 }
479 }
480
481 pub fn stop(&mut self) {
483 for client in self.get_clients_mut() {
484 if client.stop().is_err() {
485 log::error!("Failed to stop data client {}", client.client_id());
486 }
487 }
488
489 for aggregator in self.bar_aggregators.values() {
490 aggregator.borrow_mut().stop();
491 }
492
493 for state in self.spread_quote_states.values() {
494 state.aggregator.borrow_mut().stop_timer();
495 }
496 }
497
498 pub fn reset(&mut self) {
500 for client in self.get_clients_mut() {
501 match client.reset() {
502 Ok(()) => client.clear_subscription_state(),
503 Err(_) => log::error!("Failed to reset data client {}", client.client_id()),
504 }
505 }
506
507 let keys: Vec<BarAggregatorKey> = self.bar_aggregators.keys().copied().collect();
508 for (bar_type, request_id) in keys {
509 if let Err(e) = self.stop_bar_aggregator(bar_type, request_id) {
510 log::error!("Error stopping bar aggregator during reset for {bar_type}: {e}");
511 }
512 }
513 self.subscriptions_bar_aggregation.clear();
514
515 self.request_bar_aggregations.clear();
516 self.request_pipeline_parent_request.clear();
517 self.request_pipeline_n_components.clear();
518 self.request_pipeline_parent_request_id.clear();
519 self.request_pipeline_responses.clear();
520 self.time_range_pipeline_requests.clear();
521 self.time_range_pipeline_parent_request_id.clear();
522 self.parent_join_request_id.clear();
523 self.pending_join_requests.clear();
524 self.continuous_future_requests.clear();
525
526 for state in self.continuous_future_subscriptions.values_mut() {
527 if let Some(name) = state.timer_name.take() {
528 self.clock.borrow_mut().cancel_timer(&name);
529 }
530 }
531 self.continuous_future_subscriptions.clear();
532
533 let spread_ids: Vec<InstrumentId> = self.spread_quote_states.keys().copied().collect();
534 for spread_id in spread_ids {
535 self.stop_spread_quote_aggregation(spread_id);
536 }
537
538 let managers: Vec<_> = self.option_chain_managers.drain().collect();
540 for (_, manager) in managers {
541 manager.borrow_mut().teardown(&self.clock);
542 }
543
544 self.option_chain_instrument_index.clear();
545 self.cancel_pending_option_chain_requests(None);
546 self.clear_option_chain_greeks_bootstraps();
547
548 let book_updaters: Vec<(InstrumentId, Rc<BookUpdater>)> =
553 self.book_updaters.drain().collect();
554 for (instrument_id, updater) in book_updaters {
555 let deltas_topic = switchboard::get_book_deltas_topic(instrument_id);
556 let depth_topic = switchboard::get_book_depth_topic(instrument_id);
557 let deltas_handler: TypedHandler<OrderBookDeltas> =
558 TypedHandler::new(Rc::clone(&updater));
559 let depth_handler: TypedHandler<OrderBookDepth> = TypedHandler::new(updater);
560 msgbus::unsubscribe_book_deltas(deltas_topic.into(), &deltas_handler);
561 msgbus::unsubscribe_book_depth(depth_topic.into(), &depth_handler);
562 }
563
564 self.book_subscriptions.clear();
565 self.book_subscription_owners.clear();
566
567 self.book_deltas_counts.clear();
568 self.book_depth_counts.clear();
569 self.book_intervals.clear();
570 self.book_snapshot_counts.clear();
571 self.book_snapshot_sources.clear();
572 self.book_snapshotters.clear();
573 self.buffered_deltas_map.clear();
574 self.deltas_frame.clear();
575
576 self.synthetic_quote_feeds.clear();
577 self.synthetic_trade_feeds.clear();
578 self.subscribed_synthetic_quotes.clear();
579 self.subscribed_synthetic_trades.clear();
580 self.subscriptions_external.clear();
581
582 self.deferred_cmd_queue.borrow_mut().clear();
583
584 #[cfg(feature = "defi")]
585 self.clear_pool_updaters();
586
587 self.clock.borrow_mut().cancel_timers();
588
589 self.command_count = 0;
590 self.data_count = 0;
591 self.request_count = 0;
592 self.response_count = 0;
593 }
594
595 pub fn dispose(&mut self) {
597 for client in self.get_clients_mut() {
598 if client.dispose().is_err() {
599 log::error!("Failed to dispose data client {}", client.client_id());
600 }
601 }
602
603 self.clear_option_chain_greeks_bootstraps();
604
605 let mut cf_sources = Vec::new();
608
609 for state in self.continuous_future_subscriptions.values_mut() {
610 if let Some(name) = state.timer_name.take() {
611 self.clock.borrow_mut().cancel_timer(&name);
612 }
613
614 if let Some(subscription) = state.active_source_subscription.take() {
615 cf_sources.push((state.target_bar_type, subscription));
616 }
617 }
618
619 for (target_bar_type, subscription) in cf_sources {
620 self.unsubscribe_continuous_future_source(target_bar_type, subscription);
621 }
622 self.continuous_future_subscriptions.clear();
623
624 let keys: Vec<BarAggregatorKey> = self.bar_aggregators.keys().copied().collect();
627 for (bar_type, request_id) in keys {
628 if let Err(e) = self.stop_bar_aggregator(bar_type, request_id) {
629 log::error!("Error stopping bar aggregator during dispose for {bar_type}: {e}");
630 }
631 }
632
633 self.subscriptions_external.clear();
634
635 self.clock.borrow_mut().cancel_timers();
636 }
637
638 pub async fn connect(&mut self) {
643 let futures: Vec<_> = self
644 .get_clients_mut()
645 .into_iter()
646 .map(|client| async move {
647 let client_id = client.client_id();
648 (client_id, client.connect().await)
649 })
650 .collect();
651
652 let results = join_all(futures).await;
653
654 for (client_id, result) in results {
655 if result.is_err() {
656 log::error!("Failed to connect data client {client_id}: connection failed");
657 }
658 }
659 }
660
661 pub async fn disconnect(&mut self) -> anyhow::Result<()> {
667 let futures: Vec<_> = self
668 .get_clients_mut()
669 .into_iter()
670 .map(|client| async move {
671 let client_id = client.client_id();
672 (client_id, client.disconnect().await)
673 })
674 .collect();
675
676 let results = join_all(futures).await;
677
678 #[cfg(feature = "defi")]
680 self.abandon_pool_snapshots();
681
682 let failed_clients: Vec<_> = results
683 .into_iter()
684 .filter_map(|(client_id, result)| result.err().map(|_| client_id.to_string()))
685 .collect();
686
687 if failed_clients.is_empty() {
688 Ok(())
689 } else {
690 anyhow::bail!(
691 "Failed to disconnect data clients: {}",
692 failed_clients.join(", ")
693 )
694 }
695 }
696
697 #[must_use]
699 pub fn check_connected(&self) -> bool {
700 self.get_clients()
701 .iter()
702 .all(|client| client.is_connected())
703 }
704
705 #[must_use]
707 pub fn check_disconnected(&self) -> bool {
708 self.get_clients()
709 .iter()
710 .all(|client| !client.is_connected())
711 }
712
713 #[must_use]
715 pub fn subscribed_custom_data(&self) -> Vec<DataType> {
716 self.collect_subscriptions(|client| &client.subscriptions_custom)
717 }
718
719 #[must_use]
721 pub fn subscribed_instruments(&self) -> Vec<InstrumentId> {
722 self.collect_subscriptions(|client| &client.subscriptions_instrument)
723 }
724
725 #[must_use]
727 pub fn subscribed_quotes(&self) -> Vec<InstrumentId> {
728 self.collect_subscriptions(|client| &client.subscriptions_quotes)
729 }
730
731 #[must_use]
733 pub fn subscribed_trades(&self) -> Vec<InstrumentId> {
734 self.collect_subscriptions(|client| &client.subscriptions_trades)
735 }
736
737 #[must_use]
740 pub fn subscribed_bars(&self) -> Vec<BarType> {
741 let mut subscribed = self.collect_subscriptions(|client| &client.subscriptions_bars);
742 subscribed.extend(
743 self.bar_aggregators
744 .keys()
745 .filter(|(_, request_id)| request_id.is_none())
746 .map(|(bar_type, _)| *bar_type),
747 );
748 subscribed
749 }
750
751 #[must_use]
753 pub fn subscribed_mark_prices(&self) -> Vec<InstrumentId> {
754 self.collect_subscriptions(|client| &client.subscriptions_mark_prices)
755 }
756
757 #[must_use]
759 pub fn subscribed_index_prices(&self) -> Vec<InstrumentId> {
760 self.collect_subscriptions(|client| &client.subscriptions_index_prices)
761 }
762
763 #[must_use]
765 pub fn subscribed_funding_rates(&self) -> Vec<InstrumentId> {
766 self.collect_subscriptions(|client| &client.subscriptions_funding_rates)
767 }
768
769 #[must_use]
771 pub fn subscribed_instrument_status(&self) -> Vec<InstrumentId> {
772 self.collect_subscriptions(|client| &client.subscriptions_instrument_status)
773 }
774
775 #[must_use]
777 pub fn subscribed_instrument_close(&self) -> Vec<InstrumentId> {
778 self.collect_subscriptions(|client| &client.subscriptions_instrument_close)
779 }
780
781 pub fn execute(&mut self, cmd: DataCommand) {
790 match &cmd {
791 DataCommand::Subscribe(_) | DataCommand::Unsubscribe(_) => self.command_count += 1,
792 DataCommand::Request(_) => self.request_count += 1,
793 #[cfg(feature = "defi")]
794 DataCommand::DefiRequest(_) => self.request_count += 1,
795 #[cfg(feature = "defi")]
796 DataCommand::DefiSubscribe(_) | DataCommand::DefiUnsubscribe(_) => {
797 self.command_count += 1;
798 }
799 _ => {}
800 }
801
802 let command_context = match &cmd {
803 DataCommand::Subscribe(c) => (
804 "Subscribe",
805 c.client_id().copied(),
806 c.venue().copied(),
807 Some(c.command_id()),
808 ),
809 DataCommand::Unsubscribe(c) => (
810 "Unsubscribe",
811 c.client_id().copied(),
812 c.venue().copied(),
813 Some(c.command_id()),
814 ),
815 DataCommand::Request(c) => (
816 "Request",
817 c.client_id().copied(),
818 c.venue().copied(),
819 Some(*c.request_id()),
820 ),
821 #[cfg(feature = "defi")]
822 DataCommand::DefiRequest(c) => (
823 "DefiRequest",
824 c.client_id().copied(),
825 c.venue().copied(),
826 Some(*c.request_id()),
827 ),
828 #[cfg(feature = "defi")]
829 DataCommand::DefiSubscribe(c) => (
830 "DefiSubscribe",
831 c.client_id().copied(),
832 c.venue().copied(),
833 Some(c.command_id()),
834 ),
835 #[cfg(feature = "defi")]
836 DataCommand::DefiUnsubscribe(c) => (
837 "DefiUnsubscribe",
838 c.client_id().copied(),
839 c.venue().copied(),
840 Some(c.command_id()),
841 ),
842 _ => ("Unknown", None, None, None),
843 };
844
845 let result = match cmd {
846 DataCommand::Subscribe(c) => self.execute_subscribe(c),
847 DataCommand::Unsubscribe(c) => self.execute_unsubscribe(&c),
848 DataCommand::Request(c) => self.execute_request(c),
849 #[cfg(feature = "defi")]
850 DataCommand::DefiRequest(c) => self.execute_defi_request(c),
851 #[cfg(feature = "defi")]
852 DataCommand::DefiSubscribe(c) => self.execute_defi_subscribe(c),
853 #[cfg(feature = "defi")]
854 DataCommand::DefiUnsubscribe(c) => self.execute_defi_unsubscribe(&c),
855 _ => {
856 log::warn!("Unhandled DataCommand variant");
857 Ok(())
858 }
859 };
860
861 if let Err(e) = result {
862 log::error!("Data command failed: {command_context:?}: {e}");
863 }
864 }
865
866 pub fn execute_subscribe(&mut self, cmd: SubscribeCommand) -> anyhow::Result<()> {
873 if let Some(client_id) = cmd.client_id()
874 && self.external_clients.contains(client_id)
875 {
876 if let SubscribeCommand::OptionChain(command) = &cmd {
877 self.retain_external_option_chain(*client_id, command, &cmd);
878 } else if !self.subscriptions_external.retain(
879 (*client_id, SubscriptionKey::from_subscribe(&cmd)),
880 cmd.command_id(),
881 cmd.clone(),
882 ) {
883 return Ok(());
884 }
885
886 register_external_streaming_type(&cmd);
887 publish_external_data_command(*client_id, &cmd);
888
889 if self.config.debug {
890 log::debug!("Skipping subscribe command for external client {client_id}: {cmd:?}");
891 }
892
893 return Ok(());
894 }
895
896 match &cmd {
898 SubscribeCommand::BookDeltas(book_cmd) => {
899 if !self.subscribe_book_deltas(book_cmd)? && self.client_subscription_active(&cmd) {
900 return Ok(());
901 }
902 }
903 SubscribeCommand::BookDepth(book_cmd) => {
904 if !self.subscribe_book_depth(book_cmd)? && self.client_subscription_active(&cmd) {
905 return Ok(());
906 }
907 }
908 SubscribeCommand::BookSnapshots(cmd) => {
909 return self.subscribe_book_snapshots(cmd);
911 }
912 SubscribeCommand::Bars(cmd) if has_continuous_future_params(cmd.params.as_ref()) => {
913 return self.subscribe_continuous_future_bars(cmd);
914 }
915 SubscribeCommand::Bars(cmd) => {
916 self.subscribe_bars(cmd)?;
917 if cmd.bar_type.is_internally_aggregated() {
918 return Ok(());
919 }
920 }
921 SubscribeCommand::OptionChain(cmd) if cmd.snapshot_interval_ms == Some(0) => {
922 anyhow::bail!(
923 "Cannot subscribe option chain {} with a zero `snapshot_interval_ms`; use `None` for raw mode",
924 cmd.series_id,
925 );
926 }
927 SubscribeCommand::OptionChain(cmd) => {
928 self.subscribe_option_chain(cmd);
929 return Ok(());
930 }
931 SubscribeCommand::Quotes(cmd) if cmd.instrument_id.is_synthetic() => {
932 self.subscribe_synthetic_quotes(cmd.instrument_id);
933 return Ok(());
934 }
935 SubscribeCommand::Quotes(cmd)
936 if self.is_spread_quote_command(cmd.instrument_id, cmd.params.as_ref()) =>
937 {
938 self.subscribe_spread_quotes(cmd);
939 return Ok(());
940 }
941 SubscribeCommand::Trades(cmd) if cmd.instrument_id.is_synthetic() => {
942 self.subscribe_synthetic_trades(cmd.instrument_id);
943 return Ok(());
944 }
945 SubscribeCommand::Instrument(cmd) if cmd.instrument_id.is_synthetic() => {
946 anyhow::bail!("Cannot subscribe for synthetic instrument `Instrument` data");
947 }
948 SubscribeCommand::InstrumentStatus(cmd) if cmd.instrument_id.is_synthetic() => {
949 anyhow::bail!("Cannot subscribe for synthetic instrument `InstrumentStatus` data");
950 }
951 SubscribeCommand::InstrumentClose(cmd) if cmd.instrument_id.is_synthetic() => {
952 anyhow::bail!("Cannot subscribe for synthetic instrument `InstrumentClose` data");
953 }
954 SubscribeCommand::OptionGreeks(cmd) if cmd.instrument_id.is_synthetic() => {
955 anyhow::bail!("Cannot subscribe for synthetic instrument `OptionGreeks` data");
956 }
957 _ => {} }
959
960 let retained = cmd.clone();
961
962 let retain_on_failure = !matches!(
964 &cmd,
965 SubscribeCommand::BookDeltas(_) | SubscribeCommand::BookDepth(_)
966 );
967
968 #[cfg(feature = "streaming")]
969 let cmd = self.subscribe_command_with_prefilled_start_ns(cmd)?;
970
971 if let Some(client) = self.get_command_client(cmd.client_id(), cmd.venue()) {
972 client.execute_subscribe_with_retained(cmd, retained, retain_on_failure);
973 } else {
974 log::error!(
975 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
976 cmd.client_id(),
977 cmd.venue(),
978 );
979 }
980
981 Ok(())
982 }
983
984 fn client_subscription_active(&mut self, cmd: &SubscribeCommand) -> bool {
985 self.get_command_client(cmd.client_id(), cmd.venue())
986 .is_some_and(|client| client.has_active_subscription(cmd))
987 }
988
989 pub fn execute_unsubscribe(&mut self, cmd: &UnsubscribeCommand) -> anyhow::Result<()> {
995 if let Some(client_id) = cmd.client_id()
996 && self.external_clients.contains(client_id)
997 {
998 let key = (*client_id, SubscriptionKey::from_unsubscribe(cmd));
999 let command = match self.subscriptions_external.release(&key) {
1000 SubscriptionRelease::Retained => return Ok(()),
1001 SubscriptionRelease::Final(subscribe) => subscribe.into_unsubscribe(
1002 cmd.command_id(),
1003 cmd.ts_init(),
1004 cmd.correlation_id(),
1005 ),
1006 SubscriptionRelease::Untracked => cmd.clone(),
1007 };
1008 self.subscriptions_external.remove(&key);
1009 publish_external_data_command(*client_id, &command);
1010
1011 if self.config.debug {
1012 log::debug!(
1013 "Skipping unsubscribe command for external client {client_id}: {command:?}",
1014 );
1015 }
1016 return Ok(());
1017 }
1018
1019 if matches!(
1020 cmd,
1021 UnsubscribeCommand::BookDeltas(_)
1022 | UnsubscribeCommand::BookDepth(_)
1023 | UnsubscribeCommand::BookSnapshots(_)
1024 ) && !self.release_book_subscription(cmd)
1025 {
1026 return Ok(());
1027 }
1028
1029 match &cmd {
1030 UnsubscribeCommand::BookDeltas(cmd) if !self.unsubscribe_book_deltas(cmd) => {
1031 return Ok(());
1032 }
1033 UnsubscribeCommand::BookDepth(cmd) if !self.unsubscribe_book_depth(cmd) => {
1034 return Ok(());
1035 }
1036 UnsubscribeCommand::BookSnapshots(cmd) => {
1037 self.unsubscribe_book_snapshots(cmd);
1039 return Ok(());
1040 }
1041 UnsubscribeCommand::Bars(cmd)
1042 if self
1043 .continuous_future_subscriptions
1044 .contains_key(&cmd.bar_type.standard()) =>
1045 {
1046 let topic = switchboard::get_bars_topic(cmd.bar_type.standard());
1048 if msgbus::exact_subscriber_count_bars(topic) == 0 {
1049 self.unsubscribe_continuous_future_bars(cmd);
1050 }
1051 return Ok(());
1052 }
1053 UnsubscribeCommand::Bars(cmd) => {
1054 self.unsubscribe_bars(cmd);
1055 if cmd.bar_type.is_internally_aggregated() {
1056 return Ok(());
1057 }
1058 }
1059 UnsubscribeCommand::OptionChain(cmd) => {
1060 self.unsubscribe_option_chain(cmd);
1061 return Ok(());
1062 }
1063 UnsubscribeCommand::Quotes(cmd) if cmd.instrument_id.is_synthetic() => {
1064 self.unsubscribe_synthetic_quotes(cmd.instrument_id);
1065 return Ok(());
1066 }
1067 UnsubscribeCommand::Quotes(cmd)
1068 if self.is_spread_quote_command(cmd.instrument_id, cmd.params.as_ref()) =>
1069 {
1070 self.unsubscribe_spread_quotes(cmd);
1071 return Ok(());
1072 }
1073 UnsubscribeCommand::Trades(cmd) if cmd.instrument_id.is_synthetic() => {
1074 self.unsubscribe_synthetic_trades(cmd.instrument_id);
1075 return Ok(());
1076 }
1077 UnsubscribeCommand::Instrument(cmd) if cmd.instrument_id.is_synthetic() => {
1078 anyhow::bail!("Cannot unsubscribe from synthetic instrument `Instrument` data");
1079 }
1080 UnsubscribeCommand::InstrumentStatus(cmd) if cmd.instrument_id.is_synthetic() => {
1081 anyhow::bail!(
1082 "Cannot unsubscribe from synthetic instrument `InstrumentStatus` data"
1083 );
1084 }
1085 UnsubscribeCommand::InstrumentClose(cmd) if cmd.instrument_id.is_synthetic() => {
1086 anyhow::bail!(
1087 "Cannot unsubscribe from synthetic instrument `InstrumentClose` data"
1088 );
1089 }
1090 UnsubscribeCommand::OptionGreeks(cmd) if cmd.instrument_id.is_synthetic() => {
1091 anyhow::bail!("Cannot unsubscribe from synthetic instrument `OptionGreeks` data");
1092 }
1093 _ => {}
1094 }
1095
1096 if let Some(client) = self.get_command_client(cmd.client_id(), cmd.venue()) {
1097 client.execute_unsubscribe(cmd);
1098 } else {
1099 log::error!(
1100 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
1101 cmd.client_id(),
1102 cmd.venue(),
1103 );
1104 }
1105
1106 Ok(())
1107 }
1108
1109 pub fn execute_request(&mut self, req: RequestCommand) -> anyhow::Result<()> {
1116 if let Some(cid) = req.client_id()
1118 && self.external_clients.contains(cid)
1119 {
1120 if self.config.debug {
1121 log::debug!("Skipping data request for external client {cid}: {req:?}");
1122 }
1123 return Ok(());
1124 }
1125
1126 if let RequestCommand::Join(join) = req {
1127 return self.handle_request_join(join);
1128 }
1129
1130 if has_continuous_future_params(request_params(&req)) {
1131 return self.execute_continuous_future_request(req);
1132 }
1133
1134 let request_id = *req.request_id();
1135 self.prepare_request_bar_aggregators(&req)?;
1136
1137 if has_time_range_pipeline_params(request_params(&req))
1138 && is_time_range_pipeline_variant(&req)
1139 {
1140 let result = self.execute_time_range_pipeline_request(req);
1141 if result.is_err() {
1142 self.cleanup_request_bar_aggregators(&request_id);
1143 }
1144 return result;
1145 }
1146
1147 #[cfg(feature = "streaming")]
1148 if self.catalogs_registered() && streaming::is_date_range_variant(&req) {
1149 let result = self.dispatch_date_range_request(req);
1150 if result.is_err() {
1151 self.cleanup_request_bar_aggregators(&request_id);
1152 }
1153 return result;
1154 }
1155
1156 let result = self.dispatch_request_to_client(req);
1157
1158 if result.is_err() {
1159 self.cleanup_request_bar_aggregators(&request_id);
1160 }
1161
1162 result.map(|_| ())
1163 }
1164
1165 pub(super) fn dispatch_request_to_client(
1166 &mut self,
1167 req: RequestCommand,
1168 ) -> anyhow::Result<ClientId> {
1169 let client_id = req.client_id().copied();
1170 let venue = req.venue().copied();
1171 let Some(client) = self.get_client(client_id.as_ref(), venue.as_ref()) else {
1172 anyhow::bail!("Cannot handle request: no client found for {client_id:?} {venue:?}");
1173 };
1174 let resolved_client_id = client.client_id();
1175
1176 #[rustfmt::skip]
1177 let result = match req {
1178 RequestCommand::Data(req) => client.request_data(req),
1179 RequestCommand::Instrument(req) => client.request_instrument(req),
1180 RequestCommand::Instruments(req) => client.request_instruments(req),
1181 RequestCommand::BookSnapshot(req) => client.request_book_snapshot(req),
1182 RequestCommand::BookDeltas(req) => client.request_book_deltas(req),
1183 RequestCommand::BookDepth(req) => client.request_book_depth(req),
1184 RequestCommand::Quotes(req) => client.request_quotes(req),
1185 RequestCommand::Trades(req) => client.request_trades(req),
1186 RequestCommand::FundingRates(req) => client.request_funding_rates(req),
1187 RequestCommand::OptionChainReferencePrice(req) => client.request_option_chain_reference_price(req),
1188 RequestCommand::Bars(req) => client.request_bars(req),
1189 RequestCommand::Join(_) => anyhow::bail!("RequestJoin must be handled by handle_request_join"),
1190 };
1191
1192 result
1193 .map_err(|e| e.context(format!("Data client {resolved_client_id} request failed")))?;
1194
1195 Ok(resolved_client_id)
1196 }
1197
1198 pub fn process(&mut self, data: &dyn Any) {
1203 self.data_count += 1;
1204 if let Some(instrument) = data.downcast_ref::<InstrumentAny>() {
1208 self.handle_instrument(instrument);
1209 } else if let Some(funding_rate) = data.downcast_ref::<FundingRateUpdate>() {
1210 self.handle_funding_rate(*funding_rate);
1211 } else if let Some(option_greeks) = data.downcast_ref::<OptionGreeks>() {
1212 self.cache.borrow_mut().add_option_greeks(*option_greeks);
1213 self.feed_option_greeks_to_pre_bootstrap_chain(option_greeks);
1214 let topic = switchboard::get_option_greeks_topic(option_greeks.instrument_id);
1215 msgbus::publish_option_greeks(topic, option_greeks);
1216 self.drain_deferred_commands();
1217 } else if let Some(status) = data.downcast_ref::<InstrumentStatus>() {
1218 self.handle_instrument_status(*status);
1219 } else if let Some(custom) = data.downcast_ref::<CustomData>() {
1220 self.handle_custom_data(custom);
1221 } else {
1222 #[cfg(feature = "defi")]
1223 if let Some(response) = data.downcast_ref::<PoolSnapshotResponse>() {
1224 self.handle_pool_snapshot_response(response);
1225 return;
1226 }
1227
1228 log::error!("Cannot process data {data:?}, type is unrecognized");
1229 }
1230 }
1231
1232 #[allow(
1234 clippy::needless_pass_by_value,
1235 reason = "callers hand over ownership; the payload is only moved when a DeFi handler consumes it"
1236 )]
1237 pub fn process_data(&mut self, data: Data) {
1238 #[cfg(feature = "defi")]
1239 let data = match data {
1240 Data::Defi(defi) => {
1241 self.process_defi_data(*defi);
1242 return;
1243 }
1244 data => data,
1245 };
1246
1247 self.process_data_ref(DataRef::from(&data));
1248 }
1249
1250 pub fn process_data_ref(&mut self, data: DataRef<'_>) {
1255 #[cfg(feature = "defi")]
1256 let data = match data {
1257 DataRef::Defi(defi) => {
1258 self.process_defi_data(defi.clone());
1259 return;
1260 }
1261 data => data,
1262 };
1263
1264 self.data_count += 1;
1265
1266 match data {
1267 DataRef::Instrument(instrument) => self.handle_instrument(instrument),
1268 DataRef::BookDelta(delta) => self.handle_delta(*delta),
1269 DataRef::BookDeltas(deltas) => self.handle_deltas(deltas),
1270 DataRef::BookDepth(depth) => self.handle_depth(depth),
1271 DataRef::Quote(quote) => {
1272 self.handle_quote(*quote);
1273 self.drain_deferred_commands();
1274 }
1275 DataRef::Trade(trade) => self.handle_trade(*trade),
1276 DataRef::Bar(bar) => self.handle_bar(*bar),
1277 DataRef::MarkPrice(mark_price) => {
1278 self.handle_mark_price(*mark_price);
1279 self.drain_deferred_commands();
1280 }
1281 DataRef::IndexPrice(index_price) => {
1282 self.handle_index_price(*index_price);
1283 self.drain_deferred_commands();
1284 }
1285 DataRef::FundingRate(funding_rate) => {
1286 self.handle_funding_rate(*funding_rate);
1287 self.drain_deferred_commands();
1288 }
1289 DataRef::OptionGreeks(greeks) => {
1290 self.cache.borrow_mut().add_option_greeks(*greeks);
1291 self.feed_option_greeks_to_pre_bootstrap_chain(greeks);
1292 let topic = switchboard::get_option_greeks_topic(greeks.instrument_id);
1293 msgbus::publish_option_greeks(topic, greeks);
1294 self.drain_deferred_commands();
1295 }
1296 DataRef::InstrumentStatus(status) => {
1297 self.handle_instrument_status(*status);
1298 self.drain_deferred_commands();
1299 }
1300 DataRef::InstrumentClose(close) => self.handle_instrument_close(*close),
1301 DataRef::Custom(custom) => self.handle_custom_data(custom),
1302 #[cfg(feature = "defi")]
1303 DataRef::Defi(_) => unreachable!("handled before market data dispatch"),
1304 #[cfg(not(feature = "defi"))]
1305 #[allow(
1306 unreachable_patterns,
1307 reason = "DeFi variants can exist without this crate's defi feature"
1308 )]
1309 other => log_defi_data_dropped(other),
1310 }
1311 }
1312
1313 pub fn process_pipeline(&mut self, data: Data) {
1320 #[cfg(feature = "defi")]
1321 let data = match data {
1322 Data::Defi(defi) => {
1323 self.process_defi_data(*defi);
1324 return;
1325 }
1326 data => data,
1327 };
1328
1329 self.data_count += 1;
1330
1331 match data {
1332 Data::Instrument(instrument) => self.handle_instrument(&instrument),
1333 Data::BookDelta(delta) => self.handle_delta_pipeline(delta),
1334 Data::BookDeltas(deltas) => self.handle_deltas_pipeline(&deltas),
1335 Data::BookDepth(depth) => self.handle_depth_pipeline(&depth),
1336 Data::Quote(quote) => self.handle_quote_pipeline(quote),
1337 Data::Trade(trade) => self.handle_trade_pipeline(trade),
1338 Data::Bar(bar) => self.handle_bar_pipeline(bar),
1339 Data::MarkPrice(mark_price) => self.handle_mark_price_pipeline(mark_price),
1340 Data::IndexPrice(index_price) => self.handle_index_price_pipeline(index_price),
1341 Data::FundingRate(funding_rate) => {
1342 self.handle_funding_rate_pipeline(funding_rate);
1343 }
1344 Data::OptionGreeks(greeks) => self.handle_option_greeks_pipeline(greeks),
1345 Data::InstrumentStatus(status) => self.handle_instrument_status_pipeline(status),
1346 Data::InstrumentClose(close) => self.handle_instrument_close_pipeline(close),
1347 Data::Custom(custom) => self.handle_custom_data_pipeline(&custom),
1348 #[cfg(feature = "defi")]
1349 Data::Defi(_) => unreachable!("handled before market data dispatch"),
1350 #[cfg(not(feature = "defi"))]
1351 #[allow(
1352 unreachable_patterns,
1353 reason = "DeFi variants can exist without this crate's defi feature"
1354 )]
1355 other => log_defi_data_dropped(DataRef::from(&other)),
1356 }
1357 }
1358
1359 pub fn response(&mut self, mut resp: DataResponse) {
1361 if log::log_enabled!(log::Level::Debug) {
1362 let correlation_id = resp.correlation_id();
1363 match resp.record_count() {
1364 Some(count) => log::debug!(
1365 "{RECV}{RES} {} correlation_id={correlation_id} records={count}",
1366 resp.kind(),
1367 ),
1368 None => log::debug!(
1369 "{RECV}{RES} {} correlation_id={correlation_id}",
1370 resp.kind(),
1371 ),
1372 }
1373 }
1374 log::trace!("{RECV}{RES} {resp:?}");
1375
1376 self.response_count += 1;
1377
1378 resp.trim_to_bounds();
1379
1380 let Some(resp) = self.handle_request_pipeline_response(resp) else {
1381 return;
1382 };
1383
1384 if let Some(parent_id) = continuous_future_parent_request_id(response_params(&resp)) {
1386 self.handle_continuous_future_child_response(parent_id, &resp);
1387 return;
1388 }
1389
1390 if let Some(parent_id) = self
1391 .time_range_pipeline_parent_request_id
1392 .remove(resp.correlation_id())
1393 {
1394 self.handle_time_range_pipeline_child_response(parent_id, &resp);
1395 return;
1396 }
1397
1398 if self
1399 .parent_join_request_id
1400 .contains_key(resp.correlation_id())
1401 {
1402 self.finalize_request_join(resp);
1403 return;
1404 }
1405
1406 let correlation_id = *resp.correlation_id();
1407
1408 match &resp {
1409 DataResponse::Instrument(r) => {
1410 self.handle_instrument_response(r.data.clone());
1411 }
1412 DataResponse::Instruments(r) => {
1413 self.handle_instruments(&r.data);
1414 }
1415 DataResponse::Quotes(r) => {
1416 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
1417 self.handle_quotes(&r.data);
1418 }
1419 }
1420 DataResponse::Trades(r) => {
1421 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
1422 self.handle_trades(&r.data);
1423 }
1424 }
1425 DataResponse::FundingRates(r) => {
1426 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
1427 self.handle_funding_rates(&r.data);
1428 }
1429 }
1430 DataResponse::Bars(r) => {
1431 if !log_if_empty_response(&r.data, &r.bar_type, &correlation_id) {
1432 self.handle_bars(&r.data);
1433 }
1434 }
1435 DataResponse::Book(r) => self.handle_book_response(&r.data),
1436 DataResponse::BookDeltas(r) => {
1437 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
1438 self.handle_book_deltas_response(r);
1439 }
1440 }
1441 DataResponse::BookDepth(r) => {
1442 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
1443 self.handle_book_depth_response(r);
1444 }
1445 }
1446 DataResponse::OptionChainReferencePrice(r) => {
1447 self.process_request_bar_aggregation_response(&resp);
1448 return self.handle_option_chain_reference_price_response(&correlation_id, r);
1449 }
1450 DataResponse::Data(_) => {}
1451 }
1452
1453 self.process_request_bar_aggregation_response(&resp);
1454
1455 msgbus::send_response(&correlation_id, &resp);
1456 }
1457
1458 pub fn new_request_pipeline(&mut self, parent: RequestCommand, n_components: usize) {
1460 let parent_id = *parent.request_id();
1461 self.request_pipeline_n_components
1462 .insert(parent_id, n_components);
1463 self.request_pipeline_parent_request
1464 .insert(parent_id, parent);
1465 self.request_pipeline_responses
1466 .insert(parent_id, Vec::with_capacity(n_components));
1467 }
1468
1469 pub fn register_request_pipeline_leg(&mut self, leg_id: UUID4, parent_id: UUID4) {
1471 self.request_pipeline_parent_request_id
1472 .insert(leg_id, parent_id);
1473 }
1474
1475 fn handle_request_pipeline_response(&mut self, resp: DataResponse) -> Option<DataResponse> {
1480 let leg_id = *resp.correlation_id();
1481 let Some(parent_id) = self.request_pipeline_parent_request_id.remove(&leg_id) else {
1482 return Some(resp);
1483 };
1484
1485 let Some(buf) = self.request_pipeline_responses.get_mut(&parent_id) else {
1486 log::error!("Pipeline response buffer missing for parent {parent_id} (leg {leg_id})");
1487 return Some(resp);
1488 };
1489 buf.push(resp);
1490
1491 let expected = self.request_pipeline_n_components.get(&parent_id).copied();
1492 let received = buf.len();
1493 match expected {
1494 Some(n) if received < n => return None,
1495 Some(_) => {}
1496 None => {
1497 log::error!("Pipeline n_components missing for parent {parent_id}");
1498 return None;
1499 }
1500 }
1501
1502 let mut legs = self.request_pipeline_responses.remove(&parent_id)?;
1503 self.request_pipeline_n_components.remove(&parent_id);
1504 let parent = self.request_pipeline_parent_request.remove(&parent_id);
1505
1506 for leg in &mut legs {
1507 leg.trim_to_bounds();
1508 }
1509
1510 let (parent_start, parent_end) = parent_request_window(parent.as_ref());
1511 let rebuilt = rebuild_pipeline_response(parent_id, parent.as_ref(), legs);
1512
1513 if rebuilt.is_none()
1520 && let Some(original_id) = self.parent_join_request_id.remove(&parent_id)
1521 {
1522 self.pending_join_requests.remove(&original_id);
1523 log::error!(
1524 "Dropped RequestJoin {original_id} because pipeline rebuild failed for dated parent {parent_id}"
1525 );
1526 }
1527
1528 let mut rebuilt = rebuilt?;
1529
1530 if let DataResponse::BookDeltas(r) = &mut rebuilt {
1533 self.book_deltas_snapshot_replay(r);
1534 }
1535
1536 if parent_start.is_some() || parent_end.is_some() {
1542 rebuilt.trim_to_bounds();
1543 }
1544
1545 Some(rebuilt)
1546 }
1547
1548 fn handle_request_join(&mut self, req: RequestJoin) -> anyhow::Result<()> {
1549 if has_time_range_pipeline_params(req.params.as_ref()) {
1550 return self.execute_time_range_pipeline_request(RequestCommand::Join(req));
1551 }
1552
1553 let now_ns = self.clock.borrow().timestamp_ns();
1554 let now_dt = now_ns.to_datetime_utc();
1555 let zero = jiff::Timestamp::UNIX_EPOCH;
1556 let start = req.start.unwrap_or(zero).min(now_dt);
1557 let end = req.end.unwrap_or(now_dt).min(now_dt);
1558 let dated = req.with_dates(Some(start), Some(end), now_ns);
1559
1560 let original_id = req.request_id;
1561 let dated_id = dated.request_id;
1562
1563 self.pending_join_requests.insert(original_id, req);
1564 self.parent_join_request_id.insert(dated_id, original_id);
1565
1566 let leg_ids: Vec<UUID4> = dated.request_ids.clone();
1567 self.new_request_pipeline(RequestCommand::Join(dated), leg_ids.len());
1568 for leg_id in leg_ids {
1569 self.register_request_pipeline_leg(leg_id, dated_id);
1570 }
1571
1572 Ok(())
1573 }
1574
1575 fn finalize_request_join(&mut self, resp: DataResponse) {
1576 let dated_id = *resp.correlation_id();
1577 let Some(original_id) = self.parent_join_request_id.remove(&dated_id) else {
1578 log::error!("parent_join_request_id missing for dated correlation {dated_id}");
1579 return;
1580 };
1581
1582 let Some(original) = self.pending_join_requests.remove(&original_id) else {
1583 log::error!("pending_join_requests missing for original {original_id}");
1584 return;
1585 };
1586
1587 let now_ns = self.clock.borrow().timestamp_ns();
1588
1589 for leg_request_id in &original.request_ids {
1595 let empty = empty_response_like(&resp, *leg_request_id, now_ns);
1596 msgbus::send_response(leg_request_id, &empty);
1597 }
1598
1599 let final_resp = rebind_response_correlation(resp, original_id);
1605 self.response(final_resp);
1606 }
1607}
1608
1609fn register_external_streaming_type(cmd: &SubscribeCommand) {
1610 if let Some(payload_type) = streaming_payload_type(cmd) {
1611 msgbus::get_message_bus()
1612 .borrow_mut()
1613 .add_streaming_type(payload_type);
1614 }
1615}
1616
1617fn publish_external_data_command<T>(client_id: ClientId, command: &T)
1618where
1619 T: Any,
1620{
1621 let topic = format!("commands.data.{client_id}");
1622 msgbus::publish_any(topic.into(), command);
1623}
1624
1625#[rustfmt::skip]
1626fn streaming_payload_type(cmd: &SubscribeCommand) -> Option<BusPayloadType> {
1627 match cmd {
1628 SubscribeCommand::Data(cmd) => Some(BusPayloadType::Custom(Ustr::from(
1629 cmd.data_type.type_name(),
1630 ))),
1631 SubscribeCommand::Instrument(_) | SubscribeCommand::Instruments(_) => Some(BusPayloadType::Instrument),
1632 SubscribeCommand::BookDeltas(_) | SubscribeCommand::BookSnapshots(_) => Some(BusPayloadType::OrderBookDeltas),
1633 SubscribeCommand::BookDepth(_) => Some(BusPayloadType::OrderBookDepth),
1634 SubscribeCommand::Quotes(_) => Some(BusPayloadType::QuoteTick),
1635 SubscribeCommand::Trades(_) => Some(BusPayloadType::TradeTick),
1636 SubscribeCommand::Bars(_) => Some(BusPayloadType::Bar),
1637 SubscribeCommand::MarkPrices(_) => Some(BusPayloadType::MarkPriceUpdate),
1638 SubscribeCommand::IndexPrices(_) => Some(BusPayloadType::IndexPriceUpdate),
1639 SubscribeCommand::FundingRates(_) => Some(BusPayloadType::FundingRateUpdate),
1640 SubscribeCommand::OptionGreeks(_) => Some(BusPayloadType::OptionGreeks),
1641 SubscribeCommand::InstrumentStatus(_)
1642 | SubscribeCommand::InstrumentClose(_)
1643 | SubscribeCommand::OptionChain(_) => None,
1644 }
1645}
1646
1647#[cfg(not(feature = "defi"))]
1648fn log_defi_data_dropped(data: DataRef<'_>) {
1649 log::error!("Cannot process data {data:?}, nautilus-data built without its `defi` feature");
1650}