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 DataRequestFailed, DataResponse, DataResponseEnvelope, IdentifierCountError,
90 OptionChainReferencePriceResponse, PARAMS_IS_PARENT, QuoteRouting, RequestBars,
91 RequestCommand, RequestJoin, RequestOptionChainReferencePrice, RequestQuotes,
92 RequestTrades, SubscribeBars, SubscribeBookDeltas, SubscribeBookDepth, SubscribeCommand,
93 SubscribeOptionChain, SubscribeOptionGreeks, SubscribeQuotes, SubscribeTrades,
94 UnsubscribeBars, UnsubscribeBookDeltas, UnsubscribeBookDepth, UnsubscribeBookSnapshots,
95 UnsubscribeCommand, UnsubscribeInstrumentStatus, UnsubscribeOptionChain,
96 UnsubscribeOptionGreeks, UnsubscribeQuotes, UnsubscribeTrades, ensure_routable_bar_types,
97 ensure_routable_instrument_ids, is_parent_subscription, quote_routing,
98 },
99 msgbus::{
100 self, BusPayloadType, ShareableMessageHandler, TypedHandler, TypedIntoHandler,
101 switchboard::{self, MessagingSwitchboard},
102 },
103 runner::get_data_cmd_sender,
104 timer::{TimeEvent, TimeEventCallback},
105};
106use nautilus_core::{
107 DurationNanos, Params, UUID4, UnixNanos, WeakCell,
108 correctness::{FAILED, check_key_in_map, check_key_not_in_map, check_predicate_true},
109 datetime::NANOSECONDS_IN_DAY,
110};
111#[cfg(feature = "defi")]
112use nautilus_model::defi::DefiData;
113use nautilus_model::{
114 data::{
115 Bar, BarType, CustomData, Data, DataRef, DataType, FundingRateUpdate, HasTsInit,
116 IndexPriceUpdate, InstrumentClose, InstrumentStatus, MarkPriceUpdate, OrderBookDelta,
117 OrderBookDeltas, OrderBookDepth, QuoteTick, TradeTick,
118 option_chain::{OptionGreeks, StrikeRange},
119 },
120 enums::{
121 AggregationSource, BarAggregation, BookType, InstrumentClass, MarketStatusAction,
122 PriceType, RecordFlag,
123 },
124 identifiers::{
125 ClientId, GENERIC_SPREAD_ID_SEPARATOR, InstrumentId, OptionSeriesId, Venue,
126 parse_generic_spread_id_legs,
127 },
128 instruments::{Instrument, InstrumentAny, SyntheticInstrument},
129 orderbook::OrderBook,
130 types::{Price, Quantity},
131};
132use option_chain::{
133 OptionChainBootstrapper, OptionChainGreeksBootstrap, PendingOptionChainRequest,
134};
135use requests::{
136 ContinuousFutureRequest, ContinuousFutureRequestState, ContinuousFutureSegment,
137 ContinuousFutureSource, RequestBarAggregation, continuous_future_request_from_bars,
138 continuous_future_subscription_from_bars, has_continuous_future_params,
139 request_bar_aggregation_from_params, request_params, response_params,
140};
141use responses::{
142 log_if_empty_response, parent_request_window, rebind_response_correlation,
143 rebuild_pipeline_response, same_instrument,
144};
145use serde_json::Value;
146use spread_quote::SpreadQuoteState;
147#[cfg(feature = "streaming")]
148use streaming::CatalogMap;
149use time_range::{
150 TimeRangePipelineState, has_time_range_pipeline_params, is_time_range_pipeline_variant,
151};
152use ustr::Ustr;
153
154#[cfg(feature = "defi")]
155#[allow(unused_imports)] use crate::defi::engine as _;
157#[cfg(feature = "defi")]
158use crate::engine::pool::PoolUpdater;
159use crate::{
160 aggregation::{
161 BarAggregator, RenkoBarAggregator, SpreadQuoteAggregator, TickBarAggregator,
162 TickImbalanceBarAggregator, TickRunsBarAggregator, TimeBarAggregator, ValueBarAggregator,
163 ValueImbalanceBarAggregator, ValueRunsBarAggregator, VolumeBarAggregator,
164 VolumeImbalanceBarAggregator, VolumeRunsBarAggregator,
165 },
166 client::DataClientAdapter,
167 option_chains::OptionChainManager,
168 subscription::{
169 SubscriptionKey, SubscriptionRegistry, SubscriptionRelease, split_subscribe,
170 split_unsubscribe,
171 },
172};
173
174#[derive(Debug)]
176pub struct DataEngine {
177 clock: Rc<RefCell<dyn Clock>>,
178 cache: Rc<RefCell<Cache>>,
179 config: DataEngineConfig,
180 msgbus_priority: u32,
181 subscriptions_external: SubscriptionRegistry<(ClientId, SubscriptionKey), SubscribeCommand>,
182 clients: IndexMap<ClientId, DataClientAdapter>,
183 default_client_id: Option<ClientId>,
184 routing_map: IndexMap<Venue, ClientId>,
185 book_intervals: AHashMap<NonZeroUsize, BookSnapshotInfos>,
186 book_snapshot_counts: IndexMap<BookSnapshotKey, usize>,
187 book_snapshot_sources: AHashMap<InstrumentId, BookSnapshotSource>,
188 book_deltas_counts: IndexMap<BookDeltasKey, usize>,
189 book_depth_counts: IndexMap<BookDeltasKey, usize>,
190 book_updaters: AHashMap<InstrumentId, Rc<BookUpdater>>,
191 book_subscriptions: AHashMap<InstrumentId, BookSubscription>,
192 book_subscription_owners: AHashMap<InstrumentId, Vec<Rc<BookSubscriptionOwner>>>,
193 book_snapshotters: AHashMap<NonZeroUsize, Rc<BookSnapshotter>>,
194 bar_aggregators: IndexMap<BarAggregatorKey, Rc<RefCell<Box<dyn BarAggregator>>>>,
195 bar_aggregator_handlers: AHashMap<BarAggregatorKey, Vec<BarAggregatorSubscription>>,
196 subscriptions_bar_aggregation: AHashMap<BarType, BarAggregationSubscription>,
197 request_bar_aggregations: AHashMap<UUID4, RequestBarAggregation>,
198 inflight_requests: AHashMap<UUID4, RequestCommand>,
199 request_pipeline_parent_request: AHashMap<UUID4, RequestCommand>,
200 request_pipeline_n_components: AHashMap<UUID4, usize>,
201 request_pipeline_parent_request_id: AHashMap<UUID4, (UUID4, usize)>,
202 request_pipeline_responses: AHashMap<UUID4, Vec<Option<Vec<DataResponseEnvelope>>>>,
203 time_range_pipeline_requests: AHashMap<UUID4, TimeRangePipelineState>,
204 time_range_pipeline_parent_request_id: AHashMap<UUID4, UUID4>,
205 parent_join_request_id: AHashMap<UUID4, UUID4>,
206 pending_join_requests: AHashMap<UUID4, RequestJoin>,
207 continuous_future_requests: AHashMap<UUID4, ContinuousFutureRequestState>,
208 continuous_future_parent_request_id: AHashMap<UUID4, UUID4>,
209 continuous_future_subscriptions: AHashMap<BarType, ContinuousFutureSubscriptionState>,
210 continuous_future_roller: Option<Rc<ContinuousFutureRoller>>,
211 spread_quote_states: AHashMap<InstrumentId, SpreadQuoteState>,
212 option_chain_managers: AHashMap<OptionSeriesId, Rc<RefCell<OptionChainManager>>>,
213 option_chain_instrument_index: AHashMap<InstrumentId, OptionSeriesId>,
214 deferred_cmd_queue: DeferredCommandQueue,
215 option_chain_bootstrapper: Option<Rc<OptionChainBootstrapper>>,
216 pending_option_chain_requests: AHashMap<UUID4, PendingOptionChainRequest>,
217 option_chain_greeks_bootstraps: AHashMap<OptionSeriesId, OptionChainGreeksBootstrap>,
218 synthetic_quote_feeds: AHashMap<InstrumentId, Vec<SyntheticInstrument>>,
219 synthetic_trade_feeds: AHashMap<InstrumentId, Vec<SyntheticInstrument>>,
220 subscribed_synthetic_quotes: AHashMap<InstrumentId, usize>,
221 subscribed_synthetic_trades: AHashMap<InstrumentId, usize>,
222 buffered_deltas_map: AHashMap<InstrumentId, OrderBookDeltas>,
223 deltas_frame: Vec<OrderBookDelta>,
224 command_count: u64,
225 data_count: u64,
226 request_count: u64,
227 response_count: u64,
228 #[cfg(feature = "streaming")]
229 catalogs: CatalogMap,
230 #[cfg(feature = "defi")]
231 pub(crate) pool_updaters: AHashMap<InstrumentId, Rc<PoolUpdater>>,
232 #[cfg(feature = "defi")]
233 pub(crate) pool_snapshot_pending: AHashMap<InstrumentId, UUID4>,
234 #[cfg(feature = "defi")]
235 pub(crate) pool_event_buffers: AHashMap<InstrumentId, Vec<DefiData>>,
236}
237
238impl DataEngine {
239 #[must_use]
241 pub fn new(
242 clock: Rc<RefCell<dyn Clock>>,
243 cache: Rc<RefCell<Cache>>,
244 config: Option<DataEngineConfig>,
245 ) -> Self {
246 let config = config.unwrap_or_default();
247 if let Some(client_ids) = &config.external_clients {
248 let mut cache = cache.borrow_mut();
249 for client_id in client_ids {
250 cache.add_external_data_client(*client_id);
251 }
252 }
253
254 Self {
255 clock,
256 cache,
257 config,
258 msgbus_priority: 10, subscriptions_external: SubscriptionRegistry::default(),
260 clients: IndexMap::new(),
261 default_client_id: None,
262 routing_map: IndexMap::new(),
263 book_intervals: AHashMap::new(),
264 book_snapshot_counts: IndexMap::new(),
265 book_snapshot_sources: AHashMap::new(),
266 book_deltas_counts: IndexMap::new(),
267 book_depth_counts: IndexMap::new(),
268 book_updaters: AHashMap::new(),
269 book_subscriptions: AHashMap::new(),
270 book_subscription_owners: AHashMap::new(),
271 book_snapshotters: AHashMap::new(),
272 bar_aggregators: IndexMap::new(),
273 bar_aggregator_handlers: AHashMap::new(),
274 subscriptions_bar_aggregation: AHashMap::new(),
275 request_bar_aggregations: AHashMap::new(),
276 inflight_requests: AHashMap::new(),
277 request_pipeline_parent_request: AHashMap::new(),
278 request_pipeline_n_components: AHashMap::new(),
279 request_pipeline_parent_request_id: AHashMap::new(),
280 request_pipeline_responses: AHashMap::new(),
281 time_range_pipeline_requests: AHashMap::new(),
282 time_range_pipeline_parent_request_id: AHashMap::new(),
283 parent_join_request_id: AHashMap::new(),
284 pending_join_requests: AHashMap::new(),
285 continuous_future_requests: AHashMap::new(),
286 continuous_future_parent_request_id: AHashMap::new(),
287 continuous_future_subscriptions: AHashMap::new(),
288 continuous_future_roller: None,
289 spread_quote_states: AHashMap::new(),
290 option_chain_managers: AHashMap::new(),
291 option_chain_instrument_index: AHashMap::new(),
292 deferred_cmd_queue: Rc::new(RefCell::new(VecDeque::new())),
293 option_chain_bootstrapper: None,
294 pending_option_chain_requests: AHashMap::new(),
295 option_chain_greeks_bootstraps: AHashMap::new(),
296 synthetic_quote_feeds: AHashMap::new(),
297 synthetic_trade_feeds: AHashMap::new(),
298 subscribed_synthetic_quotes: AHashMap::new(),
299 subscribed_synthetic_trades: AHashMap::new(),
300 buffered_deltas_map: AHashMap::new(),
301 deltas_frame: Vec::new(),
302 command_count: 0,
303 data_count: 0,
304 request_count: 0,
305 response_count: 0,
306 #[cfg(feature = "streaming")]
307 catalogs: CatalogMap::new(),
308 #[cfg(feature = "defi")]
309 pool_updaters: AHashMap::new(),
310 #[cfg(feature = "defi")]
311 pool_snapshot_pending: AHashMap::new(),
312 #[cfg(feature = "defi")]
313 pool_event_buffers: AHashMap::new(),
314 }
315 }
316
317 #[must_use]
319 pub fn clock(&self) -> &Rc<RefCell<dyn Clock>> {
320 &self.clock
321 }
322
323 #[must_use]
325 pub fn cache(&self) -> &Rc<RefCell<Cache>> {
326 &self.cache
327 }
328
329 #[must_use]
331 pub const fn config(&self) -> &DataEngineConfig {
332 &self.config
333 }
334
335 #[cfg(feature = "defi")]
336 #[must_use]
337 pub(crate) const fn msgbus_priority(&self) -> u32 {
338 self.msgbus_priority
339 }
340
341 pub fn register_msgbus_handlers(engine: &Rc<RefCell<Self>>) {
343 let weak = WeakCell::from(Rc::downgrade(engine));
344 engine.borrow_mut().continuous_future_roller =
345 Some(Rc::new(ContinuousFutureRoller::new(engine)));
346 engine.borrow_mut().option_chain_bootstrapper =
347 Some(Rc::new(OptionChainBootstrapper::new(engine)));
348
349 let weak1 = weak.clone();
350 msgbus::register_data_command_endpoint(
351 MessagingSwitchboard::data_engine_execute(),
352 TypedIntoHandler::from(move |cmd: DataCommand| {
353 if let Some(rc) = weak1.upgrade() {
354 rc.borrow_mut().execute(cmd);
355 }
356 }),
357 );
358
359 msgbus::register_data_command_endpoint(
360 MessagingSwitchboard::data_engine_queue_execute(),
361 TypedIntoHandler::from(move |cmd: DataCommand| {
362 get_data_cmd_sender().execute(cmd);
363 }),
364 );
365
366 let weak2 = weak.clone();
368 msgbus::register_any(
369 MessagingSwitchboard::data_engine_process(),
370 ShareableMessageHandler::from_any(move |data: &dyn Any| {
371 if let Some(rc) = weak2.upgrade() {
372 rc.borrow_mut().process(data);
373 }
374 }),
375 );
376
377 let weak3 = weak.clone();
379 msgbus::register_data_endpoint(
380 MessagingSwitchboard::data_engine_process_data(),
381 TypedIntoHandler::from(move |data: Data| {
382 if let Some(rc) = weak3.upgrade() {
383 rc.borrow_mut().process_data(data);
384 }
385 }),
386 );
387
388 #[cfg(feature = "defi")]
390 {
391 let weak4 = weak.clone();
392 msgbus::register_defi_data_endpoint(
393 MessagingSwitchboard::data_engine_process_defi_data(),
394 TypedIntoHandler::from(move |data: DefiData| {
395 if let Some(rc) = weak4.upgrade() {
396 rc.borrow_mut().process_defi_data(data);
397 }
398 }),
399 );
400 }
401
402 let weak5 = weak;
403 msgbus::register_data_response_endpoint(
404 MessagingSwitchboard::data_engine_response(),
405 TypedIntoHandler::from(move |resp: DataResponse| {
406 if let Some(rc) = weak5.upgrade() {
407 rc.borrow_mut().response(resp);
408 }
409 }),
410 );
411 }
412
413 #[must_use]
415 pub const fn command_count(&self) -> u64 {
416 self.command_count
417 }
418
419 #[must_use]
421 pub const fn data_count(&self) -> u64 {
422 self.data_count
423 }
424
425 #[cfg(feature = "defi")]
426 pub(crate) const fn increment_data_count(&mut self) {
427 self.data_count += 1;
428 }
429
430 #[must_use]
432 pub const fn request_count(&self) -> u64 {
433 self.request_count
434 }
435
436 #[must_use]
438 pub const fn response_count(&self) -> u64 {
439 self.response_count
440 }
441
442 #[must_use]
444 pub fn request_pipeline_count(&self) -> usize {
445 self.request_pipeline_parent_request.len()
446 }
447
448 #[must_use]
450 pub fn time_range_pipeline_count(&self) -> usize {
451 self.time_range_pipeline_requests.len()
452 }
453
454 #[must_use]
456 pub fn pending_join_request_count(&self) -> usize {
457 self.pending_join_requests.len()
458 }
459
460 pub fn start(&mut self) {
462 for client in self.get_clients_mut() {
463 if client.start().is_err() {
464 log::error!("Failed to start data client {}", client.client_id());
465 }
466 }
467
468 for ((_, request_id), aggregator) in &self.bar_aggregators {
469 let is_subscription = request_id.is_none() && !aggregator.borrow().is_historical();
472 if is_subscription && aggregator.borrow().bar_type().spec().is_time_aggregated() {
473 aggregator
474 .borrow_mut()
475 .start_timer(Some(Rc::clone(aggregator)));
476 }
477 }
478
479 for state in self.spread_quote_states.values() {
480 state
481 .aggregator
482 .borrow_mut()
483 .start_timer(Some(Rc::clone(&state.aggregator)));
484 }
485 }
486
487 pub fn stop(&mut self) {
489 for client in self.get_clients_mut() {
490 if client.stop().is_err() {
491 log::error!("Failed to stop data client {}", client.client_id());
492 }
493 }
494
495 for aggregator in self.bar_aggregators.values() {
496 aggregator.borrow_mut().stop();
497 }
498
499 for state in self.spread_quote_states.values() {
500 state.aggregator.borrow_mut().stop_timer();
501 }
502 }
503
504 pub fn reset(&mut self) {
506 for client in self.get_clients_mut() {
507 match client.reset() {
508 Ok(()) => client.clear_subscription_state(),
509 Err(_) => log::error!("Failed to reset data client {}", client.client_id()),
510 }
511 }
512
513 let keys: Vec<BarAggregatorKey> = self.bar_aggregators.keys().copied().collect();
514 for (bar_type, request_id) in keys {
515 if let Err(e) = self.stop_bar_aggregator(bar_type, request_id) {
516 log::error!("Error stopping bar aggregator during reset for {bar_type}: {e}");
517 }
518 }
519 self.subscriptions_bar_aggregation.clear();
520
521 self.request_bar_aggregations.clear();
522 self.inflight_requests.clear();
523 self.request_pipeline_parent_request.clear();
524 self.request_pipeline_n_components.clear();
525 self.request_pipeline_parent_request_id.clear();
526 self.request_pipeline_responses.clear();
527 self.time_range_pipeline_requests.clear();
528 self.time_range_pipeline_parent_request_id.clear();
529 self.parent_join_request_id.clear();
530 self.pending_join_requests.clear();
531 self.continuous_future_requests.clear();
532 self.continuous_future_parent_request_id.clear();
533
534 for state in self.continuous_future_subscriptions.values_mut() {
535 if let Some(name) = state.timer_name.take() {
536 self.clock.borrow_mut().cancel_timer(&name);
537 }
538 }
539 self.continuous_future_subscriptions.clear();
540
541 let spread_ids: Vec<InstrumentId> = self.spread_quote_states.keys().copied().collect();
542 for spread_id in spread_ids {
543 self.stop_spread_quote_aggregation(spread_id);
544 }
545
546 let managers: Vec<_> = self.option_chain_managers.drain().collect();
548 for (_, manager) in managers {
549 manager.borrow_mut().teardown(&self.clock);
550 }
551
552 self.option_chain_instrument_index.clear();
553 self.cancel_pending_option_chain_requests(None);
554 self.clear_option_chain_greeks_bootstraps();
555
556 let book_updaters: Vec<(InstrumentId, Rc<BookUpdater>)> =
561 self.book_updaters.drain().collect();
562 for (instrument_id, updater) in book_updaters {
563 let deltas_topic = switchboard::get_book_deltas_topic(instrument_id);
564 let depth_topic = switchboard::get_book_depth_topic(instrument_id);
565 let deltas_handler: TypedHandler<OrderBookDeltas> =
566 TypedHandler::new(Rc::clone(&updater));
567 let depth_handler: TypedHandler<OrderBookDepth> = TypedHandler::new(updater);
568 msgbus::unsubscribe_book_deltas(deltas_topic.into(), &deltas_handler);
569 msgbus::unsubscribe_book_depth(depth_topic.into(), &depth_handler);
570 }
571
572 self.book_subscriptions.clear();
573 self.book_subscription_owners.clear();
574
575 self.book_deltas_counts.clear();
576 self.book_depth_counts.clear();
577 self.book_intervals.clear();
578 self.book_snapshot_counts.clear();
579 self.book_snapshot_sources.clear();
580 self.book_snapshotters.clear();
581 self.buffered_deltas_map.clear();
582 self.deltas_frame.clear();
583
584 self.synthetic_quote_feeds.clear();
585 self.synthetic_trade_feeds.clear();
586 self.subscribed_synthetic_quotes.clear();
587 self.subscribed_synthetic_trades.clear();
588 self.subscriptions_external.clear();
589
590 self.deferred_cmd_queue.borrow_mut().clear();
591
592 #[cfg(feature = "defi")]
593 self.clear_pool_updaters();
594
595 self.clock.borrow_mut().cancel_timers();
596
597 self.command_count = 0;
598 self.data_count = 0;
599 self.request_count = 0;
600 self.response_count = 0;
601 }
602
603 pub fn dispose(&mut self) {
605 for client in self.get_clients_mut() {
606 if client.dispose().is_err() {
607 log::error!("Failed to dispose data client {}", client.client_id());
608 }
609 }
610
611 self.clear_option_chain_greeks_bootstraps();
612
613 let mut cf_sources = Vec::new();
616
617 for state in self.continuous_future_subscriptions.values_mut() {
618 if let Some(name) = state.timer_name.take() {
619 self.clock.borrow_mut().cancel_timer(&name);
620 }
621
622 if let Some(subscription) = state.active_source_subscription.take() {
623 cf_sources.push((state.target_bar_type, subscription));
624 }
625 }
626
627 for (target_bar_type, subscription) in cf_sources {
628 self.unsubscribe_continuous_future_source(target_bar_type, subscription);
629 }
630 self.continuous_future_subscriptions.clear();
631
632 let keys: Vec<BarAggregatorKey> = self.bar_aggregators.keys().copied().collect();
635 for (bar_type, request_id) in keys {
636 if let Err(e) = self.stop_bar_aggregator(bar_type, request_id) {
637 log::error!("Error stopping bar aggregator during dispose for {bar_type}: {e}");
638 }
639 }
640
641 self.subscriptions_external.clear();
642
643 self.clock.borrow_mut().cancel_timers();
644 }
645
646 pub async fn connect(&mut self) {
651 let futures: Vec<_> = self
652 .get_clients_mut()
653 .into_iter()
654 .map(|client| async move {
655 let client_id = client.client_id();
656 (client_id, client.connect().await)
657 })
658 .collect();
659
660 let results = join_all(futures).await;
661
662 for (client_id, result) in results {
663 if result.is_err() {
664 log::error!("Failed to connect data client {client_id}: connection failed");
665 }
666 }
667 }
668
669 pub async fn disconnect(&mut self) -> anyhow::Result<()> {
675 let futures: Vec<_> = self
676 .get_clients_mut()
677 .into_iter()
678 .map(|client| async move {
679 let client_id = client.client_id();
680 (client_id, client.disconnect().await)
681 })
682 .collect();
683
684 let results = join_all(futures).await;
685
686 #[cfg(feature = "defi")]
688 self.abandon_pool_snapshots();
689
690 let failed_clients: Vec<_> = results
691 .into_iter()
692 .filter_map(|(client_id, result)| result.err().map(|_| client_id.to_string()))
693 .collect();
694
695 if failed_clients.is_empty() {
696 Ok(())
697 } else {
698 anyhow::bail!(
699 "Failed to disconnect data clients: {}",
700 failed_clients.join(", ")
701 )
702 }
703 }
704
705 #[must_use]
707 pub fn check_connected(&self) -> bool {
708 self.get_clients()
709 .iter()
710 .all(|client| client.is_connected())
711 }
712
713 #[must_use]
715 pub fn check_disconnected(&self) -> bool {
716 self.get_clients()
717 .iter()
718 .all(|client| !client.is_connected())
719 }
720
721 #[must_use]
723 pub fn subscribed_custom_data(&self) -> Vec<DataType> {
724 self.collect_subscriptions(|client| &client.subscriptions_custom)
725 }
726
727 #[must_use]
729 pub fn subscribed_instruments(&self) -> Vec<InstrumentId> {
730 self.collect_subscriptions(|client| &client.subscriptions_instrument)
731 }
732
733 #[must_use]
735 pub fn subscribed_quotes(&self) -> Vec<InstrumentId> {
736 self.collect_subscriptions(|client| &client.subscriptions_quotes)
737 }
738
739 #[must_use]
741 pub fn subscribed_trades(&self) -> Vec<InstrumentId> {
742 self.collect_subscriptions(|client| &client.subscriptions_trades)
743 }
744
745 #[must_use]
748 pub fn subscribed_bars(&self) -> Vec<BarType> {
749 let mut subscribed = self.collect_subscriptions(|client| &client.subscriptions_bars);
750 subscribed.extend(
751 self.bar_aggregators
752 .keys()
753 .filter(|(_, request_id)| request_id.is_none())
754 .map(|(bar_type, _)| *bar_type),
755 );
756 subscribed
757 }
758
759 #[must_use]
761 pub fn subscribed_mark_prices(&self) -> Vec<InstrumentId> {
762 self.collect_subscriptions(|client| &client.subscriptions_mark_prices)
763 }
764
765 #[must_use]
767 pub fn subscribed_index_prices(&self) -> Vec<InstrumentId> {
768 self.collect_subscriptions(|client| &client.subscriptions_index_prices)
769 }
770
771 #[must_use]
773 pub fn subscribed_funding_rates(&self) -> Vec<InstrumentId> {
774 self.collect_subscriptions(|client| &client.subscriptions_funding_rates)
775 }
776
777 #[must_use]
779 pub fn subscribed_instrument_status(&self) -> Vec<InstrumentId> {
780 self.collect_subscriptions(|client| &client.subscriptions_instrument_status)
781 }
782
783 #[must_use]
785 pub fn subscribed_instrument_close(&self) -> Vec<InstrumentId> {
786 self.collect_subscriptions(|client| &client.subscriptions_instrument_close)
787 }
788
789 pub fn execute(&mut self, cmd: DataCommand) {
798 match &cmd {
799 DataCommand::Subscribe(_) | DataCommand::Unsubscribe(_) => self.command_count += 1,
800 DataCommand::Request(_) => self.request_count += 1,
801 #[cfg(feature = "defi")]
802 DataCommand::DefiRequest(_) => self.request_count += 1,
803 #[cfg(feature = "defi")]
804 DataCommand::DefiSubscribe(_) | DataCommand::DefiUnsubscribe(_) => {
805 self.command_count += 1;
806 }
807 _ => {}
808 }
809
810 let command_context = match &cmd {
811 DataCommand::Subscribe(c) => (
812 "Subscribe",
813 c.client_id().copied(),
814 c.venue().copied(),
815 Some(c.command_id()),
816 ),
817 DataCommand::Unsubscribe(c) => (
818 "Unsubscribe",
819 c.client_id().copied(),
820 c.venue().copied(),
821 Some(c.command_id()),
822 ),
823 DataCommand::Request(c) => (
824 "Request",
825 c.client_id().copied(),
826 c.venue().copied(),
827 Some(*c.request_id()),
828 ),
829 #[cfg(feature = "defi")]
830 DataCommand::DefiRequest(c) => (
831 "DefiRequest",
832 c.client_id().copied(),
833 c.venue().copied(),
834 Some(*c.request_id()),
835 ),
836 #[cfg(feature = "defi")]
837 DataCommand::DefiSubscribe(c) => (
838 "DefiSubscribe",
839 c.client_id().copied(),
840 c.venue().copied(),
841 Some(c.command_id()),
842 ),
843 #[cfg(feature = "defi")]
844 DataCommand::DefiUnsubscribe(c) => (
845 "DefiUnsubscribe",
846 c.client_id().copied(),
847 c.venue().copied(),
848 Some(c.command_id()),
849 ),
850 _ => ("Unknown", None, None, None),
851 };
852
853 let result = match cmd {
854 DataCommand::Subscribe(c) => self.execute_subscribe(c),
855 DataCommand::Unsubscribe(c) => self.execute_unsubscribe(&c),
856 DataCommand::Request(c) => self.execute_request(c),
857 #[cfg(feature = "defi")]
858 DataCommand::DefiRequest(c) => self.execute_defi_request(c),
859 #[cfg(feature = "defi")]
860 DataCommand::DefiSubscribe(c) => self.execute_defi_subscribe(c),
861 #[cfg(feature = "defi")]
862 DataCommand::DefiUnsubscribe(c) => self.execute_defi_unsubscribe(&c),
863 _ => {
864 log::warn!("Unhandled DataCommand variant");
865 Ok(())
866 }
867 };
868
869 if let Err(e) = result {
870 log::error!("Data command failed: {command_context:?}: {e}");
871 }
872 }
873
874 pub fn execute_subscribe(&mut self, cmd: SubscribeCommand) -> anyhow::Result<()> {
881 cmd.validate_routing(&self.cache.borrow())?;
882 if let Some(client_id) = cmd.client_id().copied()
883 && self.is_external_client(client_id)
884 {
885 self.subscribe_external(client_id, &cmd);
886 return Ok(());
887 }
888
889 self.retain_intent(&cmd)?;
890
891 let retained = cmd.clone();
892 let (groups, result) = self.prepare_subscribe_groups(cmd);
893 self.dispatch_subscribe_groups(&retained, groups);
894 result
895 }
896
897 fn retain_intent(&mut self, cmd: &SubscribeCommand) -> anyhow::Result<()> {
898 let retain_intent = match cmd {
899 SubscribeCommand::Quotes(command) => {
900 self.quote_routing(&command.instrument_ids, command.params.as_ref())?
901 == QuoteRouting::Client
902 }
903 SubscribeCommand::Trades(command) => !command.instrument_ids[0].is_synthetic(),
904 _ => false,
905 };
906
907 if let Some(client) = self.get_command_client(cmd.client_id(), cmd.venue()) {
908 if let SubscribeCommand::Bars(command) = cmd {
909 let external: Vec<_> = command
910 .bar_types
911 .iter()
912 .copied()
913 .filter(BarType::is_externally_aggregated)
914 .collect();
915
916 if !external.is_empty() {
917 let intent = SubscribeCommand::Bars(command.with_bar_types(external));
918 client.retain_intent(&intent);
919 }
920 } else if retain_intent {
921 client.retain_intent(cmd);
922 }
923 }
924
925 Ok(())
926 }
927
928 fn prepare_subscribe_groups(
929 &mut self,
930 cmd: SubscribeCommand,
931 ) -> (Vec<SubscribeCommand>, anyhow::Result<()>) {
932 let members = if matches!(
933 &cmd,
934 SubscribeCommand::Quotes(_) | SubscribeCommand::Trades(_)
935 ) {
936 vec![cmd]
937 } else {
938 split_subscribe(cmd)
939 };
940
941 let mut groups: IndexMap<Option<Value>, Vec<SubscribeCommand>> = IndexMap::new();
942 let mut result = Ok(());
943
944 for member in members {
945 let prepared = match self.prepare_subscribe_member(&member) {
946 Ok(Some(prepared)) => prepared,
947 Ok(None) => continue,
948 Err(e) => {
949 log::error!(
950 "Subscribe preparation failed: command={}, client={:?}, instrument_ids={:?}, bar_types={:?}: {e}",
951 member.command_id(),
952 member.client_id(),
953 member.instrument_ids(),
954 member.bar_types(),
955 );
956
957 if result.is_ok() {
958 result = Err(e);
959 }
960
961 continue;
962 }
963 };
964
965 let params = prepared.params().map(|params| {
967 Value::Object(params.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
968 });
969
970 groups.entry(params).or_default().push(prepared);
971 }
972
973 let groups = groups
974 .into_values()
975 .map(|mut members| {
976 if members.len() == 1 {
977 return members.pop().expect("nonempty group");
978 }
979
980 let first = &members[0];
981 if first.instrument_ids().is_some() {
982 let ids: Vec<_> = members
983 .iter()
984 .flat_map(|member| member.instrument_ids().expect("same parent kind"))
985 .copied()
986 .collect();
987 first.with_instrument_ids(ids).expect("instrument command")
988 } else if first.bar_types().is_some() {
989 let types: Vec<_> = members
990 .iter()
991 .flat_map(|member| member.bar_types().expect("same parent kind"))
992 .copied()
993 .collect();
994 first.with_bar_types(types).expect("bar command")
995 } else {
996 first.clone()
997 }
998 })
999 .collect();
1000
1001 (groups, result)
1002 }
1003
1004 fn dispatch_subscribe_groups(&mut self, cmd: &SubscribeCommand, groups: Vec<SubscribeCommand>) {
1005 for group in groups {
1006 let retained = if let Some(ids) = group.instrument_ids() {
1007 cmd.with_instrument_ids(ids).expect("instrument parent")
1008 } else if let Some(types) = group.bar_types() {
1009 cmd.with_bar_types(types).expect("bar parent")
1010 } else {
1011 cmd.clone()
1012 };
1013
1014 let retain_on_failure = !matches!(
1015 &group,
1016 SubscribeCommand::BookDeltas(_) | SubscribeCommand::BookDepth(_)
1017 );
1018
1019 if let Some(client) = self.get_command_client(group.client_id(), group.venue()) {
1020 client.execute_subscribe_with_retained(group, retained, retain_on_failure);
1021 } else {
1022 log::error!(
1023 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
1024 group.client_id(),
1025 group.venue()
1026 );
1027 }
1028 }
1029 }
1030
1031 fn prepare_subscribe_member(
1032 &mut self,
1033 cmd: &SubscribeCommand,
1034 ) -> anyhow::Result<Option<SubscribeCommand>> {
1035 match cmd {
1037 SubscribeCommand::BookDeltas(book_cmd) => {
1038 if !self.subscribe_book_deltas(book_cmd)? && self.client_subscription_active(cmd) {
1039 return Ok(None);
1040 }
1041 }
1042 SubscribeCommand::BookDepth(book_cmd) => {
1043 if !self.subscribe_book_depth(book_cmd)? && self.client_subscription_active(cmd) {
1044 return Ok(None);
1045 }
1046 }
1047 SubscribeCommand::BookSnapshots(cmd) => {
1048 self.subscribe_book_snapshots(cmd)?;
1050 return Ok(None);
1051 }
1052 SubscribeCommand::Bars(cmd) if has_continuous_future_params(cmd.params.as_ref()) => {
1053 self.subscribe_continuous_future_bars(cmd)?;
1054 return Ok(None);
1055 }
1056 SubscribeCommand::Bars(cmd) => {
1057 self.subscribe_bars(cmd)?;
1058 if cmd
1059 .single_bar_type()
1060 .expect("member preparation receives one bar type")
1061 .is_internally_aggregated()
1062 {
1063 return Ok(None);
1064 }
1065 }
1066 SubscribeCommand::OptionChain(cmd) if cmd.snapshot_interval_ms == Some(0) => {
1067 anyhow::bail!(
1068 "Cannot subscribe option chain {} with a zero `snapshot_interval_ms`; use `None` for raw mode",
1069 cmd.series_id,
1070 );
1071 }
1072 SubscribeCommand::OptionChain(cmd) => {
1073 self.subscribe_option_chain(cmd);
1074 return Ok(None);
1075 }
1076 SubscribeCommand::Quotes(cmd) if self.subscribe_quotes(cmd)? => {
1077 return Ok(None);
1078 }
1079 SubscribeCommand::Trades(cmd) if self.subscribe_trades(cmd) => {
1080 return Ok(None);
1081 }
1082 SubscribeCommand::Instrument(cmd)
1083 if cmd.instrument_ids.iter().any(InstrumentId::is_synthetic) =>
1084 {
1085 anyhow::bail!("Cannot subscribe for synthetic instrument `Instrument` data");
1086 }
1087 SubscribeCommand::InstrumentStatus(cmd)
1088 if cmd.instrument_ids.iter().any(InstrumentId::is_synthetic) =>
1089 {
1090 anyhow::bail!("Cannot subscribe for synthetic instrument `InstrumentStatus` data");
1091 }
1092 SubscribeCommand::InstrumentClose(cmd)
1093 if cmd.instrument_ids.iter().any(InstrumentId::is_synthetic) =>
1094 {
1095 anyhow::bail!("Cannot subscribe for synthetic instrument `InstrumentClose` data");
1096 }
1097 SubscribeCommand::OptionGreeks(cmd)
1098 if cmd.instrument_ids.iter().any(InstrumentId::is_synthetic) =>
1099 {
1100 anyhow::bail!("Cannot subscribe for synthetic instrument `OptionGreeks` data");
1101 }
1102 _ => {} }
1104
1105 let cmd = cmd.clone();
1106 #[cfg(feature = "streaming")]
1107 let cmd = self.subscribe_command_with_prefilled_start_ns(cmd)?;
1108
1109 Ok(Some(cmd))
1110 }
1111
1112 fn client_subscription_active(&mut self, cmd: &SubscribeCommand) -> bool {
1113 self.get_command_client(cmd.client_id(), cmd.venue())
1114 .is_some_and(|client| client.has_active_subscription(cmd))
1115 }
1116
1117 pub fn execute_unsubscribe(&mut self, cmd: &UnsubscribeCommand) -> anyhow::Result<()> {
1123 if let Some(bar_types) = cmd.bar_types() {
1124 ensure_routable_bar_types(bar_types, cmd.client_id().is_some())?;
1125 }
1126
1127 if let Some(ids) = cmd.instrument_ids() {
1128 ensure_routable_instrument_ids(ids, cmd.client_id().is_some())?;
1129 }
1130
1131 if let UnsubscribeCommand::Trades(trade) = cmd
1132 && !trade
1133 .instrument_ids
1134 .iter()
1135 .all(|id| id.is_synthetic() == trade.instrument_ids[0].is_synthetic())
1136 {
1137 anyhow::bail!("trade identifier lists cannot mix client and synthetic routes");
1138 }
1139
1140 if let Some(client_id) = cmd.client_id().copied()
1141 && self.is_external_client(client_id)
1142 {
1143 self.unsubscribe_external(client_id, cmd);
1144 return Ok(());
1145 }
1146
1147 if matches!(
1148 &cmd,
1149 UnsubscribeCommand::Instrument(_)
1150 | UnsubscribeCommand::InstrumentStatus(_)
1151 | UnsubscribeCommand::InstrumentClose(_)
1152 | UnsubscribeCommand::OptionGreeks(_)
1153 | UnsubscribeCommand::BookDeltas(_)
1154 | UnsubscribeCommand::BookDepth(_)
1155 | UnsubscribeCommand::BookSnapshots(_)
1156 ) && cmd
1157 .instrument_ids()
1158 .is_some_and(|ids| ids.len() > 1 && ids.iter().any(InstrumentId::is_synthetic))
1159 {
1160 anyhow::bail!("Cannot unsubscribe synthetic instruments for this data type");
1161 }
1162
1163 let (prepared, result) = self.prepare_unsubscribe_members(cmd);
1164
1165 for command in UnsubscribeCommand::group(prepared) {
1166 if let Some(client) = self.get_command_client(command.client_id(), command.venue()) {
1167 client.execute_unsubscribe(&command);
1168 } else {
1169 log::error!(
1170 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
1171 command.client_id(),
1172 command.venue()
1173 );
1174 }
1175 }
1176
1177 result
1178 }
1179
1180 fn prepare_unsubscribe_members(
1181 &mut self,
1182 cmd: &UnsubscribeCommand,
1183 ) -> (Vec<UnsubscribeCommand>, anyhow::Result<()>) {
1184 let members = if matches!(
1185 cmd,
1186 UnsubscribeCommand::Quotes(_) | UnsubscribeCommand::Trades(_)
1187 ) {
1188 vec![cmd.clone()]
1189 } else {
1190 split_unsubscribe(cmd)
1191 };
1192
1193 let mut prepared = Vec::new();
1194 let mut result = Ok(());
1195
1196 for member in members {
1197 match self.prepare_unsubscribe_member(&member) {
1198 Ok(true) => prepared.push(member),
1199 Ok(false) => {}
1200 Err(e) => {
1201 log::error!(
1202 "Unsubscribe preparation failed: command={}, client={:?}, instrument_ids={:?}, bar_types={:?}: {e}",
1203 member.command_id(),
1204 member.client_id(),
1205 member.instrument_ids(),
1206 member.bar_types(),
1207 );
1208
1209 if result.is_ok() {
1210 result = Err(e);
1211 }
1212 }
1213 }
1214 }
1215
1216 (prepared, result)
1217 }
1218
1219 fn prepare_unsubscribe_member(&mut self, cmd: &UnsubscribeCommand) -> anyhow::Result<bool> {
1220 if matches!(
1221 cmd,
1222 UnsubscribeCommand::BookDeltas(_)
1223 | UnsubscribeCommand::BookDepth(_)
1224 | UnsubscribeCommand::BookSnapshots(_)
1225 ) && !self.release_book_subscription(cmd)
1226 {
1227 return Ok(false);
1228 }
1229
1230 match &cmd {
1231 UnsubscribeCommand::BookDeltas(cmd) if !self.unsubscribe_book_deltas(cmd) => {
1232 return Ok(false);
1233 }
1234 UnsubscribeCommand::BookDepth(cmd) if !self.unsubscribe_book_depth(cmd) => {
1235 return Ok(false);
1236 }
1237 UnsubscribeCommand::BookSnapshots(cmd) => {
1238 self.unsubscribe_book_snapshots(cmd);
1240 return Ok(false);
1241 }
1242 UnsubscribeCommand::Bars(cmd)
1243 if self.continuous_future_subscriptions.contains_key(
1244 &cmd.single_bar_type()
1245 .expect("member preparation receives one bar type")
1246 .standard(),
1247 ) =>
1248 {
1249 let topic = switchboard::get_bars_topic(
1251 cmd.single_bar_type()
1252 .expect("member preparation receives one bar type")
1253 .standard(),
1254 );
1255
1256 if msgbus::exact_subscriber_count_bars(topic) == 0 {
1257 self.unsubscribe_continuous_future_bars(cmd);
1258 }
1259
1260 return Ok(false);
1261 }
1262 UnsubscribeCommand::Bars(cmd) => {
1263 self.unsubscribe_bars(cmd);
1264 if cmd
1265 .single_bar_type()
1266 .expect("member preparation receives one bar type")
1267 .is_internally_aggregated()
1268 {
1269 return Ok(false);
1270 }
1271 }
1272 UnsubscribeCommand::OptionChain(cmd) => {
1273 self.unsubscribe_option_chain(cmd);
1274 return Ok(false);
1275 }
1276 UnsubscribeCommand::Quotes(cmd) if self.unsubscribe_quotes(cmd)? => {
1277 return Ok(false);
1278 }
1279 UnsubscribeCommand::Trades(cmd) if self.unsubscribe_trades(cmd) => {
1280 return Ok(false);
1281 }
1282 UnsubscribeCommand::Instrument(cmd)
1283 if cmd.instrument_ids.iter().any(InstrumentId::is_synthetic) =>
1284 {
1285 anyhow::bail!("Cannot unsubscribe from synthetic instrument `Instrument` data");
1286 }
1287 UnsubscribeCommand::InstrumentStatus(cmd)
1288 if cmd.instrument_ids.iter().any(InstrumentId::is_synthetic) =>
1289 {
1290 anyhow::bail!(
1291 "Cannot unsubscribe from synthetic instrument `InstrumentStatus` data"
1292 );
1293 }
1294 UnsubscribeCommand::InstrumentClose(cmd)
1295 if cmd.instrument_ids.iter().any(InstrumentId::is_synthetic) =>
1296 {
1297 anyhow::bail!(
1298 "Cannot unsubscribe from synthetic instrument `InstrumentClose` data"
1299 );
1300 }
1301 UnsubscribeCommand::OptionGreeks(cmd)
1302 if cmd.instrument_ids.iter().any(InstrumentId::is_synthetic) =>
1303 {
1304 anyhow::bail!("Cannot unsubscribe from synthetic instrument `OptionGreeks` data");
1305 }
1306 _ => {}
1307 }
1308
1309 Ok(true)
1310 }
1311
1312 fn subscribe_external(&mut self, client_id: ClientId, cmd: &SubscribeCommand) {
1313 let dispatch = match cmd {
1314 SubscribeCommand::OptionChain(command) => {
1315 self.retain_external_option_chain(client_id, command, cmd);
1316 cmd.clone()
1317 }
1318 _ => {
1319 let mut acquired = false;
1320 let mut fresh_ids = Vec::new();
1321 let mut fresh_bar_types = Vec::new();
1322
1323 for member in split_subscribe(cmd.clone()) {
1324 let key = SubscriptionKey::from_subscribe(&member);
1325
1326 if !self.subscriptions_external.retain(
1327 (client_id, key.clone()),
1328 member.command_id(),
1329 member,
1330 ) {
1331 continue;
1332 }
1333
1334 acquired = true;
1335
1336 match key {
1337 SubscriptionKey::Bars(bar_type) => fresh_bar_types.push(bar_type),
1338 SubscriptionKey::Quotes(id)
1339 | SubscriptionKey::Trades(id)
1340 | SubscriptionKey::Instrument(id)
1341 | SubscriptionKey::BookDeltas(id)
1342 | SubscriptionKey::BookDepth(id)
1343 | SubscriptionKey::MarkPrices(id)
1344 | SubscriptionKey::IndexPrices(id)
1345 | SubscriptionKey::FundingRates(id)
1346 | SubscriptionKey::InstrumentStatus(id)
1347 | SubscriptionKey::InstrumentClose(id)
1348 | SubscriptionKey::OptionGreeks(id)
1349 | SubscriptionKey::BookSnapshots(id, _) => fresh_ids.push(id),
1350 _ => {}
1351 }
1352 }
1353
1354 if !acquired {
1355 return;
1356 }
1357
1358 if cmd.bar_types().is_some() {
1359 cmd.with_bar_types(fresh_bar_types)
1360 .expect("bar command has types")
1361 } else {
1362 cmd.with_instrument_ids(fresh_ids)
1363 .unwrap_or_else(|| cmd.clone())
1364 }
1365 }
1366 };
1367
1368 register_external_streaming_type(&dispatch);
1369 publish_external_data_command(client_id, &dispatch);
1370
1371 if self.config.debug {
1372 log::debug!("Skipping subscribe command for external client {client_id}: {cmd:?}");
1373 }
1374 }
1375
1376 fn unsubscribe_external(&mut self, client_id: ClientId, cmd: &UnsubscribeCommand) {
1377 let mut releases = IndexMap::new();
1378
1379 for member in split_unsubscribe(cmd) {
1380 let key = (client_id, SubscriptionKey::from_unsubscribe(&member));
1381
1382 let (acquisition_id, command) = match self.subscriptions_external.release(&key) {
1383 SubscriptionRelease::Retained => continue,
1384 SubscriptionRelease::Final(subscribe) => (
1385 subscribe.command_id(),
1386 subscribe.into_unsubscribe(
1387 cmd.command_id(),
1388 cmd.ts_init(),
1389 cmd.correlation_id(),
1390 ),
1391 ),
1392 SubscriptionRelease::Untracked => (cmd.command_id(), member),
1393 };
1394
1395 self.subscriptions_external.remove(&key);
1396 releases
1397 .entry(acquisition_id)
1398 .or_insert_with(Vec::new)
1399 .push(command);
1400 }
1401
1402 for command in releases.into_values().flat_map(UnsubscribeCommand::group) {
1403 publish_external_data_command(client_id, &command);
1404 }
1405
1406 if self.config.debug {
1407 log::debug!("Skipping unsubscribe command for external client {client_id}: {cmd:?}");
1408 }
1409 }
1410
1411 fn subscribe_quotes(&mut self, cmd: &SubscribeQuotes) -> anyhow::Result<bool> {
1412 match self.quote_routing(&cmd.instrument_ids, cmd.params.as_ref())? {
1413 QuoteRouting::Client => Ok(false),
1414 QuoteRouting::Synthetic => {
1415 for id in &cmd.instrument_ids {
1416 self.subscribe_synthetic_quotes(*id);
1417 }
1418
1419 Ok(true)
1420 }
1421 QuoteRouting::Spread => {
1422 for id in &cmd.instrument_ids {
1423 let member = cmd.with_instrument_ids(*id);
1424 self.subscribe_spread_quotes(&member);
1425 }
1426
1427 Ok(true)
1428 }
1429 }
1430 }
1431
1432 fn unsubscribe_quotes(&mut self, cmd: &UnsubscribeQuotes) -> anyhow::Result<bool> {
1433 match self.quote_routing(&cmd.instrument_ids, cmd.params.as_ref())? {
1434 QuoteRouting::Client => Ok(false),
1435 QuoteRouting::Synthetic => {
1436 for id in &cmd.instrument_ids {
1437 self.unsubscribe_synthetic_quotes(*id);
1438 }
1439
1440 Ok(true)
1441 }
1442 QuoteRouting::Spread => {
1443 for id in &cmd.instrument_ids {
1444 let member = cmd.with_instrument_ids(*id);
1445 self.unsubscribe_spread_quotes(&member);
1446 }
1447
1448 Ok(true)
1449 }
1450 }
1451 }
1452
1453 fn subscribe_trades(&mut self, cmd: &SubscribeTrades) -> bool {
1454 if !cmd.instrument_ids[0].is_synthetic() {
1455 return false;
1456 }
1457
1458 for id in &cmd.instrument_ids {
1459 self.subscribe_synthetic_trades(*id);
1460 }
1461
1462 true
1463 }
1464
1465 fn unsubscribe_trades(&mut self, cmd: &UnsubscribeTrades) -> bool {
1466 if !cmd.instrument_ids[0].is_synthetic() {
1467 return false;
1468 }
1469
1470 for id in &cmd.instrument_ids {
1471 self.unsubscribe_synthetic_trades(*id);
1472 }
1473
1474 true
1475 }
1476
1477 fn quote_routing(
1478 &self,
1479 ids: &[InstrumentId],
1480 params: Option<&Params>,
1481 ) -> anyhow::Result<QuoteRouting> {
1482 quote_routing(ids, None, params, &self.cache.borrow())
1483 }
1484
1485 pub fn execute_request(&mut self, req: RequestCommand) -> anyhow::Result<()> {
1494 let request_id = *req.request_id();
1495 let result = self.execute_request_inner(req);
1496 if let Err(e) = &result {
1497 self.process_response_envelope(DataResponseEnvelope::terminal(
1498 request_id,
1499 Some(e.to_string()),
1500 ));
1501 }
1502
1503 result
1504 }
1505
1506 fn execute_request_inner(&mut self, req: RequestCommand) -> anyhow::Result<()> {
1507 Self::validate_request(&req)?;
1508
1509 if let Some(cid) = req.client_id()
1511 && self.is_external_client(*cid)
1512 {
1513 if self.config.debug {
1514 log::debug!("Skipping data request for external client {cid}: {req:?}");
1515 }
1516 return Ok(());
1517 }
1518
1519 if let RequestCommand::Join(join) = req {
1520 return self.handle_request_join(join);
1521 }
1522
1523 if has_continuous_future_params(request_params(&req)) {
1524 return self.execute_continuous_future_request(req);
1525 }
1526
1527 let request_id = *req.request_id();
1528 self.prepare_request_bar_aggregators(&req)?;
1529
1530 if has_time_range_pipeline_params(request_params(&req))
1531 && is_time_range_pipeline_variant(&req)
1532 {
1533 let result = self.execute_time_range_pipeline_request(req);
1534 if result.is_err() {
1535 self.cleanup_request_bar_aggregators(&request_id);
1536 }
1537 return result;
1538 }
1539
1540 #[cfg(feature = "streaming")]
1541 if self.catalogs_registered() && streaming::is_date_range_variant(&req) {
1542 let result = self.dispatch_date_range_request(req);
1543 if result.is_err() {
1544 self.cleanup_request_bar_aggregators(&request_id);
1545 }
1546 return result;
1547 }
1548
1549 let result = self.dispatch_request_to_client(req);
1550
1551 if result.is_err() {
1552 self.cleanup_request_bar_aggregators(&request_id);
1553 }
1554
1555 result.map(|_| ())
1556 }
1557
1558 fn validate_request(req: &RequestCommand) -> anyhow::Result<()> {
1559 let (instrument_ids, requires_single, limit) = match req {
1560 RequestCommand::Instrument(cmd) => (&cmd.instrument_ids, false, None),
1561 RequestCommand::BookSnapshot(cmd) => (&cmd.instrument_ids, true, None),
1562 RequestCommand::BookDeltas(cmd) => (&cmd.instrument_ids, true, None),
1563 RequestCommand::BookDepth(cmd) => (&cmd.instrument_ids, true, None),
1564 RequestCommand::Quotes(cmd) => (&cmd.instrument_ids, false, cmd.limit),
1565 RequestCommand::Trades(cmd) => (&cmd.instrument_ids, false, cmd.limit),
1566 RequestCommand::FundingRates(cmd) => (&cmd.instrument_ids, true, None),
1567 RequestCommand::Bars(cmd) => {
1568 ensure_routable_bar_types(&cmd.bar_types, cmd.client_id.is_some())?;
1569 cmd.single_bar_type()?;
1570 return Ok(());
1571 }
1572 _ => return Ok(()),
1573 };
1574
1575 ensure_routable_instrument_ids(instrument_ids, req.client_id().is_some())?;
1576 anyhow::ensure!(
1577 !requires_single || instrument_ids.len() == 1,
1578 "request requires one instrument ID for this data type, received {}",
1579 instrument_ids.len(),
1580 );
1581 anyhow::ensure!(
1582 instrument_ids.len() == 1 || limit.is_none(),
1583 "A record limit is unsupported for multi-instrument requests",
1584 );
1585 Ok(())
1586 }
1587
1588 pub(super) fn dispatch_request_to_client(
1589 &mut self,
1590 req: RequestCommand,
1591 ) -> anyhow::Result<ClientId> {
1592 self.inflight_requests
1593 .insert(*req.request_id(), req.clone());
1594 let client_id = req.client_id().copied();
1595 let venue = req.venue().copied();
1596 let Some(client) = self.get_client(client_id.as_ref(), venue.as_ref()) else {
1597 anyhow::bail!("Cannot handle request: no client found for {client_id:?} {venue:?}");
1598 };
1599 let resolved_client_id = client.client_id();
1600
1601 #[rustfmt::skip]
1602 let result = match req {
1603 RequestCommand::Data(req) => client.request_data(req),
1604 RequestCommand::Instrument(req) => client.request_instrument(req),
1605 RequestCommand::Instruments(req) => client.request_instruments(req),
1606 RequestCommand::BookSnapshot(req) => client.request_book_snapshot(req),
1607 RequestCommand::BookDeltas(req) => client.request_book_deltas(req),
1608 RequestCommand::BookDepth(req) => client.request_book_depth(req),
1609 RequestCommand::Quotes(req) => client.request_quotes(req),
1610 RequestCommand::Trades(req) => client.request_trades(req),
1611 RequestCommand::FundingRates(req) => client.request_funding_rates(req),
1612 RequestCommand::OptionChainReferencePrice(req) => client.request_option_chain_reference_price(req),
1613 RequestCommand::Bars(req) => client.request_bars(req),
1614 RequestCommand::Join(_) => anyhow::bail!("RequestJoin must be handled by handle_request_join"),
1615 };
1616
1617 result.map_err(|e| {
1618 let context = format!("Data client {resolved_client_id} request failed");
1619
1620 let context = if let Some(rejection) = e.downcast_ref::<IdentifierCountError>() {
1621 format!("{context}: {rejection}")
1622 } else {
1623 context
1624 };
1625
1626 e.context(context)
1627 })?;
1628
1629 Ok(resolved_client_id)
1630 }
1631
1632 pub fn process(&mut self, data: &dyn Any) {
1637 if let Some(response) = data.downcast_ref::<DataResponseEnvelope>() {
1638 self.response_envelope(response.clone());
1639 return;
1640 }
1641
1642 if let Some(failure) = data.downcast_ref::<DataRequestFailed>() {
1643 self.request_failed(failure.clone());
1644 return;
1645 }
1646
1647 self.data_count += 1;
1648
1649 if let Some(instrument) = data.downcast_ref::<InstrumentAny>() {
1650 self.handle_instrument(instrument);
1651 } else if let Some(funding_rate) = data.downcast_ref::<FundingRateUpdate>() {
1652 self.handle_funding_rate(*funding_rate);
1653 } else if let Some(option_greeks) = data.downcast_ref::<OptionGreeks>() {
1654 self.cache.borrow_mut().add_option_greeks(*option_greeks);
1655 self.feed_option_greeks_to_pre_bootstrap_chain(option_greeks);
1656 let topic = switchboard::get_option_greeks_topic(option_greeks.instrument_id);
1657 msgbus::publish_option_greeks(topic, option_greeks);
1658 self.drain_deferred_commands();
1659 } else if let Some(status) = data.downcast_ref::<InstrumentStatus>() {
1660 self.handle_instrument_status(*status);
1661 } else if let Some(custom) = data.downcast_ref::<CustomData>() {
1662 self.handle_custom_data(custom);
1663 } else {
1664 #[cfg(feature = "defi")]
1665 if let Some(response) = data.downcast_ref::<PoolSnapshotResponse>() {
1666 self.handle_pool_snapshot_response(response);
1667 return;
1668 }
1669
1670 log::error!("Cannot process data {data:?}, type is unrecognized");
1671 }
1672 }
1673
1674 #[allow(
1676 clippy::needless_pass_by_value,
1677 reason = "callers hand over ownership; the payload is only moved when a DeFi handler consumes it"
1678 )]
1679 pub fn process_data(&mut self, data: Data) {
1680 #[cfg(feature = "defi")]
1681 let data = match data {
1682 Data::Defi(defi) => {
1683 self.process_defi_data(*defi);
1684 return;
1685 }
1686 data => data,
1687 };
1688
1689 self.process_data_ref(DataRef::from(&data));
1690 }
1691
1692 pub fn process_data_ref(&mut self, data: DataRef<'_>) {
1697 #[cfg(feature = "defi")]
1698 let data = match data {
1699 DataRef::Defi(defi) => {
1700 self.process_defi_data(defi.clone());
1701 return;
1702 }
1703 data => data,
1704 };
1705
1706 self.data_count += 1;
1707
1708 match data {
1709 DataRef::Instrument(instrument) => self.handle_instrument(instrument),
1710 DataRef::BookDelta(delta) => self.handle_delta(*delta),
1711 DataRef::BookDeltas(deltas) => self.handle_deltas(deltas),
1712 DataRef::BookDepth(depth) => self.handle_depth(depth),
1713 DataRef::Quote(quote) => {
1714 self.handle_quote(*quote);
1715 self.drain_deferred_commands();
1716 }
1717 DataRef::Trade(trade) => self.handle_trade(*trade),
1718 DataRef::Bar(bar) => self.handle_bar(*bar),
1719 DataRef::MarkPrice(mark_price) => {
1720 self.handle_mark_price(*mark_price);
1721 self.drain_deferred_commands();
1722 }
1723 DataRef::IndexPrice(index_price) => {
1724 self.handle_index_price(*index_price);
1725 self.drain_deferred_commands();
1726 }
1727 DataRef::FundingRate(funding_rate) => {
1728 self.handle_funding_rate(*funding_rate);
1729 self.drain_deferred_commands();
1730 }
1731 DataRef::OptionGreeks(greeks) => {
1732 self.cache.borrow_mut().add_option_greeks(*greeks);
1733 self.feed_option_greeks_to_pre_bootstrap_chain(greeks);
1734 let topic = switchboard::get_option_greeks_topic(greeks.instrument_id);
1735 msgbus::publish_option_greeks(topic, greeks);
1736 self.drain_deferred_commands();
1737 }
1738 DataRef::InstrumentStatus(status) => {
1739 self.handle_instrument_status(*status);
1740 self.drain_deferred_commands();
1741 }
1742 DataRef::InstrumentClose(close) => self.handle_instrument_close(*close),
1743 DataRef::Custom(custom) => self.handle_custom_data(custom),
1744 #[cfg(feature = "defi")]
1745 DataRef::Defi(_) => unreachable!("handled before market data dispatch"),
1746 #[cfg(not(feature = "defi"))]
1747 #[allow(
1748 unreachable_patterns,
1749 reason = "DeFi variants can exist without this crate's defi feature"
1750 )]
1751 other => log_defi_data_dropped(other),
1752 }
1753 }
1754
1755 pub fn process_pipeline(&mut self, data: Data) {
1762 #[cfg(feature = "defi")]
1763 let data = match data {
1764 Data::Defi(defi) => {
1765 self.process_defi_data(*defi);
1766 return;
1767 }
1768 data => data,
1769 };
1770
1771 self.data_count += 1;
1772
1773 match data {
1774 Data::Instrument(instrument) => self.handle_instrument(&instrument),
1775 Data::BookDelta(delta) => self.handle_delta_pipeline(delta),
1776 Data::BookDeltas(deltas) => self.handle_deltas_pipeline(&deltas),
1777 Data::BookDepth(depth) => self.handle_depth_pipeline(&depth),
1778 Data::Quote(quote) => self.handle_quote_pipeline(quote),
1779 Data::Trade(trade) => self.handle_trade_pipeline(trade),
1780 Data::Bar(bar) => self.handle_bar_pipeline(bar),
1781 Data::MarkPrice(mark_price) => self.handle_mark_price_pipeline(mark_price),
1782 Data::IndexPrice(index_price) => self.handle_index_price_pipeline(index_price),
1783 Data::FundingRate(funding_rate) => {
1784 self.handle_funding_rate_pipeline(funding_rate);
1785 }
1786 Data::OptionGreeks(greeks) => self.handle_option_greeks_pipeline(greeks),
1787 Data::InstrumentStatus(status) => self.handle_instrument_status_pipeline(status),
1788 Data::InstrumentClose(close) => self.handle_instrument_close_pipeline(close),
1789 Data::Custom(custom) => self.handle_custom_data_pipeline(&custom),
1790 #[cfg(feature = "defi")]
1791 Data::Defi(_) => unreachable!("handled before market data dispatch"),
1792 #[cfg(not(feature = "defi"))]
1793 #[allow(
1794 unreachable_patterns,
1795 reason = "DeFi variants can exist without this crate's defi feature"
1796 )]
1797 other => log_defi_data_dropped(DataRef::from(&other)),
1798 }
1799 }
1800
1801 pub fn response(&mut self, mut resp: DataResponse) {
1806 resp.trim_to_bounds();
1807 let request = self.inflight_requests.remove(resp.correlation_id());
1808 #[cfg(feature = "streaming")]
1809 if request
1810 .as_ref()
1811 .and_then(request_params)
1812 .is_some_and(|params| params.get_bool(streaming::PARAM_UPDATE_CATALOG) == Some(true))
1813 {
1814 log::warn!(
1815 "Catalog write-back requires an explicit completed response envelope, request={}",
1816 resp.correlation_id()
1817 );
1818 }
1819
1820 #[cfg(not(feature = "streaming"))]
1821 let _ = request;
1822 let envelope = DataResponseEnvelope::new(resp, true, None);
1823 self.process_response_envelope(envelope);
1824 }
1825
1826 pub fn request_failed(&mut self, failure: DataRequestFailed) {
1828 log::error!(
1829 "Data request {} failed: {}",
1830 failure.request_id,
1831 failure.reason
1832 );
1833
1834 let envelope = if let Some(mut response) = failure.response {
1835 response.trim_to_bounds();
1836 DataResponseEnvelope::new(response, true, None)
1837 .with_correlation_id(failure.request_id)
1838 .with_failure(failure.reason)
1839 } else {
1840 DataResponseEnvelope::terminal(failure.request_id, Some(failure.reason))
1841 };
1842
1843 self.process_response_envelope(envelope);
1844 }
1845
1846 pub fn response_envelope(&mut self, mut envelope: DataResponseEnvelope) {
1848 if let Some(response) = envelope.response_mut() {
1849 response.trim_to_bounds();
1850 }
1851
1852 self.process_response_envelope(envelope);
1853 }
1854
1855 fn process_response_envelope(&mut self, envelope: DataResponseEnvelope) {
1856 let request = self.inflight_requests.remove(envelope.correlation_id());
1857 #[cfg(feature = "streaming")]
1858 if envelope.completed()
1859 && envelope.failure_reason().is_none()
1860 && let (Some(request), Some(response)) = (&request, envelope.response())
1861 {
1862 self.update_catalog_from_response(response, request);
1863 }
1864
1865 #[cfg(not(feature = "streaming"))]
1866 let _ = request;
1867
1868 let Some(envelope) = self.handle_request_pipeline_response(envelope) else {
1869 return;
1870 };
1871
1872 if self
1873 .request_pipeline_parent_request_id
1874 .contains_key(envelope.correlation_id())
1875 {
1876 self.process_response_envelope(envelope);
1877 return;
1878 }
1879
1880 if self.handle_continuous_future_child_response(&envelope)
1881 || self.handle_time_range_pipeline_child_response(&envelope)
1882 {
1883 return;
1884 }
1885
1886 let Some(resp) = envelope.response().cloned() else {
1887 self.finish_empty_response(&envelope);
1888 return;
1889 };
1890
1891 self.route_response_payload(resp, &envelope);
1892 }
1893
1894 fn finish_empty_response(&mut self, envelope: &DataResponseEnvelope) {
1895 if let Some(original_id) = self
1896 .parent_join_request_id
1897 .remove(envelope.correlation_id())
1898 {
1899 if let Some(original) = self.pending_join_requests.remove(&original_id) {
1900 for child_id in original.request_ids {
1901 msgbus::send_response_envelope(&DataResponseEnvelope::terminal(
1902 child_id,
1903 envelope.failure_reason().map(str::to_owned),
1904 ));
1905 }
1906 }
1907
1908 self.process_response_envelope(DataResponseEnvelope::terminal(
1909 original_id,
1910 envelope.failure_reason().map(str::to_owned),
1911 ));
1912 return;
1913 }
1914
1915 self.cleanup_request_bar_aggregators(envelope.correlation_id());
1916 msgbus::send_response_envelope(envelope);
1917 }
1918
1919 fn route_response_payload(&mut self, resp: DataResponse, envelope: &DataResponseEnvelope) {
1920 let resp = rebind_response_correlation(resp, *envelope.correlation_id());
1921 if log::log_enabled!(log::Level::Debug) {
1922 let correlation_id = resp.correlation_id();
1923 match resp.record_count() {
1924 Some(count) => log::debug!(
1925 "{RECV}{RES} {} correlation_id={correlation_id} records={count}",
1926 resp.kind(),
1927 ),
1928 None => log::debug!(
1929 "{RECV}{RES} {} correlation_id={correlation_id}",
1930 resp.kind(),
1931 ),
1932 }
1933 }
1934 log::trace!("{RECV}{RES} {resp:?}");
1935
1936 self.response_count += 1;
1937
1938 if self
1939 .parent_join_request_id
1940 .contains_key(resp.correlation_id())
1941 {
1942 self.finalize_request_join(resp, envelope.failure_reason().map(str::to_owned));
1943 return;
1944 }
1945
1946 if !self.handle_response_payload(&resp) {
1947 return;
1948 }
1949
1950 self.process_request_bar_aggregation_response(&resp, envelope.completed());
1951
1952 msgbus::send_response_envelope(envelope);
1953 }
1954
1955 fn handle_response_payload(&mut self, resp: &DataResponse) -> bool {
1956 let correlation_id = *resp.correlation_id();
1957
1958 match resp {
1959 DataResponse::Instrument(r) => {
1960 self.handle_instrument_response(r.data.clone());
1961 }
1962 DataResponse::Instruments(r) => {
1963 self.handle_instruments(&r.data);
1964 }
1965 DataResponse::Quotes(r) => {
1966 if !log_if_empty_response(
1967 &r.data,
1968 &format_args!("{:?}", r.instrument_ids),
1969 &correlation_id,
1970 ) {
1971 self.handle_quotes(&r.data);
1972 }
1973 }
1974 DataResponse::Trades(r) => {
1975 if !log_if_empty_response(
1976 &r.data,
1977 &format_args!("{:?}", r.instrument_ids),
1978 &correlation_id,
1979 ) {
1980 self.handle_trades(&r.data);
1981 }
1982 }
1983 DataResponse::FundingRates(r) => {
1984 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
1985 self.handle_funding_rates(&r.data);
1986 }
1987 }
1988 DataResponse::Bars(r) => {
1989 if !log_if_empty_response(&r.data, &r.bar_type, &correlation_id) {
1990 self.handle_bars(&r.data);
1991 }
1992 }
1993 DataResponse::Book(r) => self.handle_book_response(&r.data),
1994 DataResponse::BookDeltas(r) => {
1995 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
1996 self.handle_book_deltas_response(r);
1997 }
1998 }
1999 DataResponse::BookDepth(r) => {
2000 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
2001 self.handle_book_depth_response(r);
2002 }
2003 }
2004 DataResponse::OptionChainReferencePrice(r) => {
2005 self.process_request_bar_aggregation_response(resp, true);
2006 self.handle_option_chain_reference_price_response(&correlation_id, r);
2007 return false;
2008 }
2009 DataResponse::Data(_) => {}
2010 }
2011
2012 true
2013 }
2014
2015 pub fn new_request_pipeline(&mut self, parent: RequestCommand, n_components: usize) {
2017 let parent_id = *parent.request_id();
2018 self.request_pipeline_n_components
2019 .insert(parent_id, n_components);
2020 self.request_pipeline_parent_request
2021 .insert(parent_id, parent);
2022 self.request_pipeline_responses
2023 .insert(parent_id, Vec::with_capacity(n_components));
2024 }
2025
2026 pub fn register_request_pipeline_leg(&mut self, leg_id: UUID4, parent_id: UUID4) {
2032 let slots = self
2033 .request_pipeline_responses
2034 .get_mut(&parent_id)
2035 .expect("pipeline registered before its legs");
2036 let index = slots.len();
2037 slots.push(None);
2038 self.request_pipeline_parent_request_id
2039 .insert(leg_id, (parent_id, index));
2040 }
2041
2042 fn handle_request_pipeline_response(
2043 &mut self,
2044 envelope: DataResponseEnvelope,
2045 ) -> Option<DataResponseEnvelope> {
2046 let leg_id = *envelope.correlation_id();
2047
2048 let Some(&(parent_id, index)) = self.request_pipeline_parent_request_id.get(&leg_id) else {
2049 return Some(envelope);
2050 };
2051
2052 let completed = envelope.completed();
2053 let slots = self.request_pipeline_responses.get_mut(&parent_id)?;
2054 slots[index].get_or_insert_with(Vec::new).push(envelope);
2055
2056 if !completed {
2057 return None;
2058 }
2059
2060 self.request_pipeline_parent_request_id.remove(&leg_id);
2061 let expected = *self.request_pipeline_n_components.get(&parent_id)?;
2062
2063 if slots
2064 .iter()
2065 .flatten()
2066 .filter(|slot| slot.last().is_some_and(DataResponseEnvelope::completed))
2067 .count()
2068 < expected
2069 {
2070 return None;
2071 }
2072
2073 let slots = self.request_pipeline_responses.remove(&parent_id)?;
2074 self.request_pipeline_n_components.remove(&parent_id);
2075 let parent = self.request_pipeline_parent_request.remove(&parent_id);
2076 let failures = slots
2077 .iter()
2078 .flatten()
2079 .flatten()
2080 .filter_map(|value| value.failure_reason())
2081 .collect::<Vec<_>>();
2082 let failure_reason = (!failures.is_empty()).then(|| failures.join("; "));
2083 let legs = slots
2084 .into_iter()
2085 .flatten()
2086 .flatten()
2087 .filter_map(|value| value.response().cloned())
2088 .collect::<Vec<_>>();
2089
2090 if legs.is_empty() {
2091 return Some(DataResponseEnvelope::terminal(parent_id, failure_reason));
2092 }
2093
2094 let (parent_start, parent_end) = parent_request_window(parent.as_ref());
2095 if self.emit_partitioned_book_responses(parent_id, parent.as_ref(), &legs) {
2096 return Some(DataResponseEnvelope::terminal(parent_id, failure_reason));
2097 }
2098
2099 let rebuilt = rebuild_pipeline_response(parent_id, parent.as_ref(), legs);
2100
2101 let Some(mut rebuilt) = rebuilt else {
2102 let reason = failure_reason
2103 .unwrap_or_else(|| "Request pipeline could not rebuild its response".to_owned());
2104
2105 return Some(DataResponseEnvelope::terminal(parent_id, Some(reason)));
2106 };
2107
2108 if let DataResponse::BookDeltas(r) = &mut rebuilt {
2109 self.book_deltas_snapshot_replay(r);
2110 }
2111
2112 if !matches!(parent, Some(RequestCommand::Instrument(_)))
2114 && (parent_start.is_some() || parent_end.is_some())
2115 {
2116 rebuilt.trim_to_bounds();
2117 }
2118
2119 let result = DataResponseEnvelope::new(rebuilt, true, None);
2120 Some(if let Some(reason) = failure_reason {
2121 result.with_failure(reason)
2122 } else {
2123 result
2124 })
2125 }
2126
2127 fn emit_partitioned_book_responses(
2128 &mut self,
2129 parent_id: UUID4,
2130 parent: Option<&RequestCommand>,
2131 legs: &[DataResponse],
2132 ) -> bool {
2133 if !matches!(parent, Some(RequestCommand::BookDeltas(_))) {
2134 return false;
2135 }
2136
2137 let mut ids = legs.iter().filter_map(|r| match r {
2138 DataResponse::BookDeltas(r) => Some(r.instrument_id),
2139 _ => None,
2140 });
2141
2142 let Some(first) = ids.next() else {
2143 return false;
2144 };
2145
2146 if !ids.any(|id| !same_instrument(id, first)) {
2147 return false;
2148 }
2149
2150 let (start, end) = parent_request_window(parent);
2151
2152 for response in legs {
2153 let child_id = *response.correlation_id();
2154 let mut response = rebind_response_correlation(response.clone(), parent_id);
2155 if let DataResponse::BookDeltas(r) = &mut response {
2156 r.start = start.or(r.start);
2157 r.end = end.or(r.end);
2158 self.book_deltas_snapshot_replay(r);
2159 }
2160
2161 response.trim_to_bounds();
2162 self.process_response_envelope(DataResponseEnvelope::new(
2163 response,
2164 false,
2165 Some(child_id),
2166 ));
2167 }
2168
2169 true
2170 }
2171
2172 fn handle_request_join(&mut self, req: RequestJoin) -> anyhow::Result<()> {
2173 if has_time_range_pipeline_params(req.params.as_ref()) {
2174 return self.execute_time_range_pipeline_request(RequestCommand::Join(req));
2175 }
2176
2177 let now_ns = self.clock.borrow().timestamp_ns();
2178 let now_dt = now_ns.to_datetime_utc();
2179 let zero = jiff::Timestamp::UNIX_EPOCH;
2180 let start = req.start.unwrap_or(zero).min(now_dt);
2181 let end = req.end.unwrap_or(now_dt).min(now_dt);
2182 let dated = req.with_dates(Some(start), Some(end), now_ns);
2183
2184 let original_id = req.request_id;
2185 let dated_id = dated.request_id;
2186
2187 self.pending_join_requests.insert(original_id, req);
2188 self.parent_join_request_id.insert(dated_id, original_id);
2189
2190 let leg_ids: Vec<UUID4> = dated.request_ids.clone();
2191 self.new_request_pipeline(RequestCommand::Join(dated), leg_ids.len());
2192 for leg_id in leg_ids {
2193 self.register_request_pipeline_leg(leg_id, dated_id);
2194 }
2195
2196 Ok(())
2197 }
2198
2199 fn finalize_request_join(&mut self, resp: DataResponse, failure_reason: Option<String>) {
2200 let dated_id = *resp.correlation_id();
2201 let Some(original_id) = self.parent_join_request_id.remove(&dated_id) else {
2202 log::error!("parent_join_request_id missing for dated correlation {dated_id}");
2203 return;
2204 };
2205
2206 let Some(original) = self.pending_join_requests.remove(&original_id) else {
2207 log::error!("pending_join_requests missing for original {original_id}");
2208 return;
2209 };
2210
2211 for leg_request_id in &original.request_ids {
2212 let terminal = DataResponseEnvelope::terminal(*leg_request_id, failure_reason.clone());
2213 msgbus::send_response_envelope(&terminal);
2214 }
2215
2216 let final_resp = rebind_response_correlation(resp, original_id);
2222 let envelope = DataResponseEnvelope::new(final_resp, true, None);
2223 self.process_response_envelope(if let Some(reason) = failure_reason {
2224 envelope.with_failure(reason)
2225 } else {
2226 envelope
2227 });
2228 }
2229}
2230
2231fn register_external_streaming_type(cmd: &SubscribeCommand) {
2232 if let Some(payload_type) = streaming_payload_type(cmd) {
2233 msgbus::get_message_bus()
2234 .borrow_mut()
2235 .add_streaming_type(payload_type);
2236 }
2237}
2238
2239fn publish_external_data_command<T>(client_id: ClientId, command: &T)
2240where
2241 T: Any,
2242{
2243 let topic = format!("commands.data.{client_id}");
2244 msgbus::publish_any(topic.into(), command);
2245}
2246
2247#[rustfmt::skip]
2248fn streaming_payload_type(cmd: &SubscribeCommand) -> Option<BusPayloadType> {
2249 match cmd {
2250 SubscribeCommand::Data(cmd) => Some(BusPayloadType::Custom(Ustr::from(
2251 cmd.data_type.type_name(),
2252 ))),
2253 SubscribeCommand::Instrument(_) | SubscribeCommand::Instruments(_) => Some(BusPayloadType::Instrument),
2254 SubscribeCommand::BookDeltas(_) | SubscribeCommand::BookSnapshots(_) => Some(BusPayloadType::OrderBookDeltas),
2255 SubscribeCommand::BookDepth(_) => Some(BusPayloadType::OrderBookDepth),
2256 SubscribeCommand::Quotes(_) => Some(BusPayloadType::QuoteTick),
2257 SubscribeCommand::Trades(_) => Some(BusPayloadType::TradeTick),
2258 SubscribeCommand::Bars(_) => Some(BusPayloadType::Bar),
2259 SubscribeCommand::MarkPrices(_) => Some(BusPayloadType::MarkPriceUpdate),
2260 SubscribeCommand::IndexPrices(_) => Some(BusPayloadType::IndexPriceUpdate),
2261 SubscribeCommand::FundingRates(_) => Some(BusPayloadType::FundingRateUpdate),
2262 SubscribeCommand::OptionGreeks(_) => Some(BusPayloadType::OptionGreeks),
2263 SubscribeCommand::InstrumentStatus(_)
2264 | SubscribeCommand::InstrumentClose(_)
2265 | SubscribeCommand::OptionChain(_) => None,
2266 }
2267}
2268
2269#[cfg(not(feature = "defi"))]
2270fn log_defi_data_dropped(data: DataRef<'_>) {
2271 log::error!("Cannot process data {data:?}, nautilus-data built without its `defi` feature");
2272}