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 handlers;
43mod requests;
44mod time_range;
45
46use std::{
47 any::{Any, type_name},
48 cell::{Ref, RefCell},
49 collections::VecDeque,
50 fmt::{Debug, Display},
51 mem,
52 num::NonZeroUsize,
53 rc::Rc,
54 str::FromStr,
55};
56
57use ahash::{AHashMap, AHashSet};
58use anyhow::Context;
59pub use bar::BarAggregatorSubscription;
60use bar::{BarAggregatorKey, bar_aggregator_key};
61use book::{
62 BookDeltasKey, BookDeltasUnsubscribeResult, BookSnapshotInfo, BookSnapshotInfos,
63 BookSnapshotKey, BookSnapshotUnsubscribeResult, BookSnapshotter, BookUpdater,
64};
65pub(crate) use commands::{DeferredCommand, DeferredCommandQueue};
66use config::DataEngineConfig;
67use futures::future::join_all;
68use handlers::{
69 BAR_AGGREGATOR_PRIORITY, BarBarHandler, BarQuoteHandler, BarTradeHandler, SpreadQuoteHandler,
70};
71use indexmap::IndexMap;
72use nautilus_common::{
73 cache::Cache,
74 clock::Clock,
75 logging::{RECV, RES},
76 messages::data::{
77 BarsResponse, BookDeltasResponse, BookDepthResponse, CustomDataResponse, DataCommand,
78 DataResponse, FundingRatesResponse, OptionChainReferencePriceResponse, QuotesResponse,
79 RequestBars, RequestCommand, RequestJoin, RequestOptionChainReferencePrice, RequestQuotes,
80 RequestTrades, SubscribeBars, SubscribeBookDeltas, SubscribeBookDepth10,
81 SubscribeBookSnapshots, SubscribeCommand, SubscribeOptionChain, SubscribeOptionGreeks,
82 SubscribeQuotes, SubscribeTrades, TradesResponse, UnsubscribeBars, UnsubscribeBookDeltas,
83 UnsubscribeBookDepth10, UnsubscribeBookSnapshots, UnsubscribeCommand,
84 UnsubscribeInstrumentStatus, UnsubscribeOptionChain, UnsubscribeOptionGreeks,
85 UnsubscribeQuotes, UnsubscribeTrades, is_parent_subscription,
86 },
87 msgbus::{
88 self, BusPayloadType, ShareableMessageHandler, TypedHandler, TypedIntoHandler,
89 switchboard::{self, MessagingSwitchboard},
90 },
91 runner::get_data_cmd_sender,
92 timer::{TimeEvent, TimeEventCallback},
93};
94use nautilus_core::{
95 DurationNanos, Params, UUID4, UnixNanos, WeakCell,
96 correctness::{FAILED, check_key_in_map, check_key_not_in_map, check_predicate_true},
97 datetime::NANOSECONDS_IN_DAY,
98};
99#[cfg(feature = "defi")]
100use nautilus_model::defi::DefiData;
101use nautilus_model::{
102 data::{
103 Bar, BarType, CustomData, Data, DataRef, DataType, FundingRateUpdate, HasTsInit,
104 IndexPriceUpdate, InstrumentClose, InstrumentStatus, MarkPriceUpdate, OrderBookDelta,
105 OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick,
106 option_chain::{OptionGreeks, StrikeRange},
107 },
108 enums::{
109 AggregationSource, BarAggregation, BookType, InstrumentClass, MarketStatusAction,
110 PriceType, RecordFlag,
111 },
112 identifiers::{
113 ClientId, GENERIC_SPREAD_ID_SEPARATOR, InstrumentId, OptionSeriesId, Symbol, Venue,
114 },
115 instruments::{Instrument, InstrumentAny, SyntheticInstrument},
116 orderbook::OrderBook,
117 types::{Price, Quantity},
118};
119use requests::{
120 ContinuousFutureRequest, ContinuousFutureRequestState, ContinuousFutureSegment,
121 ContinuousFutureSource, RequestBarAggregation, continuous_future_parent_request_id,
122 continuous_future_request_from_bars, continuous_future_subscription_from_bars,
123 has_continuous_future_params, request_bar_aggregation_from_params, request_params,
124 response_params,
125};
126#[cfg(feature = "streaming")]
127use streaming::CatalogMap;
128use time_range::{
129 TimeRangePipelineState, has_time_range_pipeline_params, is_time_range_pipeline_variant,
130};
131use ustr::Ustr;
132
133#[cfg(feature = "defi")]
134#[allow(unused_imports)] use crate::defi::engine as _;
136#[cfg(feature = "defi")]
137use crate::engine::pool::PoolUpdater;
138use crate::{
139 aggregation::{
140 BarAggregator, RenkoBarAggregator, SpreadQuoteAggregator, TickBarAggregator,
141 TickImbalanceBarAggregator, TickRunsBarAggregator, TimeBarAggregator, ValueBarAggregator,
142 ValueImbalanceBarAggregator, ValueRunsBarAggregator, VolumeBarAggregator,
143 VolumeImbalanceBarAggregator, VolumeRunsBarAggregator,
144 },
145 client::DataClientAdapter,
146 option_chains::OptionChainManager,
147 subscription::{SubscriptionKey, SubscriptionRegistry, SubscriptionRelease},
148};
149
150const OPTION_CHAIN_REFERENCE_PRICE_TIMEOUT: DurationNanos = DurationNanos::from_secs(30);
151const OPTION_CHAIN_REFERENCE_PRICE_TIMEOUT_TIMER: &str = "option-chain-reference-price-timeout";
152
153#[derive(Debug)]
155pub struct DataEngine {
156 pub(crate) clock: Rc<RefCell<dyn Clock>>,
157 pub(crate) cache: Rc<RefCell<Cache>>,
158 pub(crate) external_clients: AHashSet<ClientId>,
159 subscriptions_external: SubscriptionRegistry<(ClientId, SubscriptionKey), SubscribeCommand>,
160 clients: IndexMap<ClientId, DataClientAdapter>,
161 default_client_id: Option<ClientId>,
162 routing_map: IndexMap<Venue, ClientId>,
163 book_intervals: AHashMap<NonZeroUsize, BookSnapshotInfos>,
164 book_snapshot_counts: IndexMap<BookSnapshotKey, usize>,
165 book_snapshot_sources: AHashMap<InstrumentId, BookSnapshotSource>,
166 book_deltas_counts: IndexMap<BookDeltasKey, usize>,
167 book_depth10_counts: IndexMap<BookDeltasKey, usize>,
168 book_updaters: AHashMap<InstrumentId, Rc<BookUpdater>>,
169 book_deltas_parent_expansions: AHashMap<InstrumentId, Vec<InstrumentId>>,
170 book_depth10_parent_expansions: AHashMap<InstrumentId, Vec<InstrumentId>>,
171 book_snapshotters: AHashMap<NonZeroUsize, Rc<BookSnapshotter>>,
172 bar_aggregators: IndexMap<BarAggregatorKey, Rc<RefCell<Box<dyn BarAggregator>>>>,
173 bar_aggregator_handlers: AHashMap<BarAggregatorKey, Vec<BarAggregatorSubscription>>,
174 subscriptions_bar_aggregation: AHashMap<BarType, BarAggregationSubscription>,
175 request_bar_aggregations: AHashMap<UUID4, RequestBarAggregation>,
176 request_pipeline_parent_request: AHashMap<UUID4, RequestCommand>,
177 request_pipeline_n_components: AHashMap<UUID4, usize>,
178 request_pipeline_parent_request_id: AHashMap<UUID4, UUID4>,
179 request_pipeline_responses: AHashMap<UUID4, Vec<DataResponse>>,
180 time_range_pipeline_requests: AHashMap<UUID4, TimeRangePipelineState>,
181 time_range_pipeline_parent_request_id: AHashMap<UUID4, UUID4>,
182 parent_join_request_id: AHashMap<UUID4, UUID4>,
183 pending_join_requests: AHashMap<UUID4, RequestJoin>,
184 continuous_future_requests: AHashMap<UUID4, ContinuousFutureRequestState>,
185 continuous_future_subscriptions: AHashMap<BarType, ContinuousFutureSubscriptionState>,
186 continuous_future_roller: Option<Rc<ContinuousFutureRoller>>,
187 spread_quote_states: AHashMap<InstrumentId, SpreadQuoteState>,
188 option_chain_managers: AHashMap<OptionSeriesId, Rc<RefCell<OptionChainManager>>>,
189 option_chain_instrument_index: AHashMap<InstrumentId, OptionSeriesId>,
190 deferred_cmd_queue: DeferredCommandQueue,
191 option_chain_bootstrapper: Option<Rc<OptionChainBootstrapper>>,
192 pending_option_chain_requests: AHashMap<UUID4, PendingOptionChainRequest>,
193 option_chain_greeks_bootstraps: AHashMap<OptionSeriesId, OptionChainGreeksBootstrap>,
194 synthetic_quote_feeds: AHashMap<InstrumentId, Vec<SyntheticInstrument>>,
195 synthetic_trade_feeds: AHashMap<InstrumentId, Vec<SyntheticInstrument>>,
196 subscribed_synthetic_quotes: AHashMap<InstrumentId, usize>,
197 subscribed_synthetic_trades: AHashMap<InstrumentId, usize>,
198 buffered_deltas_map: AHashMap<InstrumentId, OrderBookDeltas>,
199 deltas_frame: Vec<OrderBookDelta>,
200 command_count: u64,
201 data_count: u64,
202 request_count: u64,
203 response_count: u64,
204 pub(crate) msgbus_priority: u32,
205 pub(crate) config: DataEngineConfig,
206 #[cfg(feature = "streaming")]
207 catalogs: CatalogMap,
208 #[cfg(feature = "defi")]
209 pub(crate) pool_updaters: AHashMap<InstrumentId, Rc<PoolUpdater>>,
210 #[cfg(feature = "defi")]
211 pub(crate) pool_updaters_pending: AHashSet<InstrumentId>,
212 #[cfg(feature = "defi")]
213 pub(crate) pool_snapshot_pending: AHashSet<InstrumentId>,
214 #[cfg(feature = "defi")]
215 pub(crate) pool_event_buffers: AHashMap<InstrumentId, Vec<DefiData>>,
216}
217
218impl DataEngine {
219 #[must_use]
221 pub fn new(
222 clock: Rc<RefCell<dyn Clock>>,
223 cache: Rc<RefCell<Cache>>,
224 config: Option<DataEngineConfig>,
225 ) -> Self {
226 let config = config.unwrap_or_default();
227
228 let external_clients: AHashSet<ClientId> = config
229 .external_clients
230 .clone()
231 .unwrap_or_default()
232 .into_iter()
233 .collect();
234
235 Self {
236 clock,
237 cache,
238 external_clients,
239 subscriptions_external: SubscriptionRegistry::default(),
240 clients: IndexMap::new(),
241 default_client_id: None,
242 routing_map: IndexMap::new(),
243 book_intervals: AHashMap::new(),
244 book_snapshot_counts: IndexMap::new(),
245 book_snapshot_sources: AHashMap::new(),
246 book_deltas_counts: IndexMap::new(),
247 book_depth10_counts: IndexMap::new(),
248 book_updaters: AHashMap::new(),
249 book_deltas_parent_expansions: AHashMap::new(),
250 book_depth10_parent_expansions: AHashMap::new(),
251 book_snapshotters: AHashMap::new(),
252 bar_aggregators: IndexMap::new(),
253 bar_aggregator_handlers: AHashMap::new(),
254 subscriptions_bar_aggregation: AHashMap::new(),
255 request_bar_aggregations: AHashMap::new(),
256 request_pipeline_parent_request: AHashMap::new(),
257 request_pipeline_n_components: AHashMap::new(),
258 request_pipeline_parent_request_id: AHashMap::new(),
259 request_pipeline_responses: AHashMap::new(),
260 time_range_pipeline_requests: AHashMap::new(),
261 time_range_pipeline_parent_request_id: AHashMap::new(),
262 parent_join_request_id: AHashMap::new(),
263 pending_join_requests: AHashMap::new(),
264 continuous_future_requests: AHashMap::new(),
265 continuous_future_subscriptions: AHashMap::new(),
266 continuous_future_roller: None,
267 spread_quote_states: AHashMap::new(),
268 option_chain_managers: AHashMap::new(),
269 option_chain_instrument_index: AHashMap::new(),
270 deferred_cmd_queue: Rc::new(RefCell::new(VecDeque::new())),
271 option_chain_bootstrapper: None,
272 pending_option_chain_requests: AHashMap::new(),
273 option_chain_greeks_bootstraps: AHashMap::new(),
274 synthetic_quote_feeds: AHashMap::new(),
275 synthetic_trade_feeds: AHashMap::new(),
276 subscribed_synthetic_quotes: AHashMap::new(),
277 subscribed_synthetic_trades: AHashMap::new(),
278 buffered_deltas_map: AHashMap::new(),
279 deltas_frame: Vec::new(),
280 command_count: 0,
281 data_count: 0,
282 request_count: 0,
283 response_count: 0,
284 msgbus_priority: 10, config,
286 #[cfg(feature = "streaming")]
287 catalogs: CatalogMap::new(),
288 #[cfg(feature = "defi")]
289 pool_updaters: AHashMap::new(),
290 #[cfg(feature = "defi")]
291 pool_updaters_pending: AHashSet::new(),
292 #[cfg(feature = "defi")]
293 pool_snapshot_pending: AHashSet::new(),
294 #[cfg(feature = "defi")]
295 pool_event_buffers: AHashMap::new(),
296 }
297 }
298
299 pub fn register_msgbus_handlers(engine: &Rc<RefCell<Self>>) {
301 let weak = WeakCell::from(Rc::downgrade(engine));
302 engine.borrow_mut().continuous_future_roller =
303 Some(Rc::new(ContinuousFutureRoller::new(engine)));
304 engine.borrow_mut().option_chain_bootstrapper =
305 Some(Rc::new(OptionChainBootstrapper::new(engine)));
306
307 let weak1 = weak.clone();
308 msgbus::register_data_command_endpoint(
309 MessagingSwitchboard::data_engine_execute(),
310 TypedIntoHandler::from(move |cmd: DataCommand| {
311 if let Some(rc) = weak1.upgrade() {
312 rc.borrow_mut().execute(cmd);
313 }
314 }),
315 );
316
317 msgbus::register_data_command_endpoint(
318 MessagingSwitchboard::data_engine_queue_execute(),
319 TypedIntoHandler::from(move |cmd: DataCommand| {
320 get_data_cmd_sender().clone().execute(cmd);
321 }),
322 );
323
324 let weak2 = weak.clone();
326 msgbus::register_any(
327 MessagingSwitchboard::data_engine_process(),
328 ShareableMessageHandler::from_any(move |data: &dyn Any| {
329 if let Some(rc) = weak2.upgrade() {
330 rc.borrow_mut().process(data);
331 }
332 }),
333 );
334
335 let weak3 = weak.clone();
337 msgbus::register_data_endpoint(
338 MessagingSwitchboard::data_engine_process_data(),
339 TypedIntoHandler::from(move |data: Data| {
340 if let Some(rc) = weak3.upgrade() {
341 rc.borrow_mut().process_data(data);
342 }
343 }),
344 );
345
346 #[cfg(feature = "defi")]
348 {
349 let weak4 = weak.clone();
350 msgbus::register_defi_data_endpoint(
351 MessagingSwitchboard::data_engine_process_defi_data(),
352 TypedIntoHandler::from(move |data: DefiData| {
353 if let Some(rc) = weak4.upgrade() {
354 rc.borrow_mut().process_defi_data(data);
355 }
356 }),
357 );
358 }
359
360 let weak5 = weak;
361 msgbus::register_data_response_endpoint(
362 MessagingSwitchboard::data_engine_response(),
363 TypedIntoHandler::from(move |resp: DataResponse| {
364 if let Some(rc) = weak5.upgrade() {
365 rc.borrow_mut().response(resp);
366 }
367 }),
368 );
369 }
370
371 #[must_use]
373 pub const fn command_count(&self) -> u64 {
374 self.command_count
375 }
376
377 #[must_use]
379 pub const fn data_count(&self) -> u64 {
380 self.data_count
381 }
382
383 #[cfg(feature = "defi")]
384 pub(crate) const fn increment_data_count(&mut self) {
385 self.data_count += 1;
386 }
387
388 #[must_use]
390 pub const fn request_count(&self) -> u64 {
391 self.request_count
392 }
393
394 #[must_use]
396 pub const fn response_count(&self) -> u64 {
397 self.response_count
398 }
399
400 #[must_use]
402 pub fn has_option_chain_manager(&self, series_id: &OptionSeriesId) -> bool {
403 self.option_chain_managers.contains_key(series_id)
404 }
405
406 #[must_use]
408 pub fn pending_option_chain_request_count(&self) -> usize {
409 self.pending_option_chain_requests.len()
410 }
411
412 #[must_use]
414 pub fn request_pipeline_count(&self) -> usize {
415 self.request_pipeline_parent_request.len()
416 }
417
418 #[must_use]
420 pub fn time_range_pipeline_count(&self) -> usize {
421 self.time_range_pipeline_requests.len()
422 }
423
424 #[must_use]
426 pub fn pending_join_request_count(&self) -> usize {
427 self.pending_join_requests.len()
428 }
429
430 #[must_use]
432 pub fn get_clock(&self) -> Ref<'_, dyn Clock> {
433 self.clock.borrow()
434 }
435
436 #[must_use]
438 pub fn get_cache(&self) -> Ref<'_, Cache> {
439 self.cache.borrow()
440 }
441
442 #[must_use]
444 pub fn cache_rc(&self) -> Rc<RefCell<Cache>> {
445 Rc::clone(&self.cache)
446 }
447
448 pub fn register_client(&mut self, client: DataClientAdapter, routing: Option<Venue>) {
455 let client_id = client.client_id();
456
457 check_key_not_in_map(&client_id, &self.clients, "client_id", "clients").expect(FAILED);
458
459 if let Some(routing) = routing {
460 self.routing_map.insert(routing, client_id);
461 log::debug!("Set client {client_id} routing for {routing}");
462 }
463
464 if client.venue.is_none() && self.default_client_id.is_none() {
465 self.default_client_id = Some(client_id);
466 log::debug!("Registered client {client_id} for default routing");
467 }
468
469 self.clients.insert(client_id, client);
470 log::debug!("Registered client {client_id}");
471 }
472
473 pub fn deregister_client(&mut self, client_id: &ClientId) {
479 check_key_in_map(client_id, &self.clients, "client_id", "clients").expect(FAILED);
480
481 if self.default_client_id.as_ref() == Some(client_id) {
482 self.default_client_id = None;
483 }
484 self.clients.shift_remove(client_id);
485 log::info!("Deregistered client {client_id}");
486 }
487
488 pub fn register_default_client(&mut self, client: DataClientAdapter) {
500 check_predicate_true(
501 self.default_client_id.is_none(),
502 "default client already registered",
503 )
504 .expect(FAILED);
505
506 let client_id = client.client_id();
507 self.clients.insert(client_id, client);
508 self.default_client_id = Some(client_id);
509 log::debug!("Registered default client {client_id}");
510 }
511
512 pub fn set_default_client(&mut self, client_id: ClientId) -> anyhow::Result<()> {
519 if self.default_client_id.is_some_and(|id| id != client_id) {
520 anyhow::bail!("default client already registered");
521 }
522
523 if !self.clients.contains_key(&client_id) {
524 anyhow::bail!("No client registered with ID {client_id}");
525 }
526 self.default_client_id = Some(client_id);
527 log::debug!("Set client {client_id} as default");
528 Ok(())
529 }
530
531 pub fn register_venue_routing(
538 &mut self,
539 client_id: ClientId,
540 venue: Venue,
541 ) -> anyhow::Result<()> {
542 if !self.clients.contains_key(&client_id) {
543 anyhow::bail!("No client registered with ID {client_id}");
544 }
545
546 if let Some(existing_client_id) = self.routing_map.get(&venue)
547 && *existing_client_id != client_id
548 {
549 anyhow::bail!(
550 "Venue {venue} already routed to {existing_client_id}, \
551 cannot re-route to {client_id}"
552 );
553 }
554
555 self.routing_map.insert(venue, client_id);
556 log::debug!("Set client {client_id} routing for {venue}");
557 Ok(())
558 }
559
560 pub fn start(&mut self) {
562 for client in self.get_clients_mut() {
563 if let Err(e) = client.start() {
564 log::error!("{e}");
565 }
566 }
567
568 for ((_, request_id), aggregator) in &self.bar_aggregators {
569 let is_subscription = request_id.is_none() && !aggregator.borrow().is_historical();
572 if is_subscription && aggregator.borrow().bar_type().spec().is_time_aggregated() {
573 aggregator
574 .borrow_mut()
575 .start_timer(Some(aggregator.clone()));
576 }
577 }
578
579 for state in self.spread_quote_states.values() {
580 state
581 .aggregator
582 .borrow_mut()
583 .start_timer(Some(state.aggregator.clone()));
584 }
585 }
586
587 pub fn stop(&mut self) {
589 for client in self.get_clients_mut() {
590 if let Err(e) = client.stop() {
591 log::error!("{e}");
592 }
593 }
594
595 for aggregator in self.bar_aggregators.values() {
596 aggregator.borrow_mut().stop();
597 }
598
599 for state in self.spread_quote_states.values() {
600 state.aggregator.borrow_mut().stop_timer();
601 }
602 }
603
604 pub fn reset(&mut self) {
606 for client in self.get_clients_mut() {
607 match client.reset() {
608 Ok(()) => client.clear_subscription_state(),
609 Err(e) => log::error!("{e}"),
610 }
611 }
612
613 let keys: Vec<BarAggregatorKey> = self.bar_aggregators.keys().copied().collect();
614 for (bar_type, request_id) in keys {
615 if let Err(e) = self.stop_bar_aggregator(bar_type, request_id) {
616 log::error!("Error stopping bar aggregator during reset for {bar_type}: {e}");
617 }
618 }
619 self.subscriptions_bar_aggregation.clear();
620
621 self.request_bar_aggregations.clear();
622 self.request_pipeline_parent_request.clear();
623 self.request_pipeline_n_components.clear();
624 self.request_pipeline_parent_request_id.clear();
625 self.request_pipeline_responses.clear();
626 self.time_range_pipeline_requests.clear();
627 self.time_range_pipeline_parent_request_id.clear();
628 self.parent_join_request_id.clear();
629 self.pending_join_requests.clear();
630 self.continuous_future_requests.clear();
631
632 for state in self.continuous_future_subscriptions.values_mut() {
633 if let Some(name) = state.timer_name.take() {
634 self.clock.borrow_mut().cancel_timer(&name);
635 }
636 }
637 self.continuous_future_subscriptions.clear();
638
639 let spread_ids: Vec<InstrumentId> = self.spread_quote_states.keys().copied().collect();
640 for spread_id in spread_ids {
641 self.stop_spread_quote_aggregation(spread_id);
642 }
643
644 let managers: Vec<_> = self.option_chain_managers.drain().collect();
646 for (_, manager) in managers {
647 manager.borrow_mut().teardown(&self.clock);
648 }
649
650 self.option_chain_instrument_index.clear();
651 self.cancel_pending_option_chain_requests(None);
652 self.clear_option_chain_greeks_bootstraps();
653
654 let book_updaters: Vec<(InstrumentId, Rc<BookUpdater>)> =
659 self.book_updaters.drain().collect();
660 for (instrument_id, updater) in book_updaters {
661 let deltas_topic = switchboard::get_book_deltas_topic(instrument_id);
662 let depth_topic = switchboard::get_book_depth10_topic(instrument_id);
663 let deltas_handler: TypedHandler<OrderBookDeltas> = TypedHandler::new(updater.clone());
664 let depth_handler: TypedHandler<OrderBookDepth10> = TypedHandler::new(updater);
665 msgbus::unsubscribe_book_deltas(deltas_topic.into(), &deltas_handler);
666 msgbus::unsubscribe_book_depth10(depth_topic.into(), &depth_handler);
667 }
668
669 self.book_deltas_parent_expansions.clear();
670 self.book_depth10_parent_expansions.clear();
671
672 self.book_deltas_counts.clear();
673 self.book_depth10_counts.clear();
674 self.book_intervals.clear();
675 self.book_snapshot_counts.clear();
676 self.book_snapshot_sources.clear();
677 self.book_snapshotters.clear();
678 self.buffered_deltas_map.clear();
679 self.deltas_frame.clear();
680
681 self.synthetic_quote_feeds.clear();
682 self.synthetic_trade_feeds.clear();
683 self.subscribed_synthetic_quotes.clear();
684 self.subscribed_synthetic_trades.clear();
685 self.subscriptions_external.clear();
686
687 self.deferred_cmd_queue.borrow_mut().clear();
688
689 self.clock.borrow_mut().cancel_timers();
690
691 self.command_count = 0;
692 self.data_count = 0;
693 self.request_count = 0;
694 self.response_count = 0;
695 }
696
697 pub fn dispose(&mut self) {
699 for client in self.get_clients_mut() {
700 if let Err(e) = client.dispose() {
701 log::error!("{e}");
702 }
703 }
704
705 self.clear_option_chain_greeks_bootstraps();
706
707 let mut cf_sources = Vec::new();
710
711 for state in self.continuous_future_subscriptions.values_mut() {
712 if let Some(name) = state.timer_name.take() {
713 self.clock.borrow_mut().cancel_timer(&name);
714 }
715
716 if let Some(subscription) = state.active_source_subscription.take() {
717 cf_sources.push((state.target_bar_type, subscription));
718 }
719 }
720
721 for (target_bar_type, subscription) in cf_sources {
722 self.unsubscribe_continuous_future_source(target_bar_type, subscription);
723 }
724 self.continuous_future_subscriptions.clear();
725
726 let keys: Vec<BarAggregatorKey> = self.bar_aggregators.keys().copied().collect();
729 for (bar_type, request_id) in keys {
730 if let Err(e) = self.stop_bar_aggregator(bar_type, request_id) {
731 log::error!("Error stopping bar aggregator during dispose for {bar_type}: {e}");
732 }
733 }
734
735 self.subscriptions_external.clear();
736
737 self.clock.borrow_mut().cancel_timers();
738 }
739
740 pub async fn connect(&mut self) {
744 let futures: Vec<_> = self
745 .get_clients_mut()
746 .into_iter()
747 .map(DataClientAdapter::connect)
748 .collect();
749
750 let results = join_all(futures).await;
751
752 for error in results.into_iter().filter_map(Result::err) {
753 log::error!("Failed to connect data client: {error}");
754 }
755 }
756
757 pub async fn disconnect(&mut self) -> anyhow::Result<()> {
763 let futures: Vec<_> = self
764 .get_clients_mut()
765 .into_iter()
766 .map(DataClientAdapter::disconnect)
767 .collect();
768
769 let results = join_all(futures).await;
770 let errors: Vec<_> = results.into_iter().filter_map(Result::err).collect();
771
772 if errors.is_empty() {
773 Ok(())
774 } else {
775 let error_msgs: Vec<_> = errors.iter().map(ToString::to_string).collect();
776 anyhow::bail!(
777 "Failed to disconnect data clients: {}",
778 error_msgs.join("; ")
779 )
780 }
781 }
782
783 #[must_use]
785 pub fn check_connected(&self) -> bool {
786 self.get_clients()
787 .iter()
788 .all(|client| client.is_connected())
789 }
790
791 #[must_use]
793 pub fn check_disconnected(&self) -> bool {
794 self.get_clients()
795 .iter()
796 .all(|client| !client.is_connected())
797 }
798
799 #[must_use]
801 pub fn client_connection_status(&self) -> Vec<(ClientId, bool)> {
802 self.get_clients()
803 .into_iter()
804 .map(|client| (client.client_id(), client.is_connected()))
805 .collect()
806 }
807
808 #[must_use]
810 pub fn registered_clients(&self) -> Vec<ClientId> {
811 self.get_clients()
812 .into_iter()
813 .map(|client| client.client_id())
814 .collect()
815 }
816
817 pub(crate) fn collect_subscriptions<F, T>(&self, get_subs: F) -> Vec<T>
818 where
819 F: Fn(&DataClientAdapter) -> &AHashSet<T>,
820 T: Clone,
821 {
822 self.get_clients()
823 .into_iter()
824 .flat_map(get_subs)
825 .cloned()
826 .collect()
827 }
828
829 #[must_use]
830 pub fn get_clients(&self) -> Vec<&DataClientAdapter> {
831 self.clients.values().collect()
832 }
833
834 #[must_use]
835 pub fn get_clients_mut(&mut self) -> Vec<&mut DataClientAdapter> {
836 self.clients.values_mut().collect()
837 }
838
839 pub fn get_client(
840 &mut self,
841 client_id: Option<&ClientId>,
842 venue: Option<&Venue>,
843 ) -> Option<&mut DataClientAdapter> {
844 if let Some(client_id) = client_id {
845 return self.clients.get_mut(client_id);
846 }
847
848 if let Some(v) = venue
849 && let Some(client_id) = self.routing_map.get(v)
850 {
851 return self.clients.get_mut(client_id);
852 }
853
854 self.get_default_client()
855 }
856
857 fn get_command_client(
862 &mut self,
863 client_id: Option<&ClientId>,
864 venue: Option<&Venue>,
865 ) -> Option<&mut DataClientAdapter> {
866 let backtest_id = ClientId::new("BACKTEST");
867 if self.clients.contains_key(&backtest_id) {
868 return self.clients.get_mut(&backtest_id);
869 }
870 self.get_client(client_id, venue)
871 }
872
873 fn get_default_client(&mut self) -> Option<&mut DataClientAdapter> {
874 match self.default_client_id {
875 Some(id) => self.clients.get_mut(&id),
876 None => None,
877 }
878 }
879
880 #[must_use]
882 pub fn subscribed_custom_data(&self) -> Vec<DataType> {
883 self.collect_subscriptions(|client| &client.subscriptions_custom)
884 }
885
886 #[must_use]
888 pub fn subscribed_instruments(&self) -> Vec<InstrumentId> {
889 self.collect_subscriptions(|client| &client.subscriptions_instrument)
890 }
891
892 #[must_use]
894 pub fn subscribed_book_deltas(&self) -> Vec<InstrumentId> {
895 self.collect_subscriptions(|client| &client.subscriptions_book_deltas)
896 }
897
898 #[must_use]
900 pub fn subscribed_book_depth10(&self) -> Vec<InstrumentId> {
901 self.collect_subscriptions(|client| &client.subscriptions_book_depth10)
902 }
903
904 #[must_use]
906 pub fn subscribed_book_snapshots(&self) -> Vec<InstrumentId> {
907 self.book_snapshot_counts
908 .keys()
909 .map(|(instrument_id, _)| *instrument_id)
910 .collect()
911 }
912
913 #[must_use]
915 pub fn subscribed_quotes(&self) -> Vec<InstrumentId> {
916 self.collect_subscriptions(|client| &client.subscriptions_quotes)
917 }
918
919 #[must_use]
921 pub fn subscribed_synthetic_quotes(&self) -> Vec<InstrumentId> {
922 self.subscribed_synthetic_quotes.keys().copied().collect()
923 }
924
925 #[must_use]
927 pub fn subscribed_trades(&self) -> Vec<InstrumentId> {
928 self.collect_subscriptions(|client| &client.subscriptions_trades)
929 }
930
931 #[must_use]
933 pub fn subscribed_synthetic_trades(&self) -> Vec<InstrumentId> {
934 self.subscribed_synthetic_trades.keys().copied().collect()
935 }
936
937 #[must_use]
940 pub fn subscribed_bars(&self) -> Vec<BarType> {
941 let mut subscribed = self.collect_subscriptions(|client| &client.subscriptions_bars);
942 subscribed.extend(
943 self.bar_aggregators
944 .keys()
945 .filter(|(_, request_id)| request_id.is_none())
946 .map(|(bar_type, _)| *bar_type),
947 );
948 subscribed
949 }
950
951 #[must_use]
953 pub fn subscribed_mark_prices(&self) -> Vec<InstrumentId> {
954 self.collect_subscriptions(|client| &client.subscriptions_mark_prices)
955 }
956
957 #[must_use]
959 pub fn subscribed_index_prices(&self) -> Vec<InstrumentId> {
960 self.collect_subscriptions(|client| &client.subscriptions_index_prices)
961 }
962
963 #[must_use]
965 pub fn subscribed_funding_rates(&self) -> Vec<InstrumentId> {
966 self.collect_subscriptions(|client| &client.subscriptions_funding_rates)
967 }
968
969 #[must_use]
971 pub fn subscribed_instrument_status(&self) -> Vec<InstrumentId> {
972 self.collect_subscriptions(|client| &client.subscriptions_instrument_status)
973 }
974
975 #[must_use]
977 pub fn subscribed_instrument_close(&self) -> Vec<InstrumentId> {
978 self.collect_subscriptions(|client| &client.subscriptions_instrument_close)
979 }
980
981 pub fn execute(&mut self, cmd: DataCommand) {
990 match &cmd {
991 DataCommand::Subscribe(_) | DataCommand::Unsubscribe(_) => self.command_count += 1,
992 DataCommand::Request(_) => self.request_count += 1,
993 #[cfg(feature = "defi")]
994 DataCommand::DefiRequest(_) => self.request_count += 1,
995 #[cfg(feature = "defi")]
996 DataCommand::DefiSubscribe(_) | DataCommand::DefiUnsubscribe(_) => {
997 self.command_count += 1;
998 }
999 _ => {}
1000 }
1001
1002 if let Err(e) = match cmd {
1003 DataCommand::Subscribe(c) => self.execute_subscribe(c),
1004 DataCommand::Unsubscribe(c) => self.execute_unsubscribe(&c),
1005 DataCommand::Request(c) => self.execute_request(c),
1006 #[cfg(feature = "defi")]
1007 DataCommand::DefiRequest(c) => self.execute_defi_request(c),
1008 #[cfg(feature = "defi")]
1009 DataCommand::DefiSubscribe(c) => self.execute_defi_subscribe(c),
1010 #[cfg(feature = "defi")]
1011 DataCommand::DefiUnsubscribe(c) => self.execute_defi_unsubscribe(&c),
1012 _ => {
1013 log::warn!("Unhandled DataCommand variant");
1014 Ok(())
1015 }
1016 } {
1017 log::error!("{e}");
1018 }
1019 }
1020
1021 pub fn execute_subscribe(&mut self, cmd: SubscribeCommand) -> anyhow::Result<()> {
1028 if let Some(client_id) = cmd.client_id()
1029 && self.external_clients.contains(client_id)
1030 {
1031 if let SubscribeCommand::OptionChain(command) = &cmd {
1032 self.retain_external_option_chain(*client_id, command, &cmd);
1033 } else if !self.subscriptions_external.retain(
1034 (*client_id, SubscriptionKey::from_subscribe(&cmd)),
1035 cmd.command_id(),
1036 cmd.clone(),
1037 ) {
1038 return Ok(());
1039 }
1040
1041 register_external_streaming_type(&cmd);
1042 publish_external_data_command(*client_id, &cmd);
1043
1044 if self.config.debug {
1045 log::debug!("Skipping subscribe command for external client {client_id}: {cmd:?}");
1046 }
1047
1048 return Ok(());
1049 }
1050
1051 match &cmd {
1053 SubscribeCommand::BookDeltas(book_cmd) => {
1054 if !self.subscribe_book_deltas(book_cmd)? && self.client_subscription_active(&cmd) {
1055 return Ok(());
1056 }
1057 }
1058 SubscribeCommand::BookDepth10(book_cmd) => {
1059 if !self.subscribe_book_depth10(book_cmd)? && self.client_subscription_active(&cmd)
1060 {
1061 return Ok(());
1062 }
1063 }
1064 SubscribeCommand::BookSnapshots(cmd) => {
1065 return self.subscribe_book_snapshots(cmd);
1067 }
1068 SubscribeCommand::Bars(cmd) if has_continuous_future_params(cmd.params.as_ref()) => {
1069 return self.subscribe_continuous_future_bars(cmd);
1070 }
1071 SubscribeCommand::Bars(cmd) => {
1072 self.subscribe_bars(cmd)?;
1073 if cmd.bar_type.is_internally_aggregated() {
1074 return Ok(());
1075 }
1076 }
1077 SubscribeCommand::OptionChain(cmd) => {
1078 self.subscribe_option_chain(cmd);
1079 return Ok(());
1080 }
1081 SubscribeCommand::Quotes(cmd) if cmd.instrument_id.is_synthetic() => {
1082 self.subscribe_synthetic_quotes(cmd.instrument_id);
1083 return Ok(());
1084 }
1085 SubscribeCommand::Quotes(cmd)
1086 if self.is_spread_quote_command(cmd.instrument_id, cmd.params.as_ref()) =>
1087 {
1088 self.subscribe_spread_quotes(cmd);
1089 return Ok(());
1090 }
1091 SubscribeCommand::Trades(cmd) if cmd.instrument_id.is_synthetic() => {
1092 self.subscribe_synthetic_trades(cmd.instrument_id);
1093 return Ok(());
1094 }
1095 SubscribeCommand::Instrument(cmd) if cmd.instrument_id.is_synthetic() => {
1096 anyhow::bail!("Cannot subscribe for synthetic instrument `Instrument` data");
1097 }
1098 SubscribeCommand::InstrumentStatus(cmd) if cmd.instrument_id.is_synthetic() => {
1099 anyhow::bail!("Cannot subscribe for synthetic instrument `InstrumentStatus` data");
1100 }
1101 SubscribeCommand::InstrumentClose(cmd) if cmd.instrument_id.is_synthetic() => {
1102 anyhow::bail!("Cannot subscribe for synthetic instrument `InstrumentClose` data");
1103 }
1104 SubscribeCommand::OptionGreeks(cmd) if cmd.instrument_id.is_synthetic() => {
1105 anyhow::bail!("Cannot subscribe for synthetic instrument `OptionGreeks` data");
1106 }
1107 _ => {} }
1109
1110 let retained = cmd.clone();
1111
1112 let retain_on_failure = !matches!(
1114 &cmd,
1115 SubscribeCommand::BookDeltas(_) | SubscribeCommand::BookDepth10(_)
1116 );
1117
1118 #[cfg(feature = "streaming")]
1119 let cmd = self.subscribe_command_with_prefilled_start_ns(cmd)?;
1120
1121 if let Some(client) = self.get_command_client(cmd.client_id(), cmd.venue()) {
1122 client.execute_subscribe_with_retained(cmd, retained, retain_on_failure);
1123 } else {
1124 log::error!(
1125 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
1126 cmd.client_id(),
1127 cmd.venue(),
1128 );
1129 }
1130
1131 Ok(())
1132 }
1133
1134 fn client_subscription_active(&mut self, cmd: &SubscribeCommand) -> bool {
1135 self.get_command_client(cmd.client_id(), cmd.venue())
1136 .is_some_and(|client| client.has_active_subscription(cmd))
1137 }
1138
1139 fn retain_external_option_chain(
1140 &mut self,
1141 client_id: ClientId,
1142 command: &SubscribeOptionChain,
1143 subscribe: &SubscribeCommand,
1144 ) {
1145 let owner_id = command.correlation_id.unwrap_or(command.command_id);
1146 let current_key = (client_id, SubscriptionKey::OptionChain(command.series_id));
1147 let previous_key = self
1148 .subscriptions_external
1149 .iter()
1150 .find_map(|(key, active)| {
1151 (matches!(
1152 &key.1,
1153 SubscriptionKey::OptionChain(series_id) if *series_id == command.series_id
1154 ) && active.acquisitions.contains(&owner_id))
1155 .then(|| key.clone())
1156 });
1157
1158 if let Some(previous_key) = previous_key
1159 && previous_key != current_key
1160 {
1161 let release = {
1162 let active = self
1163 .subscriptions_external
1164 .get_mut(&previous_key)
1165 .expect("external option chain owner was present");
1166 active.acquisitions.remove(&owner_id);
1167 active.owners = active.owners.saturating_sub(1);
1168 active.owners == 0
1169 };
1170
1171 if release {
1172 let active = self
1173 .subscriptions_external
1174 .remove(&previous_key)
1175 .expect("external option chain was present for final release");
1176 let unsubscribe =
1177 active
1178 .command
1179 .into_unsubscribe(UUID4::new(), command.ts_init, Some(owner_id));
1180 publish_external_data_command(previous_key.0, &unsubscribe);
1181 }
1182 }
1183
1184 self.subscriptions_external
1185 .retain(current_key.clone(), owner_id, subscribe.clone());
1186 self.subscriptions_external
1187 .get_mut(¤t_key)
1188 .expect("external option chain was retained")
1189 .command = subscribe.clone();
1190 }
1191
1192 pub fn execute_unsubscribe(&mut self, cmd: &UnsubscribeCommand) -> anyhow::Result<()> {
1198 if let Some(client_id) = cmd.client_id()
1199 && self.external_clients.contains(client_id)
1200 {
1201 let key = (*client_id, SubscriptionKey::from_unsubscribe(cmd));
1202 let command = match self.subscriptions_external.release(&key) {
1203 SubscriptionRelease::Retained => return Ok(()),
1204 SubscriptionRelease::Final(subscribe) => subscribe.into_unsubscribe(
1205 cmd.command_id(),
1206 cmd.ts_init(),
1207 cmd.correlation_id(),
1208 ),
1209 SubscriptionRelease::Untracked => cmd.clone(),
1210 };
1211 self.subscriptions_external.remove(&key);
1212 publish_external_data_command(*client_id, &command);
1213
1214 if self.config.debug {
1215 log::debug!(
1216 "Skipping unsubscribe command for external client {client_id}: {command:?}",
1217 );
1218 }
1219 return Ok(());
1220 }
1221
1222 match &cmd {
1223 UnsubscribeCommand::BookDeltas(cmd) if !self.unsubscribe_book_deltas(cmd) => {
1224 return Ok(());
1225 }
1226 UnsubscribeCommand::BookDepth10(cmd) if !self.unsubscribe_book_depth10(cmd) => {
1227 return Ok(());
1228 }
1229 UnsubscribeCommand::BookSnapshots(cmd) => {
1230 self.unsubscribe_book_snapshots(cmd);
1232 return Ok(());
1233 }
1234 UnsubscribeCommand::Bars(cmd)
1235 if self
1236 .continuous_future_subscriptions
1237 .contains_key(&cmd.bar_type.standard()) =>
1238 {
1239 let topic = switchboard::get_bars_topic(cmd.bar_type.standard());
1241 if msgbus::exact_subscriber_count_bars(topic) == 0 {
1242 self.unsubscribe_continuous_future_bars(cmd);
1243 }
1244 return Ok(());
1245 }
1246 UnsubscribeCommand::Bars(cmd) => {
1247 self.unsubscribe_bars(cmd);
1248 if cmd.bar_type.is_internally_aggregated() {
1249 return Ok(());
1250 }
1251 }
1252 UnsubscribeCommand::OptionChain(cmd) => {
1253 self.unsubscribe_option_chain(cmd);
1254 return Ok(());
1255 }
1256 UnsubscribeCommand::Quotes(cmd) if cmd.instrument_id.is_synthetic() => {
1257 self.unsubscribe_synthetic_quotes(cmd.instrument_id);
1258 return Ok(());
1259 }
1260 UnsubscribeCommand::Quotes(cmd)
1261 if self.is_spread_quote_command(cmd.instrument_id, cmd.params.as_ref()) =>
1262 {
1263 self.unsubscribe_spread_quotes(cmd);
1264 return Ok(());
1265 }
1266 UnsubscribeCommand::Trades(cmd) if cmd.instrument_id.is_synthetic() => {
1267 self.unsubscribe_synthetic_trades(cmd.instrument_id);
1268 return Ok(());
1269 }
1270 UnsubscribeCommand::Instrument(cmd) if cmd.instrument_id.is_synthetic() => {
1271 anyhow::bail!("Cannot unsubscribe from synthetic instrument `Instrument` data");
1272 }
1273 UnsubscribeCommand::InstrumentStatus(cmd) if cmd.instrument_id.is_synthetic() => {
1274 anyhow::bail!(
1275 "Cannot unsubscribe from synthetic instrument `InstrumentStatus` data"
1276 );
1277 }
1278 UnsubscribeCommand::InstrumentClose(cmd) if cmd.instrument_id.is_synthetic() => {
1279 anyhow::bail!(
1280 "Cannot unsubscribe from synthetic instrument `InstrumentClose` data"
1281 );
1282 }
1283 UnsubscribeCommand::OptionGreeks(cmd) if cmd.instrument_id.is_synthetic() => {
1284 anyhow::bail!("Cannot unsubscribe from synthetic instrument `OptionGreeks` data");
1285 }
1286 _ => {}
1287 }
1288
1289 if let Some(client) = self.get_command_client(cmd.client_id(), cmd.venue()) {
1290 client.execute_unsubscribe(cmd);
1291 } else {
1292 log::error!(
1293 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
1294 cmd.client_id(),
1295 cmd.venue(),
1296 );
1297 }
1298
1299 Ok(())
1300 }
1301
1302 pub fn execute_request(&mut self, req: RequestCommand) -> anyhow::Result<()> {
1309 if let Some(cid) = req.client_id()
1311 && self.external_clients.contains(cid)
1312 {
1313 if self.config.debug {
1314 log::debug!("Skipping data request for external client {cid}: {req:?}");
1315 }
1316 return Ok(());
1317 }
1318
1319 if let RequestCommand::Join(join) = req {
1320 return self.handle_request_join(join);
1321 }
1322
1323 if has_continuous_future_params(request_params(&req)) {
1324 return self.execute_continuous_future_request(req);
1325 }
1326
1327 let request_id = *req.request_id();
1328 self.prepare_request_bar_aggregators(&req)?;
1329
1330 if has_time_range_pipeline_params(request_params(&req))
1331 && is_time_range_pipeline_variant(&req)
1332 {
1333 let result = self.execute_time_range_pipeline_request(req);
1334 if result.is_err() {
1335 self.cleanup_request_bar_aggregators(&request_id);
1336 }
1337 return result;
1338 }
1339
1340 #[cfg(feature = "streaming")]
1341 if self.catalogs_registered() && streaming::is_date_range_variant(&req) {
1342 let result = self.dispatch_date_range_request(req);
1343 if result.is_err() {
1344 self.cleanup_request_bar_aggregators(&request_id);
1345 }
1346 return result;
1347 }
1348
1349 let result = self.dispatch_request_to_client(req);
1350
1351 if result.is_err() {
1352 self.cleanup_request_bar_aggregators(&request_id);
1353 }
1354
1355 result.map(|_| ())
1356 }
1357
1358 pub(super) fn dispatch_request_to_client(
1359 &mut self,
1360 req: RequestCommand,
1361 ) -> anyhow::Result<ClientId> {
1362 let client_id = req.client_id().copied();
1363 let venue = req.venue().copied();
1364 let Some(client) = self.get_client(client_id.as_ref(), venue.as_ref()) else {
1365 anyhow::bail!("Cannot handle request: no client found for {client_id:?} {venue:?}");
1366 };
1367 let resolved_client_id = client.client_id();
1368
1369 #[rustfmt::skip]
1370 match req {
1371 RequestCommand::Data(req) => client.request_data(req),
1372 RequestCommand::Instrument(req) => client.request_instrument(req),
1373 RequestCommand::Instruments(req) => client.request_instruments(req),
1374 RequestCommand::BookSnapshot(req) => client.request_book_snapshot(req),
1375 RequestCommand::BookDeltas(req) => client.request_book_deltas(req),
1376 RequestCommand::BookDepth(req) => client.request_book_depth(req),
1377 RequestCommand::Quotes(req) => client.request_quotes(req),
1378 RequestCommand::Trades(req) => client.request_trades(req),
1379 RequestCommand::FundingRates(req) => client.request_funding_rates(req),
1380 RequestCommand::OptionChainReferencePrice(req) => client.request_option_chain_reference_price(req),
1381 RequestCommand::Bars(req) => client.request_bars(req),
1382 RequestCommand::Join(_) => anyhow::bail!("RequestJoin must be handled by handle_request_join"),
1383 }?;
1384
1385 Ok(resolved_client_id)
1386 }
1387
1388 fn execute_continuous_future_request(&mut self, req: RequestCommand) -> anyhow::Result<()> {
1389 let RequestCommand::Bars(parent) = req else {
1390 anyhow::bail!("Continuous future requests require `RequestBars`");
1391 };
1392 let request_id = parent.request_id;
1393 let Some(continuous_request) = continuous_future_request_from_bars(&parent)? else {
1394 return Ok(());
1395 };
1396
1397 self.ensure_continuous_future_target_instrument(&continuous_request);
1398 self.prepare_request_bar_aggregators_from_state(
1399 request_id,
1400 &continuous_request.request_bar_aggregation,
1401 )?;
1402
1403 let response_client_id = match self.resolve_request_client_id(
1404 parent.client_id.as_ref(),
1405 Some(&continuous_request.primary_bar_type.instrument_id().venue),
1406 ) {
1407 Ok(client_id) => client_id,
1408 Err(e) => {
1409 self.cleanup_request_bar_aggregators(&request_id);
1410 return Err(e);
1411 }
1412 };
1413 let (cursor_ns, end_ns) = match self.bound_continuous_future_dates(&parent) {
1414 Ok(bounds) => bounds,
1415 Err(e) => {
1416 self.cleanup_request_bar_aggregators(&request_id);
1417 return Err(e);
1418 }
1419 };
1420
1421 self.continuous_future_requests.insert(
1422 request_id,
1423 ContinuousFutureRequestState {
1424 parent,
1425 request: continuous_request,
1426 start_ns: cursor_ns,
1427 cursor_ns,
1428 end_ns,
1429 response_client_id,
1430 data_count: 0,
1431 },
1432 );
1433
1434 if let Err(e) = self.dispatch_next_continuous_future_segment(request_id) {
1435 self.continuous_future_requests.remove(&request_id);
1436 self.cleanup_request_bar_aggregators(&request_id);
1437 return Err(e);
1438 }
1439
1440 Ok(())
1441 }
1442
1443 fn resolve_request_client_id(
1444 &mut self,
1445 client_id: Option<&ClientId>,
1446 venue: Option<&Venue>,
1447 ) -> anyhow::Result<ClientId> {
1448 self.get_client(client_id, venue)
1449 .map(|client| client.client_id())
1450 .ok_or_else(|| {
1451 anyhow::anyhow!(
1452 "Cannot handle request: no client found for {client_id:?} {venue:?}"
1453 )
1454 })
1455 }
1456
1457 fn bound_continuous_future_dates(
1458 &self,
1459 request: &RequestBars,
1460 ) -> anyhow::Result<(UnixNanos, UnixNanos)> {
1461 let now = self.clock.borrow().timestamp_ns();
1462 let start = request
1463 .start
1464 .map(datetime_to_unix_nanos)
1465 .transpose()?
1466 .unwrap_or_default();
1467 let end = request
1468 .end
1469 .map(datetime_to_unix_nanos)
1470 .transpose()?
1471 .unwrap_or(now);
1472
1473 Ok((start.min(now), end.min(now)))
1474 }
1475
1476 fn ensure_continuous_future_target_instrument(&self, request: &ContinuousFutureRequest) {
1477 let target_id = request.primary_bar_type.instrument_id();
1478 if self.cache.borrow().instrument(&target_id).is_some() {
1479 return;
1480 }
1481
1482 let segment_id = request.first_segment_instrument_id();
1483 let segment_instrument = self.cache.borrow().instrument(&segment_id).cloned();
1484 let Some(segment_instrument) = segment_instrument else {
1485 log::warn!(
1486 "Cannot synthesize continuous future instrument {target_id}: first segment {segment_id} not in cache"
1487 );
1488 return;
1489 };
1490
1491 let InstrumentAny::FuturesContract(mut target) = segment_instrument else {
1492 log::warn!(
1493 "Cannot synthesize continuous future instrument {target_id}: segment {segment_id} is not a FuturesContract",
1494 );
1495 return;
1496 };
1497
1498 target.id = target_id;
1499 target.raw_symbol = target_id.symbol;
1500 target.activation_ns = UnixNanos::default();
1501 target.expiration_ns = UnixNanos::default();
1502
1503 if let Err(e) = self
1504 .cache
1505 .borrow_mut()
1506 .add_instrument(InstrumentAny::FuturesContract(target))
1507 {
1508 log_error_on_cache_insert(&e);
1509 }
1510 }
1511
1512 fn prepare_request_bar_aggregators_from_state(
1513 &mut self,
1514 request_id: UUID4,
1515 state: &RequestBarAggregation,
1516 ) -> anyhow::Result<()> {
1517 if !self.can_start_request_bar_aggregators(request_id, state) {
1518 anyhow::bail!(
1519 "Cannot request aggregated bars: one of the aggregators in `bar_types` is already running"
1520 );
1521 }
1522
1523 self.request_bar_aggregations
1524 .insert(request_id, state.clone());
1525
1526 if let Err(e) = self.init_request_bar_aggregators(request_id, state) {
1527 self.cleanup_request_bar_aggregators(&request_id);
1528 return Err(e);
1529 }
1530
1531 Ok(())
1532 }
1533
1534 fn dispatch_next_continuous_future_segment(&mut self, request_id: UUID4) -> anyhow::Result<()> {
1535 let Some(state) = self.continuous_future_requests.get(&request_id).cloned() else {
1536 anyhow::bail!("No active continuous future request for {request_id}");
1537 };
1538
1539 let Some(segment) = state
1540 .request
1541 .next_segment(state.cursor_ns.as_u64(), state.end_ns.as_u64())
1542 else {
1543 self.emit_empty_continuous_future_response(request_id);
1544 return Ok(());
1545 };
1546
1547 self.apply_continuous_future_adjustment(request_id, &state.request, segment.index)?;
1548 let child = self.build_continuous_future_child_request(request_id, &state, segment);
1549 if let Some(active) = self.continuous_future_requests.get_mut(&request_id) {
1550 active.cursor_ns = UnixNanos::from(segment.end_ns.saturating_add(1));
1551 }
1552
1553 self.dispatch_request_to_client(child).map(|_| ())
1554 }
1555
1556 fn apply_continuous_future_adjustment(
1557 &self,
1558 request_id: UUID4,
1559 request: &ContinuousFutureRequest,
1560 segment_index: usize,
1561 ) -> anyhow::Result<()> {
1562 let adjustment = request.adjustment_for_segment(segment_index);
1563 let key = bar_aggregator_key(request.primary_bar_type, Some(request_id));
1564 let aggregator = self.bar_aggregators.get(&key).ok_or_else(|| {
1565 anyhow::anyhow!("No aggregator for continuous future request {request_id}")
1566 })?;
1567 aggregator
1568 .borrow_mut()
1569 .set_adjustment(adjustment, request.adjustment_mode);
1570
1571 Ok(())
1572 }
1573
1574 fn build_continuous_future_child_request(
1575 &self,
1576 request_id: UUID4,
1577 state: &ContinuousFutureRequestState,
1578 segment: ContinuousFutureSegment,
1579 ) -> RequestCommand {
1580 let source = state.request.source_for_segment(segment.instrument_id);
1581 let start = Some(UnixNanos::from(segment.start_ns).to_datetime_utc());
1582 let end = Some(UnixNanos::from(segment.end_ns).to_datetime_utc());
1583 let child_params = Some(
1584 state
1585 .request
1586 .child_params(state.parent.params.as_ref(), request_id),
1587 );
1588 let child_request_id = UUID4::new();
1589 let ts_init = self.clock.borrow().timestamp_ns();
1590
1591 match source {
1592 ContinuousFutureSource::Bars(bar_type) => RequestCommand::Bars(RequestBars::new(
1593 bar_type,
1594 start,
1595 end,
1596 state.parent.limit,
1597 state.parent.client_id,
1598 child_request_id,
1599 ts_init,
1600 child_params,
1601 )),
1602 ContinuousFutureSource::Trades => RequestCommand::Trades(RequestTrades::new(
1603 segment.instrument_id,
1604 start,
1605 end,
1606 state.parent.limit,
1607 state.parent.client_id,
1608 child_request_id,
1609 ts_init,
1610 child_params,
1611 )),
1612 ContinuousFutureSource::Quotes => RequestCommand::Quotes(RequestQuotes::new(
1613 segment.instrument_id,
1614 start,
1615 end,
1616 state.parent.limit,
1617 state.parent.client_id,
1618 child_request_id,
1619 ts_init,
1620 child_params,
1621 )),
1622 }
1623 }
1624
1625 fn emit_empty_continuous_future_response(&mut self, request_id: UUID4) {
1626 let Some(state) = self.continuous_future_requests.remove(&request_id) else {
1627 return;
1628 };
1629
1630 let mut params = state.parent.params.unwrap_or_default();
1631 if state.data_count != 0 {
1632 params.insert(
1633 "data_count".to_string(),
1634 serde_json::json!(state.data_count),
1635 );
1636 }
1637
1638 let response = DataResponse::Bars(BarsResponse::new(
1639 request_id,
1640 state.response_client_id,
1641 state.parent.bar_type,
1642 Vec::new(),
1643 Some(state.start_ns),
1644 Some(state.end_ns),
1645 self.clock.borrow().timestamp_ns(),
1646 Some(params),
1647 ));
1648 self.response(response);
1649 }
1650
1651 fn prepare_request_bar_aggregators(&mut self, req: &RequestCommand) -> anyhow::Result<()> {
1652 let request_id = *req.request_id();
1653 let Some(state) = request_bar_aggregation_from_params(request_params(req))? else {
1654 return Ok(());
1655 };
1656
1657 self.prepare_request_bar_aggregators_from_state(request_id, &state)
1658 }
1659
1660 fn can_start_request_bar_aggregators(
1661 &self,
1662 request_id: UUID4,
1663 state: &RequestBarAggregation,
1664 ) -> bool {
1665 let aggregator_request_id = state.aggregator_request_id(request_id);
1666 state.bar_types.iter().all(|bar_type| {
1667 let key = bar_aggregator_key(*bar_type, aggregator_request_id);
1668 self.bar_aggregators
1669 .get(&key)
1670 .is_none_or(|aggregator| !aggregator.borrow().is_running())
1671 })
1672 }
1673
1674 fn init_request_bar_aggregators(
1675 &mut self,
1676 request_id: UUID4,
1677 state: &RequestBarAggregation,
1678 ) -> anyhow::Result<()> {
1679 let aggregator_request_id = state.aggregator_request_id(request_id);
1680
1681 for bar_type in &state.bar_types {
1682 self.create_bar_aggregator_for_key(
1683 *bar_type,
1684 aggregator_request_id,
1685 state.skip_first_non_full_bar,
1686 )?;
1687 self.setup_bar_aggregator(*bar_type, true, aggregator_request_id)?;
1688
1689 let key = bar_aggregator_key(*bar_type, aggregator_request_id);
1690 if let Some(aggregator) = self.bar_aggregators.get(&key) {
1691 if state.disable_build_with_no_updates {
1692 aggregator.borrow_mut().set_build_with_no_updates(false);
1693 }
1694 aggregator.borrow_mut().set_is_running(true);
1695 }
1696 }
1697
1698 self.set_request_bar_aggregator_chain_handlers(request_id, state);
1699
1700 Ok(())
1701 }
1702
1703 fn set_request_bar_aggregator_chain_handlers(
1704 &self,
1705 request_id: UUID4,
1706 state: &RequestBarAggregation,
1707 ) {
1708 let aggregator_request_id = state.aggregator_request_id(request_id);
1709
1710 for bar_type in &state.bar_types {
1711 let key = bar_aggregator_key(*bar_type, aggregator_request_id);
1712 let Some(aggregator) = self.bar_aggregators.get(&key).cloned() else {
1713 continue;
1714 };
1715
1716 let downstream: Vec<_> = state
1717 .bar_types
1718 .iter()
1719 .filter(|candidate| {
1720 candidate.is_composite()
1721 && candidate.composite().standard() == bar_type.standard()
1722 })
1723 .filter_map(|candidate| {
1724 let key = bar_aggregator_key(*candidate, aggregator_request_id);
1725 self.bar_aggregators.get(&key).cloned()
1726 })
1727 .collect();
1728 let cache = self.cache.clone();
1729 let validate_sequence = self.config.validate_data_sequence;
1730 let handler: Box<dyn FnMut(Bar)> = Box::new(move |bar: Bar| {
1731 process_engine_bar(&cache, validate_sequence, false, bar);
1732
1733 for aggregator in &downstream {
1734 aggregator.borrow_mut().handle_bar(bar);
1735 }
1736 });
1737
1738 aggregator.borrow_mut().set_historical_mode(true, handler);
1739 }
1740 }
1741
1742 fn cleanup_request_bar_aggregators(&mut self, request_id: &UUID4) -> bool {
1743 let Some(state) = self.request_bar_aggregations.remove(request_id) else {
1744 return false;
1745 };
1746 let aggregator_request_id = state.aggregator_request_id(*request_id);
1747
1748 for bar_type in state.bar_types {
1749 let key = bar_aggregator_key(bar_type, aggregator_request_id);
1750 let has_live_handlers =
1751 state.update_subscriptions && self.bar_aggregator_handlers.contains_key(&key);
1752 let keep_running = if has_live_handlers {
1753 match self.setup_bar_aggregator(bar_type, false, aggregator_request_id) {
1754 Ok(()) => true,
1755 Err(e) => {
1756 log::error!(
1757 "Error starting live request bar aggregator for {bar_type}: {e}"
1758 );
1759 false
1760 }
1761 }
1762 } else {
1763 false
1764 };
1765
1766 if let Some(aggregator) = self.bar_aggregators.get(&key) {
1767 aggregator.borrow_mut().set_is_running(keep_running);
1768 }
1769
1770 if !state.update_subscriptions
1771 && let Err(e) = self.stop_bar_aggregator(bar_type, aggregator_request_id)
1772 {
1773 log::error!("Error stopping request bar aggregator for {bar_type}: {e}");
1774 }
1775 }
1776
1777 true
1778 }
1779
1780 pub fn process(&mut self, data: &dyn Any) {
1785 self.data_count += 1;
1786 if let Some(instrument) = data.downcast_ref::<InstrumentAny>() {
1790 self.handle_instrument(instrument);
1791 } else if let Some(funding_rate) = data.downcast_ref::<FundingRateUpdate>() {
1792 self.handle_funding_rate(*funding_rate);
1793 } else if let Some(option_greeks) = data.downcast_ref::<OptionGreeks>() {
1794 self.cache.borrow_mut().add_option_greeks(*option_greeks);
1795 self.feed_option_greeks_to_pre_bootstrap_chain(option_greeks);
1796 let topic = switchboard::get_option_greeks_topic(option_greeks.instrument_id);
1797 msgbus::publish_option_greeks(topic, option_greeks);
1798 self.drain_deferred_commands();
1799 } else if let Some(status) = data.downcast_ref::<InstrumentStatus>() {
1800 self.handle_instrument_status(*status);
1801 } else if let Some(custom) = data.downcast_ref::<CustomData>() {
1802 self.handle_custom_data(custom);
1803 } else {
1804 log::error!("Cannot process data {data:?}, type is unrecognized");
1805 }
1806 }
1807
1808 #[allow(
1810 clippy::needless_pass_by_value,
1811 reason = "callers hand over ownership; the payload is only moved when a DeFi handler consumes it"
1812 )]
1813 pub fn process_data(&mut self, data: Data) {
1814 #[cfg(feature = "defi")]
1815 let data = match data {
1816 Data::Defi(defi) => {
1817 self.process_defi_data(*defi);
1818 return;
1819 }
1820 data => data,
1821 };
1822
1823 self.process_data_ref(DataRef::from(&data));
1824 }
1825
1826 pub fn process_data_ref(&mut self, data: DataRef<'_>) {
1831 #[cfg(feature = "defi")]
1832 let data = match data {
1833 DataRef::Defi(defi) => {
1834 self.process_defi_data(defi.clone());
1835 return;
1836 }
1837 data => data,
1838 };
1839
1840 self.data_count += 1;
1841
1842 match data {
1843 DataRef::BookDelta(delta) => self.handle_delta(*delta),
1844 DataRef::BookDeltas(deltas) => self.handle_deltas(deltas),
1845 DataRef::BookDepth10(depth) => self.handle_depth10(*depth),
1846 DataRef::Quote(quote) => {
1847 self.handle_quote(*quote);
1848 self.drain_deferred_commands();
1849 }
1850 DataRef::Trade(trade) => self.handle_trade(*trade),
1851 DataRef::Bar(bar) => self.handle_bar(*bar),
1852 DataRef::MarkPrice(mark_price) => {
1853 self.handle_mark_price(*mark_price);
1854 self.drain_deferred_commands();
1855 }
1856 DataRef::IndexPrice(index_price) => {
1857 self.handle_index_price(*index_price);
1858 self.drain_deferred_commands();
1859 }
1860 DataRef::FundingRate(funding_rate) => {
1861 self.handle_funding_rate(*funding_rate);
1862 self.drain_deferred_commands();
1863 }
1864 DataRef::OptionGreeks(greeks) => {
1865 self.cache.borrow_mut().add_option_greeks(*greeks);
1866 self.feed_option_greeks_to_pre_bootstrap_chain(greeks);
1867 let topic = switchboard::get_option_greeks_topic(greeks.instrument_id);
1868 msgbus::publish_option_greeks(topic, greeks);
1869 self.drain_deferred_commands();
1870 }
1871 DataRef::InstrumentStatus(status) => {
1872 self.handle_instrument_status(*status);
1873 self.drain_deferred_commands();
1874 }
1875 DataRef::InstrumentClose(close) => self.handle_instrument_close(*close),
1876 DataRef::Custom(custom) => self.handle_custom_data(custom),
1877 #[cfg(feature = "defi")]
1878 DataRef::Defi(_) => unreachable!("handled before market data dispatch"),
1879 }
1880 }
1881
1882 fn feed_option_greeks_to_pre_bootstrap_chain(&mut self, greeks: &OptionGreeks) {
1883 let Some(series_id) = self
1884 .option_chain_instrument_index
1885 .get(&greeks.instrument_id)
1886 .copied()
1887 else {
1888 return;
1889 };
1890
1891 let Some(manager_rc) = self.option_chain_managers.get(&series_id).cloned() else {
1892 return;
1893 };
1894
1895 if manager_rc.borrow().is_bootstrapped() {
1896 return;
1897 }
1898
1899 manager_rc.borrow_mut().handle_greeks(greeks);
1900
1901 if manager_rc.borrow().is_bootstrapped() {
1902 self.finish_option_chain_greeks_bootstrap(series_id, &manager_rc);
1903 }
1904 }
1905
1906 pub fn process_pipeline(&mut self, data: Data) {
1913 #[cfg(feature = "defi")]
1914 let data = match data {
1915 Data::Defi(defi) => {
1916 self.process_defi_data(*defi);
1917 return;
1918 }
1919 data => data,
1920 };
1921
1922 self.data_count += 1;
1923
1924 match data {
1925 Data::BookDelta(delta) => self.handle_delta_pipeline(delta),
1926 Data::BookDeltas(deltas) => self.handle_deltas_pipeline(&deltas),
1927 Data::BookDepth10(depth) => self.handle_depth10_pipeline(*depth),
1928 Data::Quote(quote) => self.handle_quote_pipeline(quote),
1929 Data::Trade(trade) => self.handle_trade_pipeline(trade),
1930 Data::Bar(bar) => self.handle_bar_pipeline(bar),
1931 Data::MarkPrice(mark_price) => self.handle_mark_price_pipeline(mark_price),
1932 Data::IndexPrice(index_price) => self.handle_index_price_pipeline(index_price),
1933 Data::FundingRate(funding_rate) => {
1934 self.handle_funding_rate_pipeline(funding_rate);
1935 }
1936 Data::OptionGreeks(greeks) => self.handle_option_greeks_pipeline(greeks),
1937 Data::InstrumentStatus(status) => self.handle_instrument_status_pipeline(status),
1938 Data::InstrumentClose(close) => self.handle_instrument_close_pipeline(close),
1939 Data::Custom(custom) => self.handle_custom_data_pipeline(&custom),
1940 #[cfg(feature = "defi")]
1941 Data::Defi(_) => unreachable!("handled before market data dispatch"),
1942 }
1943 }
1944
1945 pub fn response(&mut self, mut resp: DataResponse) {
1947 if log::log_enabled!(log::Level::Debug) {
1948 let correlation_id = resp.correlation_id();
1949 match resp.record_count() {
1950 Some(count) => log::debug!(
1951 "{RECV}{RES} {} correlation_id={correlation_id} records={count}",
1952 resp.kind(),
1953 ),
1954 None => log::debug!(
1955 "{RECV}{RES} {} correlation_id={correlation_id}",
1956 resp.kind(),
1957 ),
1958 }
1959 }
1960 log::trace!("{RECV}{RES} {resp:?}");
1961
1962 self.response_count += 1;
1963
1964 resp.trim_to_bounds();
1965
1966 if let Some(parent_id) = continuous_future_parent_request_id(response_params(&resp)) {
1967 self.handle_continuous_future_child_response(parent_id, &resp);
1968 return;
1969 }
1970
1971 let Some(resp) = self.handle_request_pipeline_response(resp) else {
1972 return;
1973 };
1974
1975 if let Some(parent_id) = self
1976 .time_range_pipeline_parent_request_id
1977 .remove(resp.correlation_id())
1978 {
1979 self.handle_time_range_pipeline_child_response(parent_id, &resp);
1980 return;
1981 }
1982
1983 if self
1984 .parent_join_request_id
1985 .contains_key(resp.correlation_id())
1986 {
1987 self.finalize_request_join(resp);
1988 return;
1989 }
1990
1991 let correlation_id = *resp.correlation_id();
1992
1993 match &resp {
1994 DataResponse::Instrument(r) => {
1995 self.handle_instrument_response(r.data.clone());
1996 }
1997 DataResponse::Instruments(r) => {
1998 self.handle_instruments(&r.data);
1999 }
2000 DataResponse::Quotes(r) => {
2001 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
2002 self.handle_quotes(&r.data);
2003 }
2004 }
2005 DataResponse::Trades(r) => {
2006 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
2007 self.handle_trades(&r.data);
2008 }
2009 }
2010 DataResponse::FundingRates(r) => {
2011 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
2012 self.handle_funding_rates(&r.data);
2013 }
2014 }
2015 DataResponse::Bars(r) => {
2016 if !log_if_empty_response(&r.data, &r.bar_type, &correlation_id) {
2017 self.handle_bars(&r.data);
2018 }
2019 }
2020 DataResponse::Book(r) => self.handle_book_response(&r.data),
2021 DataResponse::BookDeltas(r) => {
2022 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
2023 self.handle_book_deltas_response(r);
2024 }
2025 }
2026 DataResponse::BookDepth(r) => {
2027 if !log_if_empty_response(&r.data, &r.instrument_id, &correlation_id) {
2028 self.handle_book_depth_response(r);
2029 }
2030 }
2031 DataResponse::OptionChainReferencePrice(r) => {
2032 self.process_request_bar_aggregation_response(&resp);
2033 return self.handle_option_chain_reference_price_response(&correlation_id, r);
2034 }
2035 DataResponse::Data(_) => {}
2036 }
2037
2038 self.process_request_bar_aggregation_response(&resp);
2039
2040 msgbus::send_response(&correlation_id, &resp);
2041 }
2042
2043 pub fn new_request_pipeline(&mut self, parent: RequestCommand, n_components: usize) {
2045 let parent_id = *parent.request_id();
2046 self.request_pipeline_n_components
2047 .insert(parent_id, n_components);
2048 self.request_pipeline_parent_request
2049 .insert(parent_id, parent);
2050 self.request_pipeline_responses
2051 .insert(parent_id, Vec::with_capacity(n_components));
2052 }
2053
2054 pub fn register_request_pipeline_leg(&mut self, leg_id: UUID4, parent_id: UUID4) {
2056 self.request_pipeline_parent_request_id
2057 .insert(leg_id, parent_id);
2058 }
2059
2060 fn handle_request_pipeline_response(&mut self, resp: DataResponse) -> Option<DataResponse> {
2065 let leg_id = *resp.correlation_id();
2066 let Some(parent_id) = self.request_pipeline_parent_request_id.remove(&leg_id) else {
2067 return Some(resp);
2068 };
2069
2070 let Some(buf) = self.request_pipeline_responses.get_mut(&parent_id) else {
2071 log::error!("Pipeline response buffer missing for parent {parent_id} (leg {leg_id})");
2072 return Some(resp);
2073 };
2074 buf.push(resp);
2075
2076 let expected = self.request_pipeline_n_components.get(&parent_id).copied();
2077 let received = buf.len();
2078 match expected {
2079 Some(n) if received < n => return None,
2080 Some(_) => {}
2081 None => {
2082 log::error!("Pipeline n_components missing for parent {parent_id}");
2083 return None;
2084 }
2085 }
2086
2087 let mut legs = self.request_pipeline_responses.remove(&parent_id)?;
2088 self.request_pipeline_n_components.remove(&parent_id);
2089 let parent = self.request_pipeline_parent_request.remove(&parent_id);
2090
2091 for leg in &mut legs {
2092 leg.trim_to_bounds();
2093 }
2094
2095 let (parent_start, parent_end) = parent_request_window(parent.as_ref());
2096 let rebuilt = rebuild_pipeline_response(parent_id, parent.as_ref(), legs);
2097
2098 if rebuilt.is_none()
2105 && let Some(original_id) = self.parent_join_request_id.remove(&parent_id)
2106 {
2107 self.pending_join_requests.remove(&original_id);
2108 log::error!(
2109 "Dropped RequestJoin {original_id} because pipeline rebuild failed for dated parent {parent_id}"
2110 );
2111 }
2112
2113 let mut rebuilt = rebuilt?;
2114
2115 if let DataResponse::BookDeltas(r) = &mut rebuilt {
2118 self.book_deltas_snapshot_replay(r);
2119 }
2120
2121 if parent_start.is_some() || parent_end.is_some() {
2127 rebuilt.trim_to_bounds();
2128 }
2129
2130 Some(rebuilt)
2131 }
2132
2133 fn book_deltas_snapshot_replay(&self, resp: &mut BookDeltasResponse) {
2137 let Some(original_start_ns) = resp.start else {
2138 return;
2139 };
2140
2141 let Some(first) = resp.data.first().copied() else {
2142 return;
2143 };
2144
2145 if !RecordFlag::F_SNAPSHOT.matches(first.flags) {
2146 return;
2147 }
2148
2149 if first.ts_init.as_u64() % NANOSECONDS_IN_DAY != 0 {
2150 return;
2151 }
2152
2153 if original_start_ns <= first.ts_init {
2155 return;
2156 }
2157
2158 if self
2159 .cache
2160 .borrow()
2161 .instrument(&resp.instrument_id)
2162 .is_none()
2163 {
2164 log::warn!(
2165 "Instrument {} not found in cache, skipping snapshot replay",
2166 resp.instrument_id,
2167 );
2168 return;
2169 }
2170
2171 let book_type = resp
2172 .params
2173 .as_ref()
2174 .and_then(|p| p.get_str("book_type"))
2175 .and_then(|s| BookType::from_str(s).ok())
2176 .unwrap_or(BookType::L2_MBP);
2177
2178 let mut book = OrderBook::new(resp.instrument_id, book_type);
2179 let mut before: Vec<OrderBookDelta> = Vec::new();
2180 let mut after: Vec<OrderBookDelta> = Vec::new();
2181 let mut last_applied_ts: Option<UnixNanos> = None;
2182 let mut crossed = false;
2183
2184 for delta in &resp.data {
2185 if crossed {
2186 after.push(*delta);
2187 } else {
2188 before.push(*delta);
2189 if delta.ts_init >= original_start_ns {
2190 crossed = true;
2191 last_applied_ts = Some(delta.ts_init);
2192 }
2193 }
2194 }
2195
2196 if !before.is_empty() {
2197 if last_applied_ts.is_none() {
2198 last_applied_ts = before.last().map(|d| d.ts_init);
2199 }
2200
2201 let batch = OrderBookDeltas::new(resp.instrument_id, before);
2202 if let Err(e) = book.apply_deltas(&batch) {
2203 log::error!(
2204 "Failed to rebuild book for snapshot replay on {}: {e}",
2205 resp.instrument_id,
2206 );
2207 return;
2208 }
2209 }
2210
2211 let Some(last_ts) = last_applied_ts else {
2212 return;
2213 };
2214
2215 let snapshot_ts = last_ts.max(original_start_ns);
2216 let mut new_data = book.to_deltas(snapshot_ts, snapshot_ts).deltas;
2217 new_data.extend(after);
2218 resp.data = new_data;
2219 }
2220
2221 fn handle_request_join(&mut self, req: RequestJoin) -> anyhow::Result<()> {
2222 if has_time_range_pipeline_params(req.params.as_ref()) {
2223 return self.execute_time_range_pipeline_request(RequestCommand::Join(req));
2224 }
2225
2226 let now_ns = self.clock.borrow().timestamp_ns();
2227 let now_dt = now_ns.to_datetime_utc();
2228 let zero = jiff::Timestamp::UNIX_EPOCH;
2229 let start = req.start.unwrap_or(zero).min(now_dt);
2230 let end = req.end.unwrap_or(now_dt).min(now_dt);
2231 let dated = req.with_dates(Some(start), Some(end), now_ns);
2232
2233 let original_id = req.request_id;
2234 let dated_id = dated.request_id;
2235
2236 self.pending_join_requests.insert(original_id, req);
2237 self.parent_join_request_id.insert(dated_id, original_id);
2238
2239 let leg_ids: Vec<UUID4> = dated.request_ids.clone();
2240 self.new_request_pipeline(RequestCommand::Join(dated), leg_ids.len());
2241 for leg_id in leg_ids {
2242 self.register_request_pipeline_leg(leg_id, dated_id);
2243 }
2244
2245 Ok(())
2246 }
2247
2248 fn finalize_request_join(&mut self, resp: DataResponse) {
2249 let dated_id = *resp.correlation_id();
2250 let Some(original_id) = self.parent_join_request_id.remove(&dated_id) else {
2251 log::error!("parent_join_request_id missing for dated correlation {dated_id}");
2252 return;
2253 };
2254
2255 let Some(original) = self.pending_join_requests.remove(&original_id) else {
2256 log::error!("pending_join_requests missing for original {original_id}");
2257 return;
2258 };
2259
2260 let now_ns = self.clock.borrow().timestamp_ns();
2261
2262 for leg_request_id in &original.request_ids {
2268 let empty = empty_response_like(&resp, *leg_request_id, now_ns);
2269 msgbus::send_response(leg_request_id, &empty);
2270 }
2271
2272 let final_resp = rebind_response_correlation(resp, original_id);
2278 self.response(final_resp);
2279 }
2280
2281 fn process_request_bar_aggregation_response(&mut self, resp: &DataResponse) {
2282 let correlation_id = *resp.correlation_id();
2283 let Some(state) = self.request_bar_aggregations.get(&correlation_id).cloned() else {
2284 return;
2285 };
2286
2287 match resp {
2288 DataResponse::Quotes(r) => {
2289 for quote in &r.data {
2290 self.update_request_bar_aggregators_from_quote(&state, correlation_id, *quote);
2291 }
2292 }
2293 DataResponse::Trades(r) => {
2294 for trade in &r.data {
2295 self.update_request_bar_aggregators_from_trade(&state, correlation_id, *trade);
2296 }
2297 }
2298 DataResponse::Bars(r) => {
2299 for bar in &r.data {
2300 self.update_request_bar_aggregators_from_bar(&state, correlation_id, *bar);
2301 }
2302 }
2303 _ => {}
2304 }
2305
2306 self.cleanup_request_bar_aggregators(&correlation_id);
2307 }
2308
2309 fn handle_continuous_future_child_response(&mut self, parent_id: UUID4, resp: &DataResponse) {
2310 if !self.continuous_future_requests.contains_key(&parent_id) {
2311 log::error!("No active continuous future request for child response {parent_id}");
2312 return;
2313 }
2314
2315 let data_count = response_params(resp)
2316 .and_then(|params| params.get("data_count"))
2317 .and_then(serde_json::Value::as_u64)
2318 .or_else(|| resp.record_count().map(|count| count as u64))
2319 .unwrap_or(0);
2320
2321 if let Some(state) = self.continuous_future_requests.get_mut(&parent_id) {
2322 state.data_count += data_count;
2323 }
2324
2325 match resp {
2326 DataResponse::Quotes(r) => {
2327 if !log_if_empty_response(&r.data, &r.instrument_id, resp.correlation_id()) {
2328 self.handle_quotes(&r.data);
2329 }
2330 }
2331 DataResponse::Trades(r) => {
2332 if !log_if_empty_response(&r.data, &r.instrument_id, resp.correlation_id()) {
2333 self.handle_trades(&r.data);
2334 }
2335 }
2336 DataResponse::Bars(r) => {
2337 if !log_if_empty_response(&r.data, &r.bar_type, resp.correlation_id()) {
2338 self.handle_bars(&r.data);
2339 }
2340 }
2341 _ => {
2342 log::error!(
2343 "Continuous future child response {parent_id} must contain quotes, trades, or bars"
2344 );
2345 return;
2346 }
2347 }
2348
2349 self.process_continuous_future_aggregation_response(parent_id, resp);
2350 if let Err(e) = self.dispatch_next_continuous_future_segment(parent_id) {
2351 log::error!("Error dispatching continuous future segment for {parent_id}: {e}");
2352 self.emit_empty_continuous_future_response(parent_id);
2353 }
2354 }
2355
2356 fn process_continuous_future_aggregation_response(
2357 &self,
2358 parent_id: UUID4,
2359 resp: &DataResponse,
2360 ) {
2361 let Some(state) = self.continuous_future_requests.get(&parent_id) else {
2362 return;
2363 };
2364 let primary_bar_type = state.request.primary_bar_type;
2365 let aggregator_request_id = Some(parent_id);
2366
2367 match resp {
2368 DataResponse::Quotes(r) => {
2369 for quote in &r.data {
2370 self.update_request_bar_aggregator(
2371 primary_bar_type,
2372 aggregator_request_id,
2373 |aggregator| {
2374 aggregator.handle_quote(*quote);
2375 },
2376 );
2377 }
2378 }
2379 DataResponse::Trades(r) => {
2380 for trade in &r.data {
2381 self.update_request_bar_aggregator(
2382 primary_bar_type,
2383 aggregator_request_id,
2384 |aggregator| {
2385 aggregator.handle_trade(*trade);
2386 },
2387 );
2388 }
2389 }
2390 DataResponse::Bars(r) => {
2391 for bar in &r.data {
2392 self.update_request_bar_aggregator(
2393 primary_bar_type,
2394 aggregator_request_id,
2395 |aggregator| {
2396 aggregator.handle_bar(*bar);
2397 },
2398 );
2399 }
2400 }
2401 _ => {}
2402 }
2403 }
2404
2405 fn update_request_bar_aggregators_from_quote(
2406 &self,
2407 state: &RequestBarAggregation,
2408 request_id: UUID4,
2409 quote: QuoteTick,
2410 ) {
2411 let aggregator_request_id = state.aggregator_request_id(request_id);
2412
2413 for bar_type in &state.bar_types {
2414 if bar_type.is_composite()
2415 || bar_type.instrument_id() != quote.instrument_id
2416 || bar_type.spec().price_type == PriceType::Last
2417 {
2418 continue;
2419 }
2420
2421 self.update_request_bar_aggregator(*bar_type, aggregator_request_id, |aggregator| {
2422 aggregator.handle_quote(quote);
2423 });
2424 }
2425 }
2426
2427 fn update_request_bar_aggregators_from_trade(
2428 &self,
2429 state: &RequestBarAggregation,
2430 request_id: UUID4,
2431 trade: TradeTick,
2432 ) {
2433 let aggregator_request_id = state.aggregator_request_id(request_id);
2434
2435 for bar_type in &state.bar_types {
2436 if bar_type.is_composite()
2437 || bar_type.instrument_id() != trade.instrument_id
2438 || bar_type.spec().price_type != PriceType::Last
2439 {
2440 continue;
2441 }
2442
2443 self.update_request_bar_aggregator(*bar_type, aggregator_request_id, |aggregator| {
2444 aggregator.handle_trade(trade);
2445 });
2446 }
2447 }
2448
2449 fn update_request_bar_aggregators_from_bar(
2450 &self,
2451 state: &RequestBarAggregation,
2452 request_id: UUID4,
2453 bar: Bar,
2454 ) {
2455 let aggregator_request_id = state.aggregator_request_id(request_id);
2456
2457 for bar_type in &state.bar_types {
2458 if !bar_type.is_composite()
2459 || bar_type.composite().standard() != bar.bar_type.standard()
2460 {
2461 continue;
2462 }
2463
2464 self.update_request_bar_aggregator(*bar_type, aggregator_request_id, |aggregator| {
2465 aggregator.handle_bar(bar);
2466 });
2467 }
2468 }
2469
2470 fn update_request_bar_aggregator<F>(
2471 &self,
2472 bar_type: BarType,
2473 request_id: Option<UUID4>,
2474 update: F,
2475 ) where
2476 F: FnOnce(&mut dyn BarAggregator),
2477 {
2478 let key = bar_aggregator_key(bar_type, request_id);
2479 let Some(aggregator) = self.bar_aggregators.get(&key) else {
2480 log::error!("Cannot update request bar aggregator: no aggregator found for {bar_type}");
2481 return;
2482 };
2483
2484 update(aggregator.borrow_mut().as_mut());
2485 }
2486
2487 #[inline]
2488 fn pipeline_cache_writes_allowed(&self) -> bool {
2489 !self.config.disable_historical_cache
2490 }
2491
2492 pub(crate) fn handle_instrument(&mut self, instrument: &InstrumentAny) {
2493 log::debug!("Handling instrument: {}", instrument.id());
2494
2495 if let Err(e) = self
2496 .cache
2497 .as_ref()
2498 .borrow_mut()
2499 .add_instrument(instrument.clone())
2500 {
2501 log_error_on_cache_insert(&e);
2502 }
2503
2504 let topic = switchboard::get_instrument_topic(instrument.id());
2505 log::debug!("Publishing instrument to topic: {topic}");
2506 msgbus::publish_instrument(topic, instrument);
2507
2508 self.update_option_chains(instrument);
2509 }
2510
2511 fn update_option_chains(&mut self, instrument: &InstrumentAny) {
2512 let Some(underlying) = instrument.underlying() else {
2513 return;
2514 };
2515 let Some(expiration_ns) = instrument.expiration_ns() else {
2516 return;
2517 };
2518 let Some(strike) = instrument.strike_price() else {
2519 return;
2520 };
2521 let Some(kind) = instrument.option_kind() else {
2522 return;
2523 };
2524
2525 let venue = instrument.id().venue;
2526 let settlement = instrument.settlement_currency().code;
2527 let series_id = OptionSeriesId::new(venue, underlying, settlement, expiration_ns);
2528
2529 let Some(manager_rc) = self.option_chain_managers.get(&series_id).cloned() else {
2531 return;
2532 };
2533
2534 let clock = self.clock.clone();
2535 let client_id = manager_rc.borrow().client_id();
2536 let client = self.get_command_client(client_id.as_ref(), Some(&venue));
2537
2538 if manager_rc
2539 .borrow_mut()
2540 .add_instrument(instrument.id(), strike, kind, client, &clock)
2541 {
2542 self.option_chain_instrument_index
2543 .insert(instrument.id(), series_id);
2544 }
2545 }
2546
2547 fn handle_delta(&mut self, delta: OrderBookDelta) {
2548 let mut deltas = if self.config.buffer_deltas {
2549 self.buffer_delta(delta);
2550
2551 if !RecordFlag::F_LAST.matches(delta.flags) {
2552 return; }
2554
2555 self.buffered_deltas_map
2556 .remove(&delta.instrument_id)
2557 .expect("buffered deltas exist")
2558 } else {
2559 self.single_delta_batch(delta)
2560 };
2561
2562 let topic = switchboard::get_book_deltas_topic(deltas.instrument_id);
2563 msgbus::publish_deltas(topic, &deltas);
2564 self.reclaim_deltas_frame(mem::take(&mut deltas.deltas));
2565 }
2566
2567 fn handle_deltas(&mut self, deltas: &OrderBookDeltas) {
2568 if self.config.buffer_deltas {
2569 let instrument_id = deltas.instrument_id;
2570
2571 for delta in &deltas.deltas {
2572 let is_last = RecordFlag::F_LAST.matches(delta.flags);
2573 self.buffer_delta(*delta);
2574
2575 if is_last {
2576 let mut deltas_to_publish = self
2577 .buffered_deltas_map
2578 .remove(&instrument_id)
2579 .expect("buffered deltas exist");
2580 let topic = switchboard::get_book_deltas_topic(instrument_id);
2581 msgbus::publish_deltas(topic, &deltas_to_publish);
2582 self.reclaim_deltas_frame(mem::take(&mut deltas_to_publish.deltas));
2583 }
2584 }
2585 } else {
2586 let topic = switchboard::get_book_deltas_topic(deltas.instrument_id);
2587 msgbus::publish_deltas(topic, deltas);
2588 }
2589 }
2590
2591 fn handle_depth10(&self, depth: OrderBookDepth10) {
2592 let topic = switchboard::get_book_depth10_topic(depth.instrument_id);
2593 msgbus::publish_depth10(topic, &depth);
2594
2595 if self.config.emit_quotes_from_book_depths
2596 && let Some(quote) = derive_quote_from_depth(&depth)
2597 {
2598 book::publish_quote_if_changed(&self.cache, quote);
2599 }
2600 }
2601
2602 fn handle_quote(&self, quote: QuoteTick) {
2603 if let Err(e) = self.cache.as_ref().borrow_mut().add_quote(quote) {
2604 log_error_on_cache_insert(&e);
2605 }
2606
2607 for synthetic_quote in self.synthetic_quotes_from_quote(quote) {
2608 let topic = switchboard::get_quotes_topic(synthetic_quote.instrument_id);
2609 msgbus::publish_quote(topic, &synthetic_quote);
2610 }
2611
2612 let topic = switchboard::get_quotes_topic(quote.instrument_id);
2613 msgbus::publish_quote(topic, "e);
2614 }
2615
2616 fn handle_trade(&self, trade: TradeTick) {
2617 if let Err(e) = self.cache.as_ref().borrow_mut().add_trade(trade) {
2618 log_error_on_cache_insert(&e);
2619 }
2620
2621 for synthetic_trade in self.synthetic_trades_from_trade(trade) {
2622 let topic = switchboard::get_trades_topic(synthetic_trade.instrument_id);
2623 msgbus::publish_trade(topic, &synthetic_trade);
2624 }
2625
2626 let topic = switchboard::get_trades_topic(trade.instrument_id);
2627 msgbus::publish_trade(topic, &trade);
2628 }
2629
2630 fn synthetic_quotes_from_quote(&self, update: QuoteTick) -> Vec<QuoteTick> {
2631 let Some(synthetics) = self.synthetic_quote_feeds.get(&update.instrument_id) else {
2632 return Vec::new();
2633 };
2634
2635 synthetics
2636 .iter()
2637 .filter_map(|synthetic| self.synthetic_quote_from_update(synthetic, update))
2638 .collect()
2639 }
2640
2641 fn synthetic_quote_from_update(
2642 &self,
2643 synthetic: &SyntheticInstrument,
2644 update: QuoteTick,
2645 ) -> Option<QuoteTick> {
2646 let cache = self.cache.borrow();
2647 let mut bid_inputs = Vec::with_capacity(synthetic.components.len());
2648 let mut ask_inputs = Vec::with_capacity(synthetic.components.len());
2649
2650 for instrument_id in &synthetic.components {
2651 let (bid_price, ask_price) = if *instrument_id == update.instrument_id {
2652 (update.bid_price, update.ask_price)
2653 } else {
2654 let Some(component_quote) = cache.quote(instrument_id) else {
2655 log::warn!(
2656 "Cannot calculate synthetic instrument {} price, no quotes for {} yet",
2657 synthetic.id,
2658 instrument_id,
2659 );
2660 return None;
2661 };
2662 (component_quote.bid_price, component_quote.ask_price)
2663 };
2664
2665 bid_inputs.push(bid_price.as_f64());
2666 ask_inputs.push(ask_price.as_f64());
2667 }
2668 drop(cache);
2669
2670 let bid_price = match synthetic.calculate(&bid_inputs) {
2671 Ok(price) => price,
2672 Err(e) => {
2673 log::error!(
2674 "Cannot calculate synthetic instrument {} bid price: {e}",
2675 synthetic.id
2676 );
2677 return None;
2678 }
2679 };
2680 let ask_price = match synthetic.calculate(&ask_inputs) {
2681 Ok(price) => price,
2682 Err(e) => {
2683 log::error!(
2684 "Cannot calculate synthetic instrument {} ask price: {e}",
2685 synthetic.id
2686 );
2687 return None;
2688 }
2689 };
2690 let size_one = Quantity::from(1);
2691
2692 Some(QuoteTick::new(
2693 synthetic.id,
2694 bid_price,
2695 ask_price,
2696 size_one,
2697 size_one,
2698 update.ts_event,
2699 self.clock.borrow().timestamp_ns(),
2700 ))
2701 }
2702
2703 fn synthetic_trades_from_trade(&self, update: TradeTick) -> Vec<TradeTick> {
2704 let Some(synthetics) = self.synthetic_trade_feeds.get(&update.instrument_id) else {
2705 return Vec::new();
2706 };
2707
2708 synthetics
2709 .iter()
2710 .filter_map(|synthetic| self.synthetic_trade_from_update(synthetic, update))
2711 .collect()
2712 }
2713
2714 fn synthetic_trade_from_update(
2715 &self,
2716 synthetic: &SyntheticInstrument,
2717 update: TradeTick,
2718 ) -> Option<TradeTick> {
2719 let cache = self.cache.borrow();
2720 let mut inputs = Vec::with_capacity(synthetic.components.len());
2721
2722 for instrument_id in &synthetic.components {
2723 let price = if *instrument_id == update.instrument_id {
2724 update.price
2725 } else {
2726 let Some(component_trade) = cache.trade(instrument_id) else {
2727 log::warn!(
2728 "Cannot calculate synthetic instrument {} price, no trades for {} yet",
2729 synthetic.id,
2730 instrument_id,
2731 );
2732 return None;
2733 };
2734 component_trade.price
2735 };
2736
2737 inputs.push(price.as_f64());
2738 }
2739 drop(cache);
2740
2741 let price = match synthetic.calculate(&inputs) {
2742 Ok(price) => price,
2743 Err(e) => {
2744 log::error!(
2745 "Cannot calculate synthetic instrument {} trade price: {e}",
2746 synthetic.id
2747 );
2748 return None;
2749 }
2750 };
2751
2752 Some(TradeTick::new(
2753 synthetic.id,
2754 price,
2755 Quantity::from(1),
2756 update.aggressor_side,
2757 update.trade_id,
2758 update.ts_event,
2759 self.clock.borrow().timestamp_ns(),
2760 ))
2761 }
2762
2763 fn handle_bar(&self, bar: Bar) {
2764 process_engine_bar(&self.cache, self.config.validate_data_sequence, true, bar);
2765 }
2766
2767 fn handle_mark_price(&self, mark_price: MarkPriceUpdate) {
2768 if let Err(e) = self.cache.as_ref().borrow_mut().add_mark_price(mark_price) {
2769 log_error_on_cache_insert(&e);
2770 }
2771
2772 let topic = switchboard::get_mark_price_topic(mark_price.instrument_id);
2773 msgbus::publish_mark_price(topic, &mark_price);
2774 }
2775
2776 fn handle_index_price(&self, index_price: IndexPriceUpdate) {
2777 if let Err(e) = self
2778 .cache
2779 .as_ref()
2780 .borrow_mut()
2781 .add_index_price(index_price)
2782 {
2783 log_error_on_cache_insert(&e);
2784 }
2785
2786 let topic = switchboard::get_index_price_topic(index_price.instrument_id);
2787 msgbus::publish_index_price(topic, &index_price);
2788 }
2789
2790 pub fn handle_funding_rate(&mut self, funding_rate: FundingRateUpdate) {
2792 if let Err(e) = self
2793 .cache
2794 .as_ref()
2795 .borrow_mut()
2796 .add_funding_rate(funding_rate)
2797 {
2798 log_error_on_cache_insert(&e);
2799 }
2800
2801 let topic = switchboard::get_funding_rate_topic(funding_rate.instrument_id);
2802 msgbus::publish_funding_rate(topic, &funding_rate);
2803 }
2804
2805 fn handle_instrument_status(&mut self, status: InstrumentStatus) {
2806 if let Err(e) = self
2807 .cache
2808 .as_ref()
2809 .borrow_mut()
2810 .add_instrument_status(status)
2811 {
2812 log_error_on_cache_insert(&e);
2813 }
2814
2815 let topic = switchboard::get_instrument_status_topic(status.instrument_id);
2816 msgbus::publish_any(topic, &status);
2817
2818 if self
2819 .option_chain_instrument_index
2820 .contains_key(&status.instrument_id)
2821 && matches!(
2822 status.action,
2823 MarketStatusAction::Close | MarketStatusAction::NotAvailableForTrading
2824 )
2825 {
2826 self.expire_option_chain_instrument(status.instrument_id);
2827 }
2828 }
2829
2830 fn expire_option_chain_instrument(&mut self, instrument_id: InstrumentId) {
2837 let Some(series_id) = self.option_chain_instrument_index.remove(&instrument_id) else {
2838 return;
2839 };
2840
2841 if self
2842 .option_chain_greeks_bootstraps
2843 .get(&series_id)
2844 .is_some_and(|bootstrap| bootstrap.instrument_id == instrument_id)
2845 {
2846 self.stop_option_chain_greeks_bootstrap(series_id);
2847 }
2848
2849 let Some(manager_rc) = self.option_chain_managers.get(&series_id).cloned() else {
2850 return;
2851 };
2852
2853 let series_empty = manager_rc
2854 .borrow_mut()
2855 .handle_instrument_expired(&instrument_id);
2856
2857 self.drain_deferred_commands();
2859
2860 log::info!(
2861 "Expired instrument {instrument_id} from option chain {series_id} (series_empty={series_empty})",
2862 );
2863
2864 if series_empty {
2865 manager_rc.borrow_mut().teardown(&self.clock);
2866 self.option_chain_managers.remove(&series_id);
2867
2868 log::info!("Torn down empty option chain manager for {series_id}");
2869 }
2870 }
2871
2872 fn handle_instrument_close(&self, close: InstrumentClose) {
2873 let topic = switchboard::get_instrument_close_topic(close.instrument_id);
2874 msgbus::publish_any(topic, &close);
2875 }
2876
2877 fn handle_custom_data(&self, custom: &CustomData) {
2878 log::debug!("Processing custom data: {}", custom.data.type_name());
2879 let topic = switchboard::get_custom_topic(&custom.data_type);
2880 msgbus::publish_any(topic, custom);
2881 }
2882
2883 fn handle_delta_pipeline(&mut self, delta: OrderBookDelta) {
2884 let mut deltas = self.single_delta_batch(delta);
2886 let topic = switchboard::get_pipeline_book_deltas_topic(deltas.instrument_id);
2887 msgbus::publish_deltas(topic, &deltas);
2888 self.reclaim_deltas_frame(mem::take(&mut deltas.deltas));
2889 }
2890
2891 fn buffer_delta(&mut self, delta: OrderBookDelta) {
2892 if let Some(buffered_deltas) = self.buffered_deltas_map.get_mut(&delta.instrument_id) {
2893 buffered_deltas.deltas.push(delta);
2894 buffered_deltas.flags = delta.flags;
2895 buffered_deltas.sequence = delta.sequence;
2896 buffered_deltas.ts_event = delta.ts_event;
2897 buffered_deltas.ts_init = delta.ts_init;
2898 return;
2899 }
2900
2901 let instrument_id = delta.instrument_id;
2902 let buffered_deltas = self.single_delta_batch(delta);
2903 self.buffered_deltas_map
2904 .insert(instrument_id, buffered_deltas);
2905 }
2906
2907 fn single_delta_batch(&mut self, delta: OrderBookDelta) -> OrderBookDeltas {
2908 let instrument_id = delta.instrument_id;
2909 let mut frame = mem::take(&mut self.deltas_frame);
2910 frame.clear();
2911 frame.push(delta);
2912 OrderBookDeltas::new(instrument_id, frame)
2913 }
2914
2915 fn reclaim_deltas_frame(&mut self, mut frame: Vec<OrderBookDelta>) {
2916 frame.clear();
2917 self.deltas_frame = frame;
2918 }
2919
2920 fn handle_deltas_pipeline(&self, deltas: &OrderBookDeltas) {
2921 let topic = switchboard::get_pipeline_book_deltas_topic(deltas.instrument_id);
2922 msgbus::publish_deltas(topic, deltas);
2923 }
2924
2925 fn handle_depth10_pipeline(&self, depth: OrderBookDepth10) {
2926 let topic = switchboard::get_pipeline_book_depth10_topic(depth.instrument_id);
2927 msgbus::publish_depth10(topic, &depth);
2928 }
2929
2930 fn handle_quote_pipeline(&self, quote: QuoteTick) {
2931 if self.pipeline_cache_writes_allowed()
2932 && let Err(e) = self.cache.as_ref().borrow_mut().add_quote(quote)
2933 {
2934 log_error_on_cache_insert(&e);
2935 }
2936
2937 let topic = switchboard::get_pipeline_quotes_topic(quote.instrument_id);
2938 msgbus::publish_quote(topic, "e);
2939 }
2940
2941 fn handle_trade_pipeline(&self, trade: TradeTick) {
2942 if self.pipeline_cache_writes_allowed()
2943 && let Err(e) = self.cache.as_ref().borrow_mut().add_trade(trade)
2944 {
2945 log_error_on_cache_insert(&e);
2946 }
2947
2948 let topic = switchboard::get_pipeline_trades_topic(trade.instrument_id);
2949 msgbus::publish_trade(topic, &trade);
2950 }
2951
2952 fn handle_bar_pipeline(&self, bar: Bar) {
2953 if !validate_bar_sequence(&self.cache, self.config.validate_data_sequence, &bar) {
2954 return;
2955 }
2956
2957 if self.pipeline_cache_writes_allowed()
2958 && let Err(e) = self.cache.as_ref().borrow_mut().add_bar(bar)
2959 {
2960 log_error_on_cache_insert(&e);
2961 }
2962
2963 let topic = switchboard::get_pipeline_bars_topic(bar.bar_type);
2964 msgbus::publish_bar(topic, &bar);
2965 }
2966
2967 fn handle_mark_price_pipeline(&self, mark_price: MarkPriceUpdate) {
2968 if self.pipeline_cache_writes_allowed()
2969 && let Err(e) = self.cache.as_ref().borrow_mut().add_mark_price(mark_price)
2970 {
2971 log_error_on_cache_insert(&e);
2972 }
2973
2974 let topic = switchboard::get_pipeline_mark_price_topic(mark_price.instrument_id);
2975 msgbus::publish_mark_price(topic, &mark_price);
2976 }
2977
2978 fn handle_index_price_pipeline(&self, index_price: IndexPriceUpdate) {
2979 if self.pipeline_cache_writes_allowed()
2980 && let Err(e) = self
2981 .cache
2982 .as_ref()
2983 .borrow_mut()
2984 .add_index_price(index_price)
2985 {
2986 log_error_on_cache_insert(&e);
2987 }
2988
2989 let topic = switchboard::get_pipeline_index_price_topic(index_price.instrument_id);
2990 msgbus::publish_index_price(topic, &index_price);
2991 }
2992
2993 fn handle_funding_rate_pipeline(&self, funding_rate: FundingRateUpdate) {
2994 if self.pipeline_cache_writes_allowed()
2995 && let Err(e) = self
2996 .cache
2997 .as_ref()
2998 .borrow_mut()
2999 .add_funding_rate(funding_rate)
3000 {
3001 log_error_on_cache_insert(&e);
3002 }
3003
3004 let topic = switchboard::get_pipeline_funding_rate_topic(funding_rate.instrument_id);
3005 msgbus::publish_funding_rate(topic, &funding_rate);
3006 }
3007
3008 fn handle_instrument_status_pipeline(&self, status: InstrumentStatus) {
3009 if self.pipeline_cache_writes_allowed()
3010 && let Err(e) = self
3011 .cache
3012 .as_ref()
3013 .borrow_mut()
3014 .add_instrument_status(status)
3015 {
3016 log_error_on_cache_insert(&e);
3017 }
3018
3019 let topic = switchboard::get_pipeline_instrument_status_topic(status.instrument_id);
3020 msgbus::publish_any(topic, &status);
3021 }
3022
3023 fn handle_option_greeks_pipeline(&self, greeks: OptionGreeks) {
3024 if self.pipeline_cache_writes_allowed() {
3025 self.cache.borrow_mut().add_option_greeks(greeks);
3026 }
3027
3028 let topic = switchboard::get_pipeline_option_greeks_topic(greeks.instrument_id);
3029 msgbus::publish_option_greeks(topic, &greeks);
3030 }
3031
3032 fn handle_instrument_close_pipeline(&self, close: InstrumentClose) {
3033 let topic = switchboard::get_pipeline_instrument_close_topic(close.instrument_id);
3034 msgbus::publish_any(topic, &close);
3035 }
3036
3037 fn handle_custom_data_pipeline(&self, custom: &CustomData) {
3038 log::debug!("Pipeline custom data: {}", custom.data.type_name());
3039 let topic = switchboard::get_pipeline_custom_topic(&custom.data_type);
3040 msgbus::publish_any(topic, custom);
3041 }
3042
3043 fn drain_deferred_commands(&mut self) {
3047 loop {
3049 let commands: VecDeque<DeferredCommand> =
3050 std::mem::take(&mut *self.deferred_cmd_queue.borrow_mut());
3051
3052 if commands.is_empty() {
3053 break;
3054 }
3055
3056 for cmd in commands {
3057 match cmd {
3058 DeferredCommand::Subscribe(sub) => {
3059 let client = self.get_command_client(sub.client_id(), sub.venue());
3060 if let Some(client) = client {
3061 client.execute_subscribe(sub);
3062 }
3063 }
3064 DeferredCommand::Unsubscribe(unsub) => {
3065 if let Err(e) = self.execute_unsubscribe(&unsub) {
3066 log::error!("Failed to execute deferred unsubscribe: {e}");
3067 }
3068 }
3069 DeferredCommand::ExpireInstrument(instrument_id) => {
3070 self.expire_option_chain_instrument(instrument_id);
3071 }
3072 DeferredCommand::ExpireSeries(series_id) => {
3073 self.expire_series(series_id);
3074 }
3075 }
3076 }
3077 }
3078 }
3079
3080 fn expire_series(&mut self, series_id: OptionSeriesId) {
3086 self.stop_option_chain_greeks_bootstrap(series_id);
3087
3088 let Some(manager_rc) = self.option_chain_managers.get(&series_id).cloned() else {
3089 return;
3090 };
3091
3092 let instrument_ids: Vec<InstrumentId> = self
3093 .option_chain_instrument_index
3094 .iter()
3095 .filter(|(_, sid)| **sid == series_id)
3096 .map(|(id, _)| *id)
3097 .collect();
3098
3099 for id in &instrument_ids {
3100 self.option_chain_instrument_index.remove(id);
3101 manager_rc.borrow_mut().handle_instrument_expired(id);
3102 }
3103
3104 manager_rc.borrow_mut().teardown(&self.clock);
3105 self.option_chain_managers.remove(&series_id);
3106
3107 log::info!("Proactively torn down expired option chain {series_id}");
3108 }
3109
3110 fn subscribe_book_deltas(&mut self, cmd: &SubscribeBookDeltas) -> anyhow::Result<bool> {
3111 if cmd.instrument_id.is_synthetic() {
3112 anyhow::bail!("Cannot subscribe for synthetic instrument `OrderBookDelta` data");
3113 }
3114
3115 let parent = resolve_parent_components(&cmd.instrument_id, cmd.params.as_ref())?;
3118
3119 let had_deltas =
3120 self.has_book_delta_subscription_key(cmd.instrument_id, cmd.client_id, cmd.venue);
3121
3122 if cmd.managed {
3123 self.setup_book_updater(&cmd.instrument_id, cmd.book_type, true, parent)?;
3124 }
3125
3126 self.increment_book_delta_subscription(cmd.instrument_id, cmd.client_id, cmd.venue);
3127
3128 Ok(!had_deltas)
3129 }
3130
3131 fn subscribe_book_depth10(&mut self, cmd: &SubscribeBookDepth10) -> anyhow::Result<bool> {
3132 if cmd.instrument_id.is_synthetic() {
3133 anyhow::bail!("Cannot subscribe for synthetic instrument `OrderBookDepth10` data");
3134 }
3135
3136 let parent = resolve_parent_components(&cmd.instrument_id, cmd.params.as_ref())?;
3137 let had_depth10 =
3138 self.has_book_depth10_subscription_key(cmd.instrument_id, cmd.client_id, cmd.venue);
3139
3140 if cmd.managed {
3141 self.setup_book_updater(&cmd.instrument_id, cmd.book_type, false, parent)?;
3142 }
3143
3144 self.increment_book_depth10_subscription(cmd.instrument_id, cmd.client_id, cmd.venue);
3145
3146 Ok(!had_depth10)
3147 }
3148
3149 fn subscribe_book_snapshots(&mut self, cmd: &SubscribeBookSnapshots) -> anyhow::Result<()> {
3150 if cmd.instrument_id.is_synthetic() {
3151 anyhow::bail!("Cannot subscribe for synthetic instrument `OrderBookDelta` data");
3152 }
3153
3154 let parent = resolve_parent_components(&cmd.instrument_id, cmd.params.as_ref())?;
3155
3156 let had_snapshots = self.has_book_snapshot_subscriptions(&cmd.instrument_id);
3157
3158 if !had_snapshots {
3159 self.setup_book_updater(&cmd.instrument_id, cmd.book_type, false, parent)?;
3164 }
3165
3166 self.increment_book_snapshot_subscription(cmd, parent);
3167
3168 if !had_snapshots {
3169 self.book_snapshot_sources.insert(
3170 cmd.instrument_id,
3171 BookSnapshotSource {
3172 command: cmd.clone(),
3173 client_command: SubscribeCommand::BookDeltas(SubscribeBookDeltas::new(
3174 cmd.instrument_id,
3175 cmd.book_type,
3176 cmd.client_id,
3177 cmd.venue,
3178 UUID4::new(),
3179 cmd.ts_init,
3180 cmd.depth,
3181 true, Some(cmd.command_id),
3183 cmd.params.clone(),
3184 )),
3185 },
3186 );
3187 }
3188
3189 let source = self
3190 .book_snapshot_sources
3191 .get(&cmd.instrument_id)
3192 .cloned()
3193 .expect("snapshot source command must exist after increment");
3194 self.subscribe_book_snapshot_source(&source.command, source.client_command);
3195
3196 Ok(())
3197 }
3198
3199 fn subscribe_book_snapshot_source(
3200 &mut self,
3201 cmd: &SubscribeBookSnapshots,
3202 client_command: SubscribeCommand,
3203 ) {
3204 if let Some(client_id) = cmd.client_id.as_ref()
3205 && self.external_clients.contains(client_id)
3206 {
3207 if self.config.debug {
3208 log::debug!("Skipping subscribe command for external client {client_id}: {cmd:?}");
3209 }
3210 return;
3211 }
3212
3213 log::debug!(
3214 "Forwarding BookSnapshots as BookDeltas for {}, client_id={:?}, venue={:?}",
3215 cmd.instrument_id,
3216 cmd.client_id,
3217 cmd.venue,
3218 );
3219
3220 if let Some(client) = self.get_command_client(cmd.client_id.as_ref(), cmd.venue.as_ref()) {
3221 log::debug!(
3222 "Calling client.execute_subscribe for BookDeltas: {}",
3223 cmd.instrument_id
3224 );
3225 client.execute_subscribe(client_command);
3226 } else {
3227 log::error!(
3228 "Cannot handle command: no client found for client_id={:?}, venue={:?}",
3229 cmd.client_id,
3230 cmd.venue,
3231 );
3232 }
3233 }
3234
3235 fn subscribe_bars(&mut self, cmd: &SubscribeBars) -> anyhow::Result<()> {
3236 match cmd.bar_type.aggregation_source() {
3237 AggregationSource::Internal => self.start_bar_aggregation(cmd)?,
3238 AggregationSource::External => {
3239 if cmd.bar_type.instrument_id().is_synthetic() {
3240 anyhow::bail!(
3241 "Cannot subscribe for externally aggregated synthetic instrument bar data"
3242 );
3243 }
3244 }
3245 }
3246
3247 Ok(())
3248 }
3249
3250 fn subscribe_synthetic_quotes(&mut self, instrument_id: InstrumentId) {
3251 let synthetic = match self.cache.borrow().try_synthetic(&instrument_id).cloned() {
3252 Ok(synthetic) => synthetic,
3253 Err(e) => {
3254 log::error!("Cannot subscribe to `QuoteTick` data for synthetic instrument: {e}");
3255 return;
3256 }
3257 };
3258
3259 if let Some(owners) = self.subscribed_synthetic_quotes.get_mut(&instrument_id) {
3260 *owners += 1;
3261 return;
3262 }
3263 self.subscribed_synthetic_quotes.insert(instrument_id, 1);
3264
3265 for component_id in &synthetic.components {
3266 let synthetics = self.synthetic_quote_feeds.entry(*component_id).or_default();
3267 if !synthetics
3268 .iter()
3269 .any(|registered| registered.id == synthetic.id)
3270 {
3271 synthetics.push(synthetic.clone());
3272 }
3273 }
3274 }
3275
3276 fn subscribe_synthetic_trades(&mut self, instrument_id: InstrumentId) {
3277 let synthetic = match self.cache.borrow().try_synthetic(&instrument_id).cloned() {
3278 Ok(synthetic) => synthetic,
3279 Err(e) => {
3280 log::error!("Cannot subscribe to `TradeTick` data for synthetic instrument: {e}");
3281 return;
3282 }
3283 };
3284
3285 if let Some(owners) = self.subscribed_synthetic_trades.get_mut(&instrument_id) {
3286 *owners += 1;
3287 return;
3288 }
3289 self.subscribed_synthetic_trades.insert(instrument_id, 1);
3290
3291 for component_id in &synthetic.components {
3292 let synthetics = self.synthetic_trade_feeds.entry(*component_id).or_default();
3293 if !synthetics
3294 .iter()
3295 .any(|registered| registered.id == synthetic.id)
3296 {
3297 synthetics.push(synthetic.clone());
3298 }
3299 }
3300 }
3301
3302 fn is_spread_quote_command(
3303 &self,
3304 instrument_id: InstrumentId,
3305 params: Option<&Params>,
3306 ) -> bool {
3307 if !params
3308 .and_then(|params| params.get_bool("aggregate_spread_quotes"))
3309 .unwrap_or(false)
3310 {
3311 return false;
3312 }
3313
3314 self.cache
3315 .borrow()
3316 .instrument(&instrument_id)
3317 .is_some_and(InstrumentAny::is_spread)
3318 }
3319
3320 fn subscribe_spread_quotes(&mut self, cmd: &SubscribeQuotes) {
3321 if let Some(state) = self.spread_quote_states.get_mut(&cmd.instrument_id) {
3322 state.owners += 1;
3323 let sources = state.sources.clone();
3324 for source in sources {
3325 self.execute(DataCommand::Subscribe(source));
3326 }
3327 return;
3328 }
3329
3330 let Some(instrument) = self.cache.borrow().instrument(&cmd.instrument_id).cloned() else {
3331 log::error!(
3332 "Cannot create spread quote aggregator: no instrument found for {}",
3333 cmd.instrument_id,
3334 );
3335 return;
3336 };
3337 let Some(legs) = spread_instrument_legs(&instrument) else {
3338 log::error!(
3339 "Cannot create spread quote aggregator: invalid spread legs for {}",
3340 cmd.instrument_id,
3341 );
3342 return;
3343 };
3344
3345 if legs.len() <= 1 {
3346 log::error!(
3347 "Cannot create spread quote aggregator: spread instrument {} should have more than one leg",
3348 cmd.instrument_id,
3349 );
3350 return;
3351 }
3352
3353 let cache = self.cache.clone();
3354 let handler = Box::new(move |quote: QuoteTick| {
3355 let exchange_endpoint = format!(
3356 "SimulatedExchange.process_new_quote.{}",
3357 quote.instrument_id.venue
3358 );
3359 let exchange_endpoint = exchange_endpoint.into();
3360 if msgbus::has_quote_endpoint(exchange_endpoint) {
3361 msgbus::send_quote(exchange_endpoint, "e);
3362 }
3363
3364 if let Err(e) = cache.borrow_mut().add_quote(quote) {
3365 log_error_on_cache_insert(&e);
3366 }
3367 let topic = switchboard::get_quotes_topic(quote.instrument_id);
3368 msgbus::publish_quote(topic, "e);
3369 });
3370 let aggregator = Rc::new(RefCell::new(SpreadQuoteAggregator::new(
3371 cmd.instrument_id,
3372 &legs,
3373 matches!(
3374 instrument,
3375 InstrumentAny::FuturesSpread(_) | InstrumentAny::CryptoFuturesSpread(_)
3376 ),
3377 instrument.price_precision(),
3378 instrument.size_precision(),
3379 handler,
3380 self.clock.clone(),
3381 false,
3382 spread_quote_update_interval_seconds(cmd.params.as_ref()),
3383 cmd.params
3384 .as_ref()
3385 .and_then(|params| params.get_u64("quote_build_delay"))
3386 .unwrap_or(0),
3387 cmd.params
3388 .as_ref()
3389 .and_then(|params| params.get_bool("disable_vega_pricing"))
3390 .unwrap_or(false),
3391 cmd.params
3392 .as_ref()
3393 .and_then(|params| params.get_u64("vega_pricing_timeout_seconds"))
3394 .unwrap_or(60),
3395 None,
3396 None,
3397 )));
3398
3399 let mut handlers = Vec::with_capacity(legs.len());
3400 for (leg_id, _) in &legs {
3401 let topic = switchboard::get_quotes_topic(*leg_id);
3402 let handler = TypedHandler::new(SpreadQuoteHandler::new(
3403 &aggregator,
3404 cmd.instrument_id,
3405 *leg_id,
3406 ));
3407 msgbus::subscribe_quotes(topic.into(), handler.clone(), Some(BAR_AGGREGATOR_PRIORITY));
3408 handlers.push((*leg_id, handler));
3409 }
3410
3411 aggregator
3412 .borrow_mut()
3413 .start_timer(Some(aggregator.clone()));
3414 aggregator.borrow_mut().set_running(true);
3415 let source_commands = legs
3416 .into_iter()
3417 .map(|(leg_id, _)| {
3418 SubscribeCommand::Quotes(SubscribeQuotes::new(
3419 leg_id,
3420 cmd.client_id,
3421 cmd.venue,
3422 UUID4::new(),
3423 cmd.ts_init,
3424 Some(cmd.command_id),
3425 cmd.params.clone(),
3426 ))
3427 })
3428 .collect::<Vec<_>>();
3429 self.spread_quote_states.insert(
3430 cmd.instrument_id,
3431 SpreadQuoteState {
3432 aggregator,
3433 handlers,
3434 owners: 1,
3435 command: cmd.clone(),
3436 sources: source_commands.clone(),
3437 },
3438 );
3439
3440 for source_command in source_commands {
3441 self.execute(DataCommand::Subscribe(source_command));
3442 }
3443 }
3444
3445 fn unsubscribe_spread_quotes(&mut self, cmd: &UnsubscribeQuotes) {
3446 let Some(state) = self.spread_quote_states.get_mut(&cmd.instrument_id) else {
3447 log::warn!(
3448 "Cannot unsubscribe spread quotes for {}: not subscribed",
3449 cmd.instrument_id,
3450 );
3451 return;
3452 };
3453
3454 if state.owners > 1 {
3455 state.owners -= 1;
3456 return;
3457 }
3458
3459 let Some((subscribe, leg_ids)) = self.stop_spread_quote_aggregation(cmd.instrument_id)
3460 else {
3461 return;
3462 };
3463
3464 for leg_id in leg_ids {
3465 let unsubscribe = UnsubscribeQuotes::new(
3466 leg_id,
3467 subscribe.client_id,
3468 subscribe.venue,
3469 UUID4::new(),
3470 cmd.ts_init,
3471 Some(subscribe.command_id),
3472 subscribe.params.clone(),
3473 );
3474 self.execute(DataCommand::Unsubscribe(UnsubscribeCommand::Quotes(
3475 unsubscribe,
3476 )));
3477 }
3478 }
3479
3480 fn stop_spread_quote_aggregation(
3481 &mut self,
3482 spread_instrument_id: InstrumentId,
3483 ) -> Option<(SubscribeQuotes, Vec<InstrumentId>)> {
3484 let Some(state) = self.spread_quote_states.remove(&spread_instrument_id) else {
3485 log::warn!("Cannot stop spread quote aggregation: no state for {spread_instrument_id}");
3486 return None;
3487 };
3488
3489 state.aggregator.borrow_mut().stop_timer();
3490 state.aggregator.borrow_mut().set_running(false);
3491
3492 let mut leg_ids = Vec::with_capacity(state.handlers.len());
3493 for (leg_id, handler) in state.handlers {
3494 let topic = switchboard::get_quotes_topic(leg_id);
3495 msgbus::unsubscribe_quotes(topic.into(), &handler);
3496 leg_ids.push(leg_id);
3497 }
3498
3499 Some((state.command, leg_ids))
3500 }
3501
3502 fn unsubscribe_book_deltas(&mut self, cmd: &UnsubscribeBookDeltas) -> bool {
3503 match self.decrement_book_delta_subscription(cmd.instrument_id, cmd.client_id, cmd.venue) {
3504 BookDeltasUnsubscribeResult::NotSubscribed => {
3505 log::warn!("Cannot unsubscribe from `OrderBookDeltas` data: not subscribed");
3506 return false;
3507 }
3508 BookDeltasUnsubscribeResult::Decremented => return false,
3509 BookDeltasUnsubscribeResult::Removed => {}
3510 }
3511
3512 self.maintain_book_updater(&cmd.instrument_id);
3513 true
3514 }
3515
3516 fn unsubscribe_book_depth10(&mut self, cmd: &UnsubscribeBookDepth10) -> bool {
3517 match self.decrement_book_depth10_subscription(cmd.instrument_id, cmd.client_id, cmd.venue)
3518 {
3519 BookDeltasUnsubscribeResult::NotSubscribed => {
3520 log::warn!("Cannot unsubscribe from `OrderBookDepth10` data: not subscribed");
3521 return false;
3522 }
3523 BookDeltasUnsubscribeResult::Decremented => return false,
3524 BookDeltasUnsubscribeResult::Removed => {}
3525 }
3526
3527 self.maintain_book_updater(&cmd.instrument_id);
3528
3529 true
3530 }
3531
3532 fn unsubscribe_book_snapshots(&mut self, cmd: &UnsubscribeBookSnapshots) {
3533 match self.decrement_book_snapshot_subscription(cmd.instrument_id, cmd.interval_ms) {
3534 BookSnapshotUnsubscribeResult::NotSubscribed => {
3535 log::warn!("Cannot unsubscribe from `OrderBook` snapshots: not subscribed");
3536 return;
3537 }
3538 BookSnapshotUnsubscribeResult::Decremented => return,
3539 BookSnapshotUnsubscribeResult::Removed => {}
3540 }
3541
3542 if self.has_book_snapshot_subscriptions(&cmd.instrument_id) {
3543 return;
3544 }
3545
3546 self.maintain_book_updater(&cmd.instrument_id);
3547
3548 let Some(source) = self.book_snapshot_sources.remove(&cmd.instrument_id) else {
3549 log::error!(
3550 "Cannot release order book snapshot source for {}: command not retained",
3551 cmd.instrument_id,
3552 );
3553 return;
3554 };
3555
3556 if let Some(client_id) = source.command.client_id.as_ref()
3557 && self.external_clients.contains(client_id)
3558 {
3559 return;
3560 }
3561
3562 if let Some(client) = self.get_command_client(
3563 source.command.client_id.as_ref(),
3564 source.command.venue.as_ref(),
3565 ) {
3566 let deltas_cmd = UnsubscribeBookDeltas::new(
3567 source.command.instrument_id,
3568 source.command.client_id,
3569 source.command.venue,
3570 UUID4::new(),
3571 cmd.ts_init,
3572 Some(source.command.command_id),
3573 source.command.params,
3574 );
3575 client.execute_unsubscribe(&UnsubscribeCommand::BookDeltas(deltas_cmd));
3576 }
3577 }
3578
3579 fn unsubscribe_bars(&mut self, cmd: &UnsubscribeBars) {
3580 let bar_type = cmd.bar_type;
3581
3582 let topic = switchboard::get_bars_topic(bar_type.standard());
3584 if msgbus::exact_subscriber_count_bars(topic) > 0 {
3585 return;
3586 }
3587
3588 let retained = self
3589 .subscriptions_bar_aggregation
3590 .get(&bar_type.standard())
3591 .map(|subscription| subscription.command.clone());
3592 let command = retained.map_or_else(
3593 || cmd.clone(),
3594 |subscribe| {
3595 UnsubscribeBars::new(
3596 subscribe.bar_type,
3597 subscribe.client_id,
3598 subscribe.venue,
3599 cmd.command_id,
3600 cmd.ts_init,
3601 Some(subscribe.command_id),
3602 subscribe.params,
3603 )
3604 },
3605 );
3606
3607 if self
3608 .bar_aggregators
3609 .contains_key(&bar_aggregator_key(bar_type, None))
3610 {
3611 match self.stop_bar_aggregator(bar_type, None) {
3612 Ok(()) => {
3613 self.subscriptions_bar_aggregation
3614 .remove(&bar_type.standard());
3615 self.unsubscribe_bar_aggregator(&command);
3616 }
3617 Err(e) => log::error!("Error stopping bar aggregator for {bar_type}: {e}"),
3618 }
3619 }
3620
3621 if bar_type.is_composite() {
3623 let source_type = bar_type.composite();
3624 let source_topic = switchboard::get_bars_topic(source_type);
3625 if msgbus::exact_subscriber_count_bars(source_topic) == 0
3626 && self
3627 .bar_aggregators
3628 .contains_key(&bar_aggregator_key(source_type, None))
3629 {
3630 match self.stop_bar_aggregator(source_type, None) {
3631 Ok(()) => self.unsubscribe_bar_aggregator(&UnsubscribeBars::new(
3634 source_type,
3635 command.client_id,
3636 command.venue,
3637 UUID4::new(),
3638 command.ts_init,
3639 Some(command.command_id),
3640 command.params.clone(),
3641 )),
3642 Err(e) => {
3643 log::error!("Error stopping source bar aggregator for {source_type}: {e}");
3644 }
3645 }
3646 }
3647 }
3648 }
3649
3650 fn unsubscribe_synthetic_quotes(&mut self, instrument_id: InstrumentId) {
3651 let Some(owners) = self.subscribed_synthetic_quotes.get_mut(&instrument_id) else {
3652 log::warn!("Cannot unsubscribe from synthetic `QuoteTick` data: not subscribed");
3653 return;
3654 };
3655
3656 if *owners > 1 {
3657 *owners -= 1;
3658 return;
3659 }
3660 self.subscribed_synthetic_quotes.remove(&instrument_id);
3661
3662 self.synthetic_quote_feeds.retain(|_, synthetics| {
3663 synthetics.retain(|synthetic| synthetic.id != instrument_id);
3664 !synthetics.is_empty()
3665 });
3666 }
3667
3668 fn unsubscribe_synthetic_trades(&mut self, instrument_id: InstrumentId) {
3669 let Some(owners) = self.subscribed_synthetic_trades.get_mut(&instrument_id) else {
3670 log::warn!("Cannot unsubscribe from synthetic `TradeTick` data: not subscribed");
3671 return;
3672 };
3673
3674 if *owners > 1 {
3675 *owners -= 1;
3676 return;
3677 }
3678 self.subscribed_synthetic_trades.remove(&instrument_id);
3679
3680 self.synthetic_trade_feeds.retain(|_, synthetics| {
3681 synthetics.retain(|synthetic| synthetic.id != instrument_id);
3682 !synthetics.is_empty()
3683 });
3684 }
3685
3686 fn subscribe_option_chain(&mut self, cmd: &SubscribeOptionChain) {
3687 self.drain_deferred_commands();
3688 let series_id = cmd.series_id;
3689 self.stop_option_chain_greeks_bootstrap(series_id);
3690
3691 if let Some(old) = self.option_chain_managers.remove(&series_id) {
3693 log::info!("Re-subscribing option chain for {series_id}, tearing down previous");
3694 let (active_ids, old_venue, old_client_id) = {
3695 let old = old.borrow();
3696 let active_ids = old
3697 .all_instrument_ids()
3698 .into_iter()
3699 .filter(|instrument_id| old.is_instrument_active(instrument_id))
3700 .collect::<Vec<_>>();
3701 (active_ids, old.venue(), old.client_id())
3702 };
3703 old.borrow_mut().teardown(&self.clock);
3704 self.forward_option_chain_unsubscribes(&active_ids, old_venue, old_client_id);
3705 }
3706
3707 self.cancel_pending_option_chain_requests(Some(series_id));
3708
3709 if !matches!(cmd.strike_range, StrikeRange::Fixed(_)) {
3712 let resolved_client_id = self
3713 .get_client(cmd.client_id.as_ref(), Some(&series_id.venue))
3714 .map(|c| c.client_id);
3715
3716 if let Some(client_id) = resolved_client_id {
3717 let request_id = UUID4::new();
3718 let ts_init = self.clock.borrow().timestamp_ns();
3719
3720 let sample_instrument_id = {
3721 let cache = self.cache.borrow();
3722 cache
3723 .instruments(&series_id.venue, Some(&series_id.underlying))
3724 .iter()
3725 .filter(|i| {
3726 i.instrument_class() == InstrumentClass::Option
3727 && i.expiration_ns() == Some(series_id.expiration_ns)
3728 && i.settlement_currency().code == series_id.settlement_currency
3729 })
3730 .min_by_key(|i| i.id())
3731 .map(|i| i.id())
3732 };
3733
3734 if let Some(instrument_id) = sample_instrument_id {
3735 let request = RequestOptionChainReferencePrice::new(
3736 series_id,
3737 instrument_id,
3738 Some(client_id),
3739 request_id,
3740 ts_init,
3741 None,
3742 );
3743 let deadline_ns = ts_init.saturating_add(OPTION_CHAIN_REFERENCE_PRICE_TIMEOUT);
3744 self.pending_option_chain_requests.insert(
3745 request_id,
3746 PendingOptionChainRequest {
3747 command: cmd.clone(),
3748 sample_instrument_id: instrument_id,
3749 deadline_ns,
3750 },
3751 );
3752
3753 if !self.schedule_option_chain_reference_price_timeout() {
3754 self.bootstrap_all_pending_option_chains();
3755 return;
3756 }
3757
3758 let req_cmd = RequestCommand::OptionChainReferencePrice(request);
3759 if let Err(e) = self.execute_request(req_cmd) {
3760 log::warn!(
3761 "Failed to request option-chain reference price for {series_id}: {e}"
3762 );
3763
3764 if let Some(pending) =
3765 self.pending_option_chain_requests.remove(&request_id)
3766 {
3767 self.maintain_option_chain_reference_price_timeout();
3768 self.create_option_chain_manager_with_greeks_bootstrap(pending);
3769 }
3770 }
3771
3772 return;
3773 }
3774 }
3775 }
3776
3777 self.create_option_chain_manager(cmd, None);
3778 }
3779
3780 fn schedule_option_chain_reference_price_timeout(&self) -> bool {
3781 let Some(deadline_ns) = self
3782 .pending_option_chain_requests
3783 .values()
3784 .map(|pending| pending.deadline_ns)
3785 .min()
3786 else {
3787 self.clock
3788 .borrow_mut()
3789 .cancel_timer(OPTION_CHAIN_REFERENCE_PRICE_TIMEOUT_TIMER);
3790 return true;
3791 };
3792
3793 if self
3794 .clock
3795 .borrow()
3796 .next_time_ns(OPTION_CHAIN_REFERENCE_PRICE_TIMEOUT_TIMER)
3797 == Some(deadline_ns)
3798 {
3799 return true;
3800 }
3801
3802 let Some(bootstrapper) = self.option_chain_bootstrapper.clone() else {
3803 log::error!(
3804 "Cannot schedule option-chain reference price timeout: data engine message bus handlers are not registered"
3805 );
3806 return false;
3807 };
3808
3809 let callback_fn: Rc<dyn Fn(TimeEvent)> = Rc::new(move |_| {
3810 bootstrapper.handle_timeout();
3811 });
3812 let callback = TimeEventCallback::from(callback_fn);
3813
3814 if let Err(e) = self.clock.borrow_mut().set_time_alert_ns(
3815 OPTION_CHAIN_REFERENCE_PRICE_TIMEOUT_TIMER,
3816 deadline_ns,
3817 Some(callback),
3818 Some(true),
3819 ) {
3820 log::error!("Failed to schedule option-chain reference price timeout: {e}");
3821 return false;
3822 }
3823
3824 true
3825 }
3826
3827 fn maintain_option_chain_reference_price_timeout(&mut self) {
3828 if !self.schedule_option_chain_reference_price_timeout() {
3829 self.bootstrap_all_pending_option_chains();
3830 }
3831 }
3832
3833 fn bootstrap_all_pending_option_chains(&mut self) {
3834 self.clock
3835 .borrow_mut()
3836 .cancel_timer(OPTION_CHAIN_REFERENCE_PRICE_TIMEOUT_TIMER);
3837 let mut pending: Vec<PendingOptionChainRequest> =
3838 mem::take(&mut self.pending_option_chain_requests)
3839 .into_values()
3840 .collect();
3841 pending.sort_unstable_by_key(|pending| pending.command.series_id);
3842 for pending in pending {
3843 self.create_option_chain_manager_with_greeks_bootstrap(pending);
3844 }
3845 }
3846
3847 fn cancel_pending_option_chain_requests(&mut self, series_id: Option<OptionSeriesId>) -> bool {
3848 let request_ids: Vec<UUID4> = self
3849 .pending_option_chain_requests
3850 .iter()
3851 .filter_map(|(request_id, pending)| {
3852 (series_id.is_none() || series_id == Some(pending.command.series_id))
3853 .then_some(*request_id)
3854 })
3855 .collect();
3856
3857 for request_id in &request_ids {
3858 self.pending_option_chain_requests.remove(request_id);
3859 }
3860 self.maintain_option_chain_reference_price_timeout();
3861
3862 !request_ids.is_empty()
3863 }
3864
3865 fn handle_option_chain_reference_price_timeout(&mut self) {
3866 let now_ns = self.clock.borrow().timestamp_ns();
3867 let mut requests: Vec<(OptionSeriesId, UUID4)> = self
3868 .pending_option_chain_requests
3869 .iter()
3870 .filter_map(|(request_id, pending)| {
3871 (pending.deadline_ns <= now_ns).then_some((pending.command.series_id, *request_id))
3872 })
3873 .collect();
3874 requests.sort_unstable_by_key(|(series_id, _)| *series_id);
3875
3876 for (_, request_id) in requests {
3877 let Some(pending) = self.pending_option_chain_requests.remove(&request_id) else {
3878 continue;
3879 };
3880 let series_id = pending.command.series_id;
3881 log::warn!(
3882 "Option-chain reference price request timed out for {series_id}; bootstrapping from live data"
3883 );
3884 self.create_option_chain_manager_with_greeks_bootstrap(pending);
3885 }
3886
3887 self.maintain_option_chain_reference_price_timeout();
3888 }
3889
3890 fn create_option_chain_manager(
3892 &mut self,
3893 cmd: &SubscribeOptionChain,
3894 initial_atm_price: Option<Price>,
3895 ) -> Rc<RefCell<OptionChainManager>> {
3896 let series_id = cmd.series_id;
3897 let cache = self.cache.clone();
3898 let clock = self.clock.clone();
3899 let priority = self.msgbus_priority;
3900 let deferred_cmd_queue = self.deferred_cmd_queue.clone();
3901
3902 let manager_rc = {
3903 let client = self.get_command_client(cmd.client_id.as_ref(), Some(&series_id.venue));
3904 OptionChainManager::create_and_setup(
3905 series_id,
3906 &cache,
3907 cmd,
3908 &clock,
3909 priority,
3910 client,
3911 initial_atm_price,
3912 deferred_cmd_queue,
3913 )
3914 };
3915
3916 for id in manager_rc.borrow().all_instrument_ids() {
3918 self.option_chain_instrument_index.insert(id, series_id);
3919 }
3920
3921 self.option_chain_managers
3922 .insert(series_id, manager_rc.clone());
3923 manager_rc
3924 }
3925
3926 fn create_option_chain_manager_with_greeks_bootstrap(
3927 &mut self,
3928 pending: PendingOptionChainRequest,
3929 ) {
3930 let cmd = pending.command;
3931 let series_id = cmd.series_id;
3932 let instrument_id = pending.sample_instrument_id;
3933 let manager = self.create_option_chain_manager(&cmd, None);
3934 let Some(client_id) = manager.borrow().client_id() else {
3935 return;
3936 };
3937
3938 let ownership_handler = TypedHandler::from(|_: &OptionGreeks| {});
3939 let topic = switchboard::get_option_greeks_topic(instrument_id);
3940 msgbus::subscribe_option_greeks(
3941 topic.into(),
3942 ownership_handler.clone(),
3943 Some(self.msgbus_priority),
3944 );
3945 let replaced = self.option_chain_greeks_bootstraps.insert(
3946 series_id,
3947 OptionChainGreeksBootstrap {
3948 instrument_id,
3949 client_id,
3950 venue: series_id.venue,
3951 ownership_handler,
3952 },
3953 );
3954 debug_assert!(
3955 replaced.is_none(),
3956 "Invariant: each option series has at most one Greeks bootstrap subscription"
3957 );
3958
3959 let ts_init = self.clock.borrow().timestamp_ns();
3960
3961 if let Err(e) =
3962 self.execute_subscribe(SubscribeCommand::OptionGreeks(SubscribeOptionGreeks::new(
3963 instrument_id,
3964 Some(client_id),
3965 Some(series_id.venue),
3966 UUID4::new(),
3967 ts_init,
3968 None,
3969 None,
3970 )))
3971 {
3972 log::error!("Failed to subscribe option-chain bootstrap Greeks for {series_id}: {e}");
3973 }
3974 }
3975
3976 fn finish_option_chain_greeks_bootstrap(
3977 &mut self,
3978 series_id: OptionSeriesId,
3979 manager: &Rc<RefCell<OptionChainManager>>,
3980 ) {
3981 let sample_is_active = self
3982 .option_chain_greeks_bootstraps
3983 .get(&series_id)
3984 .is_some_and(|bootstrap| {
3985 manager
3986 .borrow()
3987 .is_instrument_active(&bootstrap.instrument_id)
3988 });
3989 let Some(bootstrap) = self.remove_option_chain_greeks_bootstrap(series_id) else {
3990 return;
3991 };
3992
3993 if !sample_is_active {
3994 self.release_option_chain_greeks_bootstrap(&bootstrap);
3995 }
3996 }
3997
3998 fn stop_option_chain_greeks_bootstrap(&mut self, series_id: OptionSeriesId) -> bool {
3999 let Some(bootstrap) = self.remove_option_chain_greeks_bootstrap(series_id) else {
4000 return false;
4001 };
4002 self.release_option_chain_greeks_bootstrap(&bootstrap);
4003 true
4004 }
4005
4006 fn remove_option_chain_greeks_bootstrap(
4007 &mut self,
4008 series_id: OptionSeriesId,
4009 ) -> Option<OptionChainGreeksBootstrap> {
4010 let bootstrap = self.option_chain_greeks_bootstraps.remove(&series_id)?;
4011 let topic = switchboard::get_option_greeks_topic(bootstrap.instrument_id);
4012 msgbus::unsubscribe_option_greeks(topic.into(), &bootstrap.ownership_handler);
4013 Some(bootstrap)
4014 }
4015
4016 fn release_option_chain_greeks_bootstrap(&mut self, bootstrap: &OptionChainGreeksBootstrap) {
4017 let cmd = UnsubscribeCommand::OptionGreeks(UnsubscribeOptionGreeks::new(
4018 bootstrap.instrument_id,
4019 Some(bootstrap.client_id),
4020 Some(bootstrap.venue),
4021 UUID4::new(),
4022 self.clock.borrow().timestamp_ns(),
4023 None,
4024 None,
4025 ));
4026
4027 if let Err(e) = self.execute_unsubscribe(&cmd) {
4028 log::error!(
4029 "Failed to unsubscribe option-chain bootstrap Greeks for {}: {e}",
4030 bootstrap.instrument_id
4031 );
4032 }
4033 }
4034
4035 fn clear_option_chain_greeks_bootstraps(&mut self) {
4036 let bootstraps = mem::take(&mut self.option_chain_greeks_bootstraps);
4037 for bootstrap in bootstraps.into_values() {
4038 let topic = switchboard::get_option_greeks_topic(bootstrap.instrument_id);
4039 msgbus::unsubscribe_option_greeks(topic.into(), &bootstrap.ownership_handler);
4040 }
4041 }
4042
4043 fn unsubscribe_option_chain(&mut self, cmd: &UnsubscribeOptionChain) {
4044 self.drain_deferred_commands();
4045 let series_id = cmd.series_id;
4046 let topic = switchboard::get_option_chain_topic(series_id);
4047 if msgbus::exact_subscriber_count_option_chain(topic) > 0 {
4048 return;
4049 }
4050
4051 let canceled_pending = self.cancel_pending_option_chain_requests(Some(series_id));
4052 let canceled_greeks_bootstrap = self.stop_option_chain_greeks_bootstrap(series_id);
4053
4054 let Some(manager_rc) = self.option_chain_managers.remove(&series_id) else {
4055 if !canceled_pending && !canceled_greeks_bootstrap {
4056 log::warn!("Cannot unsubscribe option chain for {series_id}: not subscribed");
4057 }
4058 return;
4059 };
4060
4061 let (all_ids, active_ids, venue, client_id) = {
4063 let manager = manager_rc.borrow();
4064 let all_ids = manager.all_instrument_ids();
4065 let active_ids = all_ids
4066 .iter()
4067 .filter(|instrument_id| manager.is_instrument_active(instrument_id))
4068 .copied()
4069 .collect::<Vec<_>>();
4070 (all_ids, active_ids, manager.venue(), manager.client_id())
4071 };
4072
4073 for id in &all_ids {
4075 self.option_chain_instrument_index.remove(id);
4076 }
4077
4078 manager_rc.borrow_mut().teardown(&self.clock);
4079
4080 self.forward_option_chain_unsubscribes(&active_ids, venue, client_id);
4082
4083 log::info!("Unsubscribed option chain for {series_id}");
4084 }
4085
4086 fn forward_option_chain_unsubscribes(
4088 &mut self,
4089 instrument_ids: &[InstrumentId],
4090 venue: Venue,
4091 client_id: Option<ClientId>,
4092 ) {
4093 let ts_init = self.clock.borrow().timestamp_ns();
4094
4095 for instrument_id in instrument_ids {
4096 let quote_cmd = UnsubscribeCommand::Quotes(UnsubscribeQuotes::new(
4097 *instrument_id,
4098 client_id,
4099 Some(venue),
4100 UUID4::new(),
4101 ts_init,
4102 None,
4103 None,
4104 ));
4105 let greeks_cmd = UnsubscribeCommand::OptionGreeks(UnsubscribeOptionGreeks::new(
4106 *instrument_id,
4107 client_id,
4108 Some(venue),
4109 UUID4::new(),
4110 ts_init,
4111 None,
4112 None,
4113 ));
4114 let status_cmd =
4115 UnsubscribeCommand::InstrumentStatus(UnsubscribeInstrumentStatus::new(
4116 *instrument_id,
4117 client_id,
4118 Some(venue),
4119 UUID4::new(),
4120 ts_init,
4121 None,
4122 None,
4123 ));
4124
4125 for cmd in ["e_cmd, &greeks_cmd, &status_cmd] {
4126 if let Err(e) = self.execute_unsubscribe(cmd) {
4127 log::error!("Failed to execute option chain unsubscribe: {e}");
4128 }
4129 }
4130 }
4131 }
4132
4133 fn maintain_book_updater(&mut self, instrument_id: &InstrumentId) {
4134 let is_parent = self
4141 .book_deltas_parent_expansions
4142 .contains_key(instrument_id)
4143 || self
4144 .book_depth10_parent_expansions
4145 .contains_key(instrument_id);
4146 let target_ids: Vec<InstrumentId> = if is_parent {
4147 let mut set: AHashSet<InstrumentId> = AHashSet::new();
4148
4149 if let Some(expansion) = self.book_deltas_parent_expansions.get(instrument_id) {
4150 set.extend(expansion.iter().copied());
4151 }
4152
4153 if let Some(expansion) = self.book_depth10_parent_expansions.get(instrument_id) {
4154 set.extend(expansion.iter().copied());
4155 }
4156
4157 if set.is_empty() {
4158 return;
4159 }
4160
4161 set.into_iter().collect()
4162 } else {
4163 vec![*instrument_id]
4164 };
4165
4166 if is_parent {
4167 let parent_still_needs_deltas = self.has_book_delta_subscriptions(instrument_id)
4172 || self.has_book_depth10_subscriptions(instrument_id)
4173 || self.has_book_snapshot_subscriptions(instrument_id);
4174 let parent_still_needs_depth10 = self.has_book_depth10_subscriptions(instrument_id)
4175 || self.has_book_snapshot_subscriptions(instrument_id);
4176
4177 if !parent_still_needs_deltas {
4178 self.book_deltas_parent_expansions.remove(instrument_id);
4179 }
4180
4181 if !parent_still_needs_depth10 {
4182 self.book_depth10_parent_expansions.remove(instrument_id);
4183 }
4184 }
4185
4186 for target_id in &target_ids {
4187 let wants_deltas = self.is_underlying_wanted_for_deltas(target_id);
4188 let wants_depth10 = self.is_underlying_wanted_for_depth10(target_id);
4189
4190 let Some(updater) = self.book_updaters.get(target_id).cloned() else {
4191 continue;
4192 };
4193
4194 let deltas_handler: TypedHandler<OrderBookDeltas> = TypedHandler::new(updater.clone());
4195 let depth_handler: TypedHandler<OrderBookDepth10> = TypedHandler::new(updater);
4196
4197 if !wants_deltas {
4198 let topic = switchboard::get_book_deltas_topic(*target_id);
4199 msgbus::unsubscribe_book_deltas(topic.into(), &deltas_handler);
4200 }
4201
4202 if !wants_depth10 {
4203 let topic = switchboard::get_book_depth10_topic(*target_id);
4204 msgbus::unsubscribe_book_depth10(topic.into(), &depth_handler);
4205 }
4206
4207 if !wants_deltas && !wants_depth10 {
4208 self.book_updaters.remove(target_id);
4209 log::debug!("Removed BookUpdater for instrument ID {target_id}");
4210 }
4211 }
4212 }
4213
4214 fn has_book_snapshot_subscriptions(&self, instrument_id: &InstrumentId) -> bool {
4215 self.book_snapshot_counts
4216 .keys()
4217 .any(|(id, _)| id == instrument_id)
4218 }
4219
4220 fn has_book_delta_subscriptions(&self, instrument_id: &InstrumentId) -> bool {
4221 self.book_deltas_counts
4222 .keys()
4223 .any(|(id, _, _)| id == instrument_id)
4224 }
4225
4226 fn has_book_delta_subscription_key(
4227 &self,
4228 instrument_id: InstrumentId,
4229 client_id: Option<ClientId>,
4230 venue: Option<Venue>,
4231 ) -> bool {
4232 self.book_deltas_counts
4233 .contains_key(&(instrument_id, client_id, venue))
4234 }
4235
4236 fn has_book_depth10_subscriptions(&self, instrument_id: &InstrumentId) -> bool {
4237 self.book_depth10_counts
4238 .keys()
4239 .any(|(id, _, _)| id == instrument_id)
4240 }
4241
4242 fn has_book_depth10_subscription_key(
4243 &self,
4244 instrument_id: InstrumentId,
4245 client_id: Option<ClientId>,
4246 venue: Option<Venue>,
4247 ) -> bool {
4248 self.book_depth10_counts
4249 .contains_key(&(instrument_id, client_id, venue))
4250 }
4251
4252 fn increment_book_delta_subscription(
4253 &mut self,
4254 instrument_id: InstrumentId,
4255 client_id: Option<ClientId>,
4256 venue: Option<Venue>,
4257 ) {
4258 let key = (instrument_id, client_id, venue);
4259
4260 if let Some(count) = self.book_deltas_counts.get_mut(&key) {
4261 *count += 1;
4262 } else {
4263 self.book_deltas_counts.insert(key, 1);
4264 }
4265 }
4266
4267 fn decrement_book_delta_subscription(
4268 &mut self,
4269 instrument_id: InstrumentId,
4270 client_id: Option<ClientId>,
4271 venue: Option<Venue>,
4272 ) -> BookDeltasUnsubscribeResult {
4273 let key = (instrument_id, client_id, venue);
4274
4275 let Some(count) = self.book_deltas_counts.get_mut(&key) else {
4276 return BookDeltasUnsubscribeResult::NotSubscribed;
4277 };
4278
4279 if *count > 1 {
4280 *count -= 1;
4281 return BookDeltasUnsubscribeResult::Decremented;
4282 }
4283
4284 self.book_deltas_counts.shift_remove(&key);
4285 BookDeltasUnsubscribeResult::Removed
4286 }
4287
4288 fn increment_book_depth10_subscription(
4289 &mut self,
4290 instrument_id: InstrumentId,
4291 client_id: Option<ClientId>,
4292 venue: Option<Venue>,
4293 ) {
4294 let key = (instrument_id, client_id, venue);
4295 *self.book_depth10_counts.entry(key).or_insert(0) += 1;
4296 }
4297
4298 fn decrement_book_depth10_subscription(
4299 &mut self,
4300 instrument_id: InstrumentId,
4301 client_id: Option<ClientId>,
4302 venue: Option<Venue>,
4303 ) -> BookDeltasUnsubscribeResult {
4304 let key = (instrument_id, client_id, venue);
4305 let Some(count) = self.book_depth10_counts.get_mut(&key) else {
4306 return BookDeltasUnsubscribeResult::NotSubscribed;
4307 };
4308
4309 if *count > 1 {
4310 *count -= 1;
4311 return BookDeltasUnsubscribeResult::Decremented;
4312 }
4313
4314 self.book_depth10_counts.shift_remove(&key);
4315 BookDeltasUnsubscribeResult::Removed
4316 }
4317
4318 fn increment_book_snapshot_subscription(
4319 &mut self,
4320 cmd: &SubscribeBookSnapshots,
4321 parent: Option<(Ustr, InstrumentClass)>,
4322 ) -> bool {
4323 let key = (cmd.instrument_id, cmd.interval_ms);
4324
4325 if let Some(count) = self.book_snapshot_counts.get_mut(&key) {
4326 *count += 1;
4327 return false;
4328 }
4329
4330 self.book_snapshot_counts.insert(key, 1);
4331
4332 let snapshot_infos = if let Some(snapshot_infos) = self.book_intervals.get(&cmd.interval_ms)
4333 {
4334 snapshot_infos.clone()
4335 } else {
4336 let snapshot_infos = Rc::new(RefCell::new(IndexMap::new()));
4337 self.book_intervals
4338 .insert(cmd.interval_ms, snapshot_infos.clone());
4339 self.schedule_book_snapshotter(cmd.interval_ms, snapshot_infos.clone());
4340 snapshot_infos
4341 };
4342
4343 let topic = switchboard::get_book_snapshots_topic(cmd.instrument_id, cmd.interval_ms);
4344 let snap_info = BookSnapshotInfo {
4345 instrument_id: cmd.instrument_id,
4346 venue: cmd.instrument_id.venue,
4347 parent,
4348 topic,
4349 interval_ms: cmd.interval_ms,
4350 };
4351
4352 snapshot_infos
4353 .borrow_mut()
4354 .insert(cmd.instrument_id, snap_info);
4355
4356 true
4357 }
4358
4359 fn decrement_book_snapshot_subscription(
4360 &mut self,
4361 instrument_id: InstrumentId,
4362 interval_ms: NonZeroUsize,
4363 ) -> BookSnapshotUnsubscribeResult {
4364 let key = (instrument_id, interval_ms);
4365
4366 let Some(count) = self.book_snapshot_counts.get_mut(&key) else {
4367 return BookSnapshotUnsubscribeResult::NotSubscribed;
4368 };
4369
4370 if *count > 1 {
4371 *count -= 1;
4372 return BookSnapshotUnsubscribeResult::Decremented;
4373 }
4374
4375 self.book_snapshot_counts.shift_remove(&key);
4376
4377 let remove_interval = if let Some(snapshot_infos) = self.book_intervals.get(&interval_ms) {
4378 let mut snapshot_infos = snapshot_infos.borrow_mut();
4379 snapshot_infos.shift_remove(&instrument_id);
4380 snapshot_infos.is_empty()
4381 } else {
4382 false
4383 };
4384
4385 if remove_interval {
4386 self.book_intervals.remove(&interval_ms);
4387
4388 if let Some(snapshotter) = self.book_snapshotters.remove(&interval_ms) {
4389 let timer_name = snapshotter.timer_name;
4390 let mut clock = self.clock.borrow_mut();
4391 if clock.timer_exists(&timer_name) {
4392 clock.cancel_timer(&timer_name);
4393 }
4394 }
4395 }
4396
4397 BookSnapshotUnsubscribeResult::Removed
4398 }
4399
4400 fn schedule_book_snapshotter(
4401 &mut self,
4402 interval_ms: NonZeroUsize,
4403 snapshot_infos: BookSnapshotInfos,
4404 ) {
4405 let interval_ms_u64 =
4406 u64::try_from(interval_ms.get()).expect("Snapshot interval exceeds u64");
4407 let interval_ns = DurationNanos::from_millis(interval_ms_u64);
4408 let now_ns = self.clock.borrow().timestamp_ns();
4409 let start_time_ns = now_ns
4410 .floor(interval_ns)
4411 .checked_add(interval_ns)
4412 .expect("Book snapshot timer start exceeds UnixNanos range");
4413
4414 let snapshotter = Rc::new(BookSnapshotter::new(
4415 interval_ms,
4416 snapshot_infos,
4417 self.cache.clone(),
4418 ));
4419 let timer_name = snapshotter.timer_name;
4420 let snapshotter_callback = snapshotter.clone();
4421 let callback_fn: Rc<dyn Fn(TimeEvent)> =
4422 Rc::new(move |event| snapshotter_callback.snapshot(event));
4423 let callback = TimeEventCallback::from(callback_fn);
4424
4425 self.clock
4426 .borrow_mut()
4427 .set_timer_ns(
4428 &timer_name,
4429 interval_ns,
4430 Some(start_time_ns),
4431 None,
4432 Some(callback),
4433 None,
4434 None,
4435 )
4436 .expect(FAILED);
4437
4438 self.book_snapshotters.insert(interval_ms, snapshotter);
4439 }
4440
4441 fn handle_instrument_response(&self, instrument: InstrumentAny) {
4442 let mut cache = self.cache.as_ref().borrow_mut();
4443 if let Err(e) = cache.add_instrument(instrument) {
4444 log_error_on_cache_insert(&e);
4445 }
4446 }
4447
4448 fn handle_instruments(&self, instruments: &[InstrumentAny]) {
4449 let mut cache = self.cache.as_ref().borrow_mut();
4451 for instrument in instruments {
4452 if let Err(e) = cache.add_instrument(instrument.clone()) {
4453 log_error_on_cache_insert(&e);
4454 }
4455 }
4456 }
4457
4458 fn handle_quotes(&self, quotes: &[QuoteTick]) {
4459 if let Err(e) = self.cache.as_ref().borrow_mut().add_quotes(quotes) {
4460 log_error_on_cache_insert(&e);
4461 }
4462 }
4463
4464 fn handle_trades(&self, trades: &[TradeTick]) {
4465 if let Err(e) = self.cache.as_ref().borrow_mut().add_trades(trades) {
4466 log_error_on_cache_insert(&e);
4467 }
4468 }
4469
4470 fn handle_funding_rates(&self, funding_rates: &[FundingRateUpdate]) {
4471 if let Err(e) = self
4472 .cache
4473 .as_ref()
4474 .borrow_mut()
4475 .add_funding_rates(funding_rates)
4476 {
4477 log_error_on_cache_insert(&e);
4478 }
4479 }
4480
4481 fn handle_bars(&self, bars: &[Bar]) {
4482 if let Err(e) = self.cache.as_ref().borrow_mut().add_bars(bars) {
4483 log_error_on_cache_insert(&e);
4484 }
4485 }
4486
4487 fn cache_is_owned_by_live_subscription(&self, instrument_id: &InstrumentId) -> bool {
4490 self.book_updaters.contains_key(instrument_id)
4491 }
4492
4493 fn handle_book_response(&self, book: &OrderBook) {
4494 if self.cache_is_owned_by_live_subscription(&book.instrument_id) {
4495 log::debug!(
4496 "Skipping cache write for order book {}: live subscription owns the book",
4497 book.instrument_id,
4498 );
4499 return;
4500 }
4501
4502 log::debug!("Adding order book {} to cache", book.instrument_id);
4503
4504 if let Err(e) = self
4505 .cache
4506 .as_ref()
4507 .borrow_mut()
4508 .add_order_book(book.clone())
4509 {
4510 log_error_on_cache_insert(&e);
4511 }
4512 }
4513
4514 fn handle_book_deltas_response(&self, resp: &BookDeltasResponse) {
4515 if !self.cache_is_owned_by_live_subscription(&resp.instrument_id) {
4516 let mut cache = self.cache.as_ref().borrow_mut();
4517 if let Some(book) = cache.order_book_mut(&resp.instrument_id) {
4518 for delta in &resp.data {
4519 if let Err(e) = book.apply_delta(delta) {
4520 log::error!("Failed to apply historical delta to cache: {e}");
4521 }
4522 }
4523 } else {
4524 log::debug!(
4525 "Skipping cache write for {} historical deltas on {}: no cache book yet",
4526 resp.data.len(),
4527 resp.instrument_id,
4528 );
4529 }
4530 }
4531
4532 if resp.data.is_empty() {
4537 return;
4538 }
4539
4540 let topic = switchboard::get_pipeline_book_deltas_topic(resp.instrument_id);
4541 let mut frame: Vec<OrderBookDelta> = Vec::new();
4542
4543 for delta in &resp.data {
4544 frame.push(*delta);
4545 if RecordFlag::F_LAST.matches(delta.flags) {
4546 let batch = OrderBookDeltas::new(resp.instrument_id, std::mem::take(&mut frame));
4547 msgbus::publish_deltas(topic, &batch);
4548 }
4549 }
4550
4551 if !frame.is_empty() {
4552 let batch = OrderBookDeltas::new(resp.instrument_id, frame);
4553 msgbus::publish_deltas(topic, &batch);
4554 }
4555 }
4556
4557 fn handle_book_depth_response(&self, resp: &BookDepthResponse) {
4558 let topic = switchboard::get_pipeline_book_depth10_topic(resp.instrument_id);
4559
4560 for depth in &resp.data {
4561 msgbus::publish_depth10(topic, depth);
4562 }
4563 }
4564
4565 fn handle_option_chain_reference_price_response(
4566 &mut self,
4567 correlation_id: &UUID4,
4568 resp: &OptionChainReferencePriceResponse,
4569 ) {
4570 let Some(pending) = self.pending_option_chain_requests.get(correlation_id) else {
4571 log::debug!(
4572 "No pending option chain request for correlation_id={correlation_id}, ignoring"
4573 );
4574 return;
4575 };
4576
4577 if resp.series_id != pending.command.series_id {
4578 log::warn!(
4579 "Ignoring option-chain reference price response for {}: pending series is {}",
4580 resp.series_id,
4581 pending.command.series_id,
4582 );
4583 return;
4584 }
4585
4586 let pending = self
4587 .pending_option_chain_requests
4588 .remove(correlation_id)
4589 .expect("checked above");
4590 self.maintain_option_chain_reference_price_timeout();
4591 let series_id = pending.command.series_id;
4592
4593 if let Some(price) = resp.price {
4594 log::info!("Reference price for {series_id}: {price} (instant bootstrap)");
4595 } else {
4596 log::info!(
4597 "No reference price available for {series_id}, will bootstrap from live data",
4598 );
4599 }
4600
4601 if resp.price.is_some() {
4602 self.create_option_chain_manager(&pending.command, resp.price);
4603 } else {
4604 self.create_option_chain_manager_with_greeks_bootstrap(pending);
4605 }
4606 }
4607
4608 fn setup_book_updater(
4609 &mut self,
4610 instrument_id: &InstrumentId,
4611 book_type: BookType,
4612 only_deltas: bool,
4613 parent: Option<(Ustr, InstrumentClass)>,
4614 ) -> anyhow::Result<()> {
4615 let target_ids: Vec<InstrumentId> = if let Some((root, class)) = parent {
4620 self.cache
4621 .borrow()
4622 .instruments_by_parent(&instrument_id.venue, &root, class)
4623 .iter()
4624 .map(|i| i.id())
4625 .collect()
4626 } else {
4627 vec![*instrument_id]
4628 };
4629
4630 {
4631 let mut cache = self.cache.borrow_mut();
4632 for target_id in &target_ids {
4633 if !cache.has_order_book(target_id) {
4634 let book = OrderBook::new(*target_id, book_type);
4635 log::debug!("Created {book}");
4636 cache.add_order_book(book)?;
4637 }
4638 }
4639 }
4640
4641 if parent.is_some() {
4642 self.book_deltas_parent_expansions
4643 .insert(*instrument_id, target_ids.clone());
4644
4645 if !only_deltas {
4646 self.book_depth10_parent_expansions
4647 .insert(*instrument_id, target_ids.clone());
4648 }
4649 }
4650
4651 for target_id in &target_ids {
4652 let updater = self
4653 .book_updaters
4654 .entry(*target_id)
4655 .or_insert_with(|| {
4656 Rc::new(BookUpdater::new(
4657 target_id,
4658 self.cache.clone(),
4659 self.config.emit_quotes_from_book,
4660 ))
4661 })
4662 .clone();
4663
4664 let deltas_topic = switchboard::get_book_deltas_topic(*target_id);
4669 let deltas_handler = TypedHandler::new(updater.clone());
4670 msgbus::subscribe_book_deltas(
4671 deltas_topic.into(),
4672 deltas_handler,
4673 Some(self.msgbus_priority),
4674 );
4675
4676 if !only_deltas {
4677 let depth_topic = switchboard::get_book_depth10_topic(*target_id);
4678 let depth_handler = TypedHandler::new(updater);
4679 msgbus::subscribe_book_depth10(
4680 depth_topic.into(),
4681 depth_handler,
4682 Some(self.msgbus_priority),
4683 );
4684 }
4685 }
4686
4687 Ok(())
4688 }
4689
4690 fn is_underlying_wanted_for_deltas(&self, target_id: &InstrumentId) -> bool {
4691 if self.has_book_delta_subscriptions(target_id)
4695 || self.has_book_depth10_subscriptions(target_id)
4696 || self.has_book_snapshot_subscriptions(target_id)
4697 {
4698 return true;
4699 }
4700 self.book_deltas_parent_expansions
4701 .values()
4702 .any(|expansion| expansion.contains(target_id))
4703 }
4704
4705 fn is_underlying_wanted_for_depth10(&self, target_id: &InstrumentId) -> bool {
4706 if self.has_book_depth10_subscriptions(target_id)
4709 || self.has_book_snapshot_subscriptions(target_id)
4710 {
4711 return true;
4712 }
4713 self.book_depth10_parent_expansions
4714 .values()
4715 .any(|expansion| expansion.contains(target_id))
4716 }
4717
4718 fn create_bar_aggregator(
4719 &self,
4720 instrument: &InstrumentAny,
4721 bar_type: BarType,
4722 skip_first_non_full_bar: Option<bool>,
4723 ) -> Box<dyn BarAggregator> {
4724 let cache = self.cache.clone();
4725 let validate_sequence = self.config.validate_data_sequence;
4726
4727 let handler = move |bar: Bar| {
4728 process_engine_bar(&cache, validate_sequence, true, bar);
4729 };
4730
4731 let clock = self.clock.clone();
4732 let config = self.config.clone();
4733
4734 let price_precision = instrument.price_precision();
4735 let size_precision = instrument.size_precision();
4736
4737 if bar_type.spec().is_time_aggregated() {
4738 let time_bars_origin_offset = config
4739 .time_bars_origin_offset
4740 .get(&bar_type.spec().aggregation)
4741 .map(|duration| jiff::SignedDuration::try_from(*duration).unwrap_or_default());
4742
4743 Box::new(TimeBarAggregator::new(
4744 bar_type,
4745 price_precision,
4746 size_precision,
4747 clock,
4748 handler,
4749 config.time_bars_build_with_no_updates,
4750 config.time_bars_timestamp_on_close,
4751 config.time_bars_interval_type,
4752 time_bars_origin_offset,
4753 config.time_bars_build_delay,
4754 skip_first_non_full_bar.unwrap_or(config.time_bars_skip_first_non_full_bar),
4755 ))
4756 } else {
4757 match bar_type.spec().aggregation {
4758 BarAggregation::Tick => Box::new(TickBarAggregator::new(
4759 bar_type,
4760 price_precision,
4761 size_precision,
4762 handler,
4763 )) as Box<dyn BarAggregator>,
4764 BarAggregation::TickImbalance => Box::new(TickImbalanceBarAggregator::new(
4765 bar_type,
4766 price_precision,
4767 size_precision,
4768 handler,
4769 )) as Box<dyn BarAggregator>,
4770 BarAggregation::TickRuns => Box::new(TickRunsBarAggregator::new(
4771 bar_type,
4772 price_precision,
4773 size_precision,
4774 handler,
4775 )) as Box<dyn BarAggregator>,
4776 BarAggregation::Volume => Box::new(VolumeBarAggregator::new(
4777 bar_type,
4778 price_precision,
4779 size_precision,
4780 handler,
4781 )) as Box<dyn BarAggregator>,
4782 BarAggregation::VolumeImbalance => Box::new(VolumeImbalanceBarAggregator::new(
4783 bar_type,
4784 price_precision,
4785 size_precision,
4786 handler,
4787 )) as Box<dyn BarAggregator>,
4788 BarAggregation::VolumeRuns => Box::new(VolumeRunsBarAggregator::new(
4789 bar_type,
4790 price_precision,
4791 size_precision,
4792 handler,
4793 )) as Box<dyn BarAggregator>,
4794 BarAggregation::Value => Box::new(ValueBarAggregator::new(
4795 bar_type,
4796 price_precision,
4797 size_precision,
4798 handler,
4799 )) as Box<dyn BarAggregator>,
4800 BarAggregation::ValueImbalance => Box::new(ValueImbalanceBarAggregator::new(
4801 bar_type,
4802 price_precision,
4803 size_precision,
4804 handler,
4805 )) as Box<dyn BarAggregator>,
4806 BarAggregation::ValueRuns => Box::new(ValueRunsBarAggregator::new(
4807 bar_type,
4808 price_precision,
4809 size_precision,
4810 handler,
4811 )) as Box<dyn BarAggregator>,
4812 BarAggregation::Renko => Box::new(RenkoBarAggregator::new(
4813 bar_type,
4814 price_precision,
4815 size_precision,
4816 instrument.price_increment(),
4817 handler,
4818 )) as Box<dyn BarAggregator>,
4819 other => unreachable!(
4820 "Unsupported internal bar aggregation dispatch for {other:?}; update `create_bar_aggregator`"
4821 ),
4822 }
4823 }
4824 }
4825
4826 fn create_bar_aggregator_for_key(
4827 &mut self,
4828 bar_type: BarType,
4829 request_id: Option<UUID4>,
4830 skip_first_non_full_bar: Option<bool>,
4831 ) -> anyhow::Result<()> {
4832 let key = bar_aggregator_key(bar_type, request_id);
4833 if self.bar_aggregators.contains_key(&key) {
4834 return Ok(());
4835 }
4836
4837 let instrument = {
4838 let cache = self.cache.borrow();
4839 cache
4840 .instrument(&bar_type.instrument_id())
4841 .ok_or_else(|| {
4842 anyhow::anyhow!(
4843 "Cannot start bar aggregation: no instrument found for {}",
4844 bar_type.instrument_id(),
4845 )
4846 })?
4847 .clone()
4848 };
4849 let aggregator = self.create_bar_aggregator(&instrument, bar_type, skip_first_non_full_bar);
4850 debug_assert_eq!(
4851 aggregator.bar_type(),
4852 key.0,
4853 "aggregator bar type must match its standardized key"
4854 );
4855 self.bar_aggregators
4856 .insert(key, Rc::new(RefCell::new(aggregator)));
4857
4858 Ok(())
4859 }
4860
4861 fn start_bar_aggregation(&mut self, cmd: &SubscribeBars) -> anyhow::Result<()> {
4862 let key = bar_aggregator_key(cmd.bar_type, None);
4863
4864 if self
4865 .bar_aggregators
4866 .get(&key)
4867 .is_some_and(|aggregator| aggregator.borrow().is_running())
4868 && self.bar_aggregator_handlers.contains_key(&key)
4869 {
4870 if let Some(source_command) = self
4871 .subscriptions_bar_aggregation
4872 .get(&cmd.bar_type.standard())
4873 .and_then(|subscription| subscription.source.clone())
4874 {
4875 self.execute(DataCommand::Subscribe(source_command));
4876 }
4877 log::warn!(
4878 "Aggregator for {} is currently in use, subscription can't be started",
4879 cmd.bar_type,
4880 );
4881 return Ok(());
4882 }
4883
4884 let skip_first_non_full_bar = cmd
4885 .params
4886 .as_ref()
4887 .and_then(|params| params.get_bool("skip_first_non_full_bar"));
4888 self.start_bar_aggregator(cmd.bar_type, None, skip_first_non_full_bar)?;
4889 let source = self.subscribe_bar_aggregator(cmd);
4890 self.subscriptions_bar_aggregation.insert(
4891 cmd.bar_type.standard(),
4892 BarAggregationSubscription {
4893 command: cmd.clone(),
4894 source,
4895 },
4896 );
4897
4898 Ok(())
4899 }
4900
4901 fn start_bar_aggregator(
4902 &mut self,
4903 bar_type: BarType,
4904 request_id: Option<UUID4>,
4905 skip_first_non_full_bar: Option<bool>,
4906 ) -> anyhow::Result<()> {
4907 let key = bar_aggregator_key(bar_type, request_id);
4908 let bar_type_std = bar_type.standard();
4909
4910 self.create_bar_aggregator_for_key(bar_type, request_id, skip_first_non_full_bar)?;
4911 let aggregator = self
4912 .bar_aggregators
4913 .get(&key)
4914 .ok_or_else(|| anyhow::anyhow!("Cannot start bar aggregation for {bar_type}"))?
4915 .clone();
4916 let defer_subscription_activation = request_id.is_none()
4917 && aggregator.borrow().is_running()
4918 && !self.bar_aggregator_handlers.contains_key(&key);
4919
4920 if !self.bar_aggregator_handlers.contains_key(&key) {
4921 let mut subscriptions = Vec::new();
4923
4924 if bar_type.is_composite() {
4925 let topic = switchboard::get_bars_topic(bar_type.composite());
4926 let handler = TypedHandler::new(BarBarHandler::new(&aggregator, bar_type_std));
4927 msgbus::subscribe_bars(topic.into(), handler.clone(), None);
4928 subscriptions.push(BarAggregatorSubscription::Bar { topic, handler });
4929 } else if bar_type.spec().price_type == PriceType::Last {
4930 let topic = switchboard::get_trades_topic(bar_type.instrument_id());
4931 let handler = TypedHandler::new(BarTradeHandler::new(&aggregator, bar_type_std));
4932 msgbus::subscribe_trades(
4933 topic.into(),
4934 handler.clone(),
4935 Some(BAR_AGGREGATOR_PRIORITY),
4936 );
4937 subscriptions.push(BarAggregatorSubscription::Trade { topic, handler });
4938 } else {
4939 if matches!(
4941 bar_type.spec().aggregation,
4942 BarAggregation::TickImbalance
4943 | BarAggregation::VolumeImbalance
4944 | BarAggregation::ValueImbalance
4945 | BarAggregation::TickRuns
4946 | BarAggregation::VolumeRuns
4947 | BarAggregation::ValueRuns
4948 ) {
4949 log::warn!(
4950 "Bar type {bar_type} uses imbalance/runs aggregation which requires trade \
4951 data with `aggressor_side`, but `price_type` is not LAST so it will receive \
4952 quote data: bars will not emit correctly",
4953 );
4954 }
4955
4956 let topic = switchboard::get_quotes_topic(bar_type.instrument_id());
4957 let handler = TypedHandler::new(BarQuoteHandler::new(&aggregator, bar_type_std));
4958 msgbus::subscribe_quotes(
4959 topic.into(),
4960 handler.clone(),
4961 Some(BAR_AGGREGATOR_PRIORITY),
4962 );
4963 subscriptions.push(BarAggregatorSubscription::Quote { topic, handler });
4964 }
4965
4966 self.bar_aggregator_handlers.insert(key, subscriptions);
4967 }
4968
4969 if defer_subscription_activation {
4970 return Ok(());
4971 }
4972
4973 self.setup_bar_aggregator(bar_type, false, request_id)?;
4974
4975 aggregator.borrow_mut().set_is_running(true);
4976
4977 Ok(())
4978 }
4979
4980 fn subscribe_bar_aggregator(&mut self, cmd: &SubscribeBars) -> Option<SubscribeCommand> {
4981 let subscribe = self.bar_aggregator_source_command(cmd)?;
4982 self.execute(DataCommand::Subscribe(subscribe.clone()));
4983 Some(subscribe)
4984 }
4985
4986 fn bar_aggregator_source_command(&self, cmd: &SubscribeBars) -> Option<SubscribeCommand> {
4987 let key = bar_aggregator_key(cmd.bar_type, None);
4988 if !self.bar_aggregators.contains_key(&key) {
4989 log::error!(
4990 "Cannot subscribe bar aggregator: no aggregator found for {}",
4991 cmd.bar_type,
4992 );
4993 return None;
4994 }
4995
4996 if cmd.bar_type.is_composite() {
4997 let composite_bar_type = cmd.bar_type.composite();
4998 if composite_bar_type.is_externally_aggregated() {
4999 let subscribe = SubscribeBars::new(
5000 composite_bar_type,
5001 cmd.client_id,
5002 cmd.venue,
5003 UUID4::new(),
5004 cmd.ts_init,
5005 Some(cmd.command_id),
5006 cmd.params.clone(),
5007 );
5008 return Some(SubscribeCommand::Bars(subscribe));
5009 }
5010 } else if cmd.bar_type.spec().price_type == PriceType::Last {
5011 let subscribe = SubscribeTrades::new(
5012 cmd.bar_type.instrument_id(),
5013 cmd.client_id,
5014 cmd.venue,
5015 UUID4::new(),
5016 cmd.ts_init,
5017 Some(cmd.command_id),
5018 cmd.params.clone(),
5019 );
5020 return Some(SubscribeCommand::Trades(subscribe));
5021 } else {
5022 let subscribe = SubscribeQuotes::new(
5023 cmd.bar_type.instrument_id(),
5024 cmd.client_id,
5025 cmd.venue,
5026 UUID4::new(),
5027 cmd.ts_init,
5028 Some(cmd.command_id),
5029 cmd.params.clone(),
5030 );
5031 return Some(SubscribeCommand::Quotes(subscribe));
5032 }
5033
5034 None
5035 }
5036
5037 fn setup_bar_aggregator(
5041 &self,
5042 bar_type: BarType,
5043 historical: bool,
5044 request_id: Option<UUID4>,
5045 ) -> anyhow::Result<()> {
5046 let key = bar_aggregator_key(bar_type, request_id);
5047 let aggregator = self.bar_aggregators.get(&key).ok_or_else(|| {
5048 anyhow::anyhow!("Cannot setup bar aggregator: no aggregator found for {bar_type}")
5049 })?;
5050
5051 let cache = self.cache.clone();
5053 let validate_sequence = self.config.validate_data_sequence;
5054 let publish = !historical;
5055 let handler: Box<dyn FnMut(Bar)> = Box::new(move |bar: Bar| {
5056 process_engine_bar(&cache, validate_sequence, publish, bar);
5057 });
5058
5059 aggregator
5060 .borrow_mut()
5061 .set_historical_mode(historical, handler);
5062
5063 if bar_type.spec().is_time_aggregated() {
5065 use nautilus_common::clock::TestClock;
5066
5067 if historical {
5068 let test_clock = Rc::new(RefCell::new(TestClock::new()));
5070 aggregator.borrow_mut().set_clock(test_clock);
5071 let aggregator_weak = Rc::downgrade(aggregator);
5074 aggregator.borrow_mut().set_aggregator_weak(aggregator_weak);
5075 } else {
5076 aggregator.borrow_mut().set_clock(self.clock.clone());
5077 aggregator
5078 .borrow_mut()
5079 .start_timer(Some(aggregator.clone()));
5080 }
5081 }
5082
5083 Ok(())
5084 }
5085
5086 fn unsubscribe_bar_aggregator(&mut self, cmd: &UnsubscribeBars) {
5087 if cmd.bar_type.is_composite() {
5088 let composite_bar_type = cmd.bar_type.composite();
5089 if composite_bar_type.is_externally_aggregated() {
5090 let unsubscribe = UnsubscribeBars::new(
5091 composite_bar_type,
5092 cmd.client_id,
5093 cmd.venue,
5094 UUID4::new(),
5095 cmd.ts_init,
5096 Some(cmd.command_id),
5097 cmd.params.clone(),
5098 );
5099 self.execute(DataCommand::Unsubscribe(UnsubscribeCommand::Bars(
5100 unsubscribe,
5101 )));
5102 }
5103 } else if cmd.bar_type.spec().price_type == PriceType::Last {
5104 let unsubscribe = UnsubscribeTrades::new(
5105 cmd.bar_type.instrument_id(),
5106 cmd.client_id,
5107 cmd.venue,
5108 UUID4::new(),
5109 cmd.ts_init,
5110 Some(cmd.command_id),
5111 cmd.params.clone(),
5112 );
5113 self.execute(DataCommand::Unsubscribe(UnsubscribeCommand::Trades(
5114 unsubscribe,
5115 )));
5116 } else {
5117 let unsubscribe = UnsubscribeQuotes::new(
5118 cmd.bar_type.instrument_id(),
5119 cmd.client_id,
5120 cmd.venue,
5121 UUID4::new(),
5122 cmd.ts_init,
5123 Some(cmd.command_id),
5124 cmd.params.clone(),
5125 );
5126 self.execute(DataCommand::Unsubscribe(UnsubscribeCommand::Quotes(
5127 unsubscribe,
5128 )));
5129 }
5130 }
5131
5132 fn stop_bar_aggregator(
5133 &mut self,
5134 bar_type: BarType,
5135 request_id: Option<UUID4>,
5136 ) -> anyhow::Result<()> {
5137 let key = bar_aggregator_key(bar_type, request_id);
5138 let aggregator = self.bar_aggregators.shift_remove(&key).ok_or_else(|| {
5139 anyhow::anyhow!("Cannot stop bar aggregator: no aggregator to stop for {bar_type}")
5140 })?;
5141
5142 aggregator.borrow_mut().stop();
5143
5144 if let Some(subs) = self.bar_aggregator_handlers.remove(&key) {
5146 for sub in subs {
5147 match sub {
5148 BarAggregatorSubscription::Bar { topic, handler } => {
5149 msgbus::unsubscribe_bars(topic.into(), &handler);
5150 }
5151 BarAggregatorSubscription::Trade { topic, handler } => {
5152 msgbus::unsubscribe_trades(topic.into(), &handler);
5153 }
5154 BarAggregatorSubscription::Quote { topic, handler } => {
5155 msgbus::unsubscribe_quotes(topic.into(), &handler);
5156 }
5157 }
5158 }
5159 }
5160
5161 Ok(())
5162 }
5163
5164 fn subscribe_continuous_future_bars(&mut self, cmd: &SubscribeBars) -> anyhow::Result<()> {
5165 let target_bar_type = cmd.bar_type;
5166 let target_key = target_bar_type.standard();
5167
5168 if !target_bar_type.is_internally_aggregated() {
5169 anyhow::bail!(
5170 "Continuous future bar subscriptions require an internally aggregated target, was {target_bar_type}"
5171 );
5172 }
5173
5174 if self.continuous_future_roller.is_none() {
5175 anyhow::bail!(
5176 "Cannot subscribe continuous future bars for {target_bar_type}: roller is not initialized; ensure `register_msgbus_handlers` runs before subscribing"
5177 );
5178 }
5179
5180 let request = continuous_future_subscription_from_bars(cmd)?.ok_or_else(|| {
5181 anyhow::anyhow!(
5182 "Continuous future bar subscription requires `continuous_future_transitions`, was {cmd:?}"
5183 )
5184 })?;
5185
5186 self.ensure_continuous_future_target_instrument(&request);
5187
5188 if self
5189 .continuous_future_subscriptions
5190 .contains_key(&target_key)
5191 {
5192 log::warn!("Continuous future bars already subscribed for {target_bar_type}");
5193 return Ok(());
5194 }
5195
5196 let aggregator_key = bar_aggregator_key(target_bar_type, None);
5197 if let Some(aggregator) = self.bar_aggregators.get(&aggregator_key)
5198 && aggregator.borrow().is_running()
5199 {
5200 log::warn!(
5201 "Aggregator for {target_bar_type} is currently in use, continuous future subscription can't be started"
5202 );
5203 return Ok(());
5204 }
5205
5206 self.create_bar_aggregator_for_key(target_bar_type, None, None)?;
5207 self.setup_bar_aggregator(target_bar_type, false, None)?;
5208
5209 let now_ns = self.clock.borrow().timestamp_ns().as_u64();
5210 let Some(segment) = request.next_segment(now_ns, now_ns) else {
5211 log::error!("Cannot determine active continuous future segment for {target_bar_type}");
5212 if let Err(e) = self.stop_bar_aggregator(target_bar_type, None) {
5213 log::error!(
5214 "Error rolling back continuous future aggregator for {target_bar_type}: {e}"
5215 );
5216 }
5217 return Ok(());
5218 };
5219
5220 self.apply_continuous_future_subscription_adjustment(&request, segment.index)?;
5221 let source = request.source_for_segment(segment.instrument_id);
5222 let source_subscription =
5223 self.subscribe_continuous_future_source(target_bar_type, source, segment.instrument_id);
5224
5225 if let Some(aggregator) = self.bar_aggregators.get(&aggregator_key) {
5226 aggregator.borrow_mut().set_is_running(true);
5227 }
5228
5229 let next_transition_index =
5230 (segment.index < request.transitions.len()).then_some(segment.index);
5231
5232 self.continuous_future_subscriptions.insert(
5233 target_key,
5234 ContinuousFutureSubscriptionState {
5235 target_bar_type,
5236 client_id: cmd.client_id,
5237 venue: cmd.venue,
5238 command_id: cmd.command_id,
5239 params: cmd.params.clone(),
5240 request,
5241 active_segment_instrument_id: segment.instrument_id,
5242 active_source: source,
5243 active_source_subscription: Some(source_subscription),
5244 next_transition_index,
5245 timer_name: None,
5246 },
5247 );
5248
5249 let child_cmd = self.build_continuous_future_subscribe_command(
5250 &target_key,
5251 source,
5252 segment.instrument_id,
5253 cmd.command_id,
5254 cmd.ts_init,
5255 true,
5256 );
5257
5258 if let Some(child) = child_cmd {
5259 self.execute(child);
5260 }
5261
5262 self.schedule_continuous_future_transition(target_key);
5263
5264 Ok(())
5265 }
5266
5267 fn unsubscribe_continuous_future_bars(&mut self, cmd: &UnsubscribeBars) {
5268 let target_key = cmd.bar_type.standard();
5269 let Some(mut state) = self.continuous_future_subscriptions.remove(&target_key) else {
5270 log::warn!(
5271 "Cannot unsubscribe continuous future bars: no subscription state for {target_key}"
5272 );
5273 return;
5274 };
5275
5276 if let Some(name) = state.timer_name.take() {
5277 self.clock.borrow_mut().cancel_timer(&name);
5278 }
5279
5280 let ts_init = self.clock.borrow().timestamp_ns();
5281 let segment_instrument_id = state.active_segment_instrument_id;
5282 let source = state.active_source;
5283 let source_subscription = state.active_source_subscription.take();
5284 let client_id = state.client_id;
5285 let venue = state.venue;
5286 let params = state.params.clone();
5287 let target_bar_type = state.target_bar_type;
5288 drop(state);
5289
5290 if let Some(subscription) = source_subscription {
5291 self.unsubscribe_continuous_future_source(target_bar_type, subscription);
5292 }
5293
5294 let child_cmd = build_continuous_future_unsubscribe_command(
5295 source,
5296 segment_instrument_id,
5297 client_id,
5298 venue,
5299 params.as_ref(),
5300 cmd.command_id,
5301 ts_init,
5302 );
5303 self.execute(child_cmd);
5304
5305 if let Err(e) = self.stop_bar_aggregator(target_bar_type, None) {
5306 log::error!("Error stopping continuous future aggregator for {target_bar_type}: {e}");
5307 }
5308 }
5309
5310 fn handle_continuous_future_subscription_transition(&mut self, event: &TimeEvent) {
5311 let event_name = event.name.as_str();
5312 let Some((target_key, transition_index)) = parse_transition_timer_name(event_name) else {
5313 log::warn!(
5314 "Ignoring continuous future transition event with unparsable name {event_name}"
5315 );
5316 return;
5317 };
5318
5319 let Some(state) = self.continuous_future_subscriptions.get_mut(&target_key) else {
5320 log::warn!(
5321 "Ignoring continuous future transition event {event_name}: no subscription state for {target_key}"
5322 );
5323 return;
5324 };
5325
5326 if state.timer_name.as_deref() != Some(event_name) {
5327 return;
5328 }
5329 state.timer_name = None;
5330
5331 let Some(next_index) = state.next_transition_index else {
5332 return;
5333 };
5334
5335 if next_index != transition_index || next_index >= state.request.transitions.len() {
5336 return;
5337 }
5338
5339 let prev_segment_instrument_id = state.active_segment_instrument_id;
5340 let next_segment_instrument_id = state.request.transitions[next_index].post_instrument_id;
5341 let new_segment_index = next_index + 1;
5342 state.active_segment_instrument_id = next_segment_instrument_id;
5343 state.next_transition_index =
5344 (new_segment_index < state.request.transitions.len()).then_some(new_segment_index);
5345
5346 let old_source = state.active_source;
5347 let old_source_subscription = state.active_source_subscription.take();
5348 let client_id = state.client_id;
5349 let venue = state.venue;
5350 let params = state.params.clone();
5351 let command_id = state.command_id;
5352 let target_bar_type = state.target_bar_type;
5353
5354 let ts_init = self.clock.borrow().timestamp_ns();
5355
5356 if let Some(subscription) = old_source_subscription {
5357 self.unsubscribe_continuous_future_source(target_bar_type, subscription);
5358 }
5359
5360 let unsub_child = build_continuous_future_unsubscribe_command(
5361 old_source,
5362 prev_segment_instrument_id,
5363 client_id,
5364 venue,
5365 params.as_ref(),
5366 command_id,
5367 ts_init,
5368 );
5369 self.execute(unsub_child);
5370
5371 if let Err(e) = self
5372 .apply_continuous_future_subscription_adjustment_for(target_bar_type, new_segment_index)
5373 {
5374 log::error!("Error applying continuous future adjustment for {target_bar_type}: {e}");
5375 return;
5376 }
5377
5378 let new_source = {
5379 let Some(state) = self.continuous_future_subscriptions.get(&target_key) else {
5380 return;
5381 };
5382 state.request.source_for_segment(next_segment_instrument_id)
5383 };
5384 let new_subscription = self.subscribe_continuous_future_source(
5385 target_bar_type,
5386 new_source,
5387 next_segment_instrument_id,
5388 );
5389
5390 if let Some(state) = self.continuous_future_subscriptions.get_mut(&target_key) {
5391 state.active_source = new_source;
5392 state.active_source_subscription = Some(new_subscription);
5393 }
5394
5395 let sub_child = self.build_continuous_future_subscribe_command(
5396 &target_key,
5397 new_source,
5398 next_segment_instrument_id,
5399 command_id,
5400 ts_init,
5401 true,
5402 );
5403
5404 if let Some(child) = sub_child {
5405 self.execute(child);
5406 }
5407
5408 self.schedule_continuous_future_transition(target_key);
5409 }
5410
5411 fn apply_continuous_future_subscription_adjustment(
5412 &self,
5413 request: &ContinuousFutureRequest,
5414 segment_index: usize,
5415 ) -> anyhow::Result<()> {
5416 let key = bar_aggregator_key(request.primary_bar_type, None);
5417 let aggregator = self.bar_aggregators.get(&key).ok_or_else(|| {
5418 anyhow::anyhow!(
5419 "No live aggregator for continuous future subscription {}",
5420 request.primary_bar_type
5421 )
5422 })?;
5423 let adjustment = request.adjustment_for_segment(segment_index);
5424 aggregator
5425 .borrow_mut()
5426 .set_adjustment(adjustment, request.adjustment_mode);
5427 Ok(())
5428 }
5429
5430 fn apply_continuous_future_subscription_adjustment_for(
5431 &self,
5432 target_bar_type: BarType,
5433 segment_index: usize,
5434 ) -> anyhow::Result<()> {
5435 let Some(state) = self
5436 .continuous_future_subscriptions
5437 .get(&target_bar_type.standard())
5438 else {
5439 anyhow::bail!("No continuous future subscription state for {target_bar_type}");
5440 };
5441 self.apply_continuous_future_subscription_adjustment(&state.request, segment_index)
5442 }
5443
5444 fn subscribe_continuous_future_source(
5445 &mut self,
5446 target_bar_type: BarType,
5447 source: ContinuousFutureSource,
5448 segment_instrument_id: InstrumentId,
5449 ) -> BarAggregatorSubscription {
5450 let key = bar_aggregator_key(target_bar_type, None);
5451 let aggregator = self
5452 .bar_aggregators
5453 .get(&key)
5454 .cloned()
5455 .expect("aggregator was created before subscribe_continuous_future_source");
5456
5457 let subscription = match source {
5458 ContinuousFutureSource::Bars(source_bar_type) => {
5459 let topic = switchboard::get_bars_topic(source_bar_type);
5460 let handler =
5461 TypedHandler::new(BarBarHandler::new(&aggregator, target_bar_type.standard()));
5462 msgbus::subscribe_bars(topic.into(), handler.clone(), None);
5463 BarAggregatorSubscription::Bar { topic, handler }
5464 }
5465 ContinuousFutureSource::Trades => {
5466 let topic = switchboard::get_trades_topic(segment_instrument_id);
5467 let handler = TypedHandler::new(BarTradeHandler::new(
5468 &aggregator,
5469 target_bar_type.standard(),
5470 ));
5471 msgbus::subscribe_trades(
5472 topic.into(),
5473 handler.clone(),
5474 Some(BAR_AGGREGATOR_PRIORITY),
5475 );
5476 BarAggregatorSubscription::Trade { topic, handler }
5477 }
5478 ContinuousFutureSource::Quotes => {
5479 let topic = switchboard::get_quotes_topic(segment_instrument_id);
5480 let handler = TypedHandler::new(BarQuoteHandler::new(
5481 &aggregator,
5482 target_bar_type.standard(),
5483 ));
5484 msgbus::subscribe_quotes(
5485 topic.into(),
5486 handler.clone(),
5487 Some(BAR_AGGREGATOR_PRIORITY),
5488 );
5489 BarAggregatorSubscription::Quote { topic, handler }
5490 }
5491 };
5492
5493 self.bar_aggregator_handlers
5494 .entry(key)
5495 .or_default()
5496 .push(subscription.clone());
5497
5498 subscription
5499 }
5500
5501 fn unsubscribe_continuous_future_source(
5502 &mut self,
5503 target_bar_type: BarType,
5504 subscription: BarAggregatorSubscription,
5505 ) {
5506 let key = bar_aggregator_key(target_bar_type, None);
5507 if let Some(subs) = self.bar_aggregator_handlers.get_mut(&key) {
5508 subs.retain(|registered| !same_subscription(registered, &subscription));
5509 }
5510
5511 match subscription {
5512 BarAggregatorSubscription::Bar { topic, handler } => {
5513 msgbus::unsubscribe_bars(topic.into(), &handler);
5514 }
5515 BarAggregatorSubscription::Trade { topic, handler } => {
5516 msgbus::unsubscribe_trades(topic.into(), &handler);
5517 }
5518 BarAggregatorSubscription::Quote { topic, handler } => {
5519 msgbus::unsubscribe_quotes(topic.into(), &handler);
5520 }
5521 }
5522 }
5523
5524 fn build_continuous_future_subscribe_command(
5525 &self,
5526 target_key: &BarType,
5527 source: ContinuousFutureSource,
5528 segment_instrument_id: InstrumentId,
5529 command_id: UUID4,
5530 ts_init: UnixNanos,
5531 subscribe: bool,
5532 ) -> Option<DataCommand> {
5533 let state = self.continuous_future_subscriptions.get(target_key)?;
5534
5535 if !subscribe {
5536 return Some(build_continuous_future_unsubscribe_command(
5537 source,
5538 segment_instrument_id,
5539 state.client_id,
5540 state.venue,
5541 state.params.as_ref(),
5542 command_id,
5543 ts_init,
5544 ));
5545 }
5546
5547 let child_params = state
5548 .request
5549 .child_params(state.params.as_ref(), command_id);
5550
5551 Some(build_continuous_future_subscribe_inner(
5552 source,
5553 segment_instrument_id,
5554 state.client_id,
5555 state.venue,
5556 child_params,
5557 command_id,
5558 ts_init,
5559 ))
5560 }
5561
5562 fn schedule_continuous_future_transition(&mut self, target_key: BarType) {
5563 let Some(state) = self.continuous_future_subscriptions.get_mut(&target_key) else {
5564 return;
5565 };
5566
5567 if let Some(name) = state.timer_name.take() {
5568 self.clock.borrow_mut().cancel_timer(&name);
5569 }
5570
5571 let Some(transition_index) = state.next_transition_index else {
5572 return;
5573 };
5574 let Some(row) = state.request.transitions.get(transition_index) else {
5575 return;
5576 };
5577 let transition_ns = row.transition_time_ns;
5578 let timer_name = format!("continuous-future-roll:{target_key}:{transition_index}");
5579
5580 let Some(roller) = self.continuous_future_roller.clone() else {
5581 log::error!(
5582 "Cannot schedule continuous future transition timer for {target_key}: roller not initialized"
5583 );
5584 return;
5585 };
5586
5587 let callback_fn: Rc<dyn Fn(TimeEvent)> =
5588 Rc::new(move |event| roller.handle_transition(&event));
5589 let callback = TimeEventCallback::from(callback_fn);
5590
5591 if let Err(e) = self.clock.borrow_mut().set_time_alert_ns(
5592 &timer_name,
5593 UnixNanos::from(transition_ns),
5594 Some(callback),
5595 Some(true),
5596 ) {
5597 log::error!("Failed to schedule continuous future transition {timer_name}: {e}");
5598 return;
5599 }
5600
5601 if let Some(state) = self.continuous_future_subscriptions.get_mut(&target_key) {
5602 state.timer_name = Some(timer_name);
5603 }
5604 }
5605}
5606
5607fn resolve_parent_components(
5615 instrument_id: &InstrumentId,
5616 params: Option<&Params>,
5617) -> anyhow::Result<Option<(Ustr, InstrumentClass)>> {
5618 if !is_parent_subscription(params) {
5619 return Ok(None);
5620 }
5621 let Some((root, class)) = instrument_id.parse_parent_components() else {
5622 anyhow::bail!(
5623 "Cannot expand parent subscription for {instrument_id}: \
5624 symbol does not parse as `<root>.<class>` with a recognized class suffix"
5625 );
5626 };
5627 Ok(Some((Ustr::from(root), class)))
5628}
5629
5630fn register_external_streaming_type(cmd: &SubscribeCommand) {
5631 if let Some(payload_type) = streaming_payload_type(cmd) {
5632 msgbus::get_message_bus()
5633 .borrow_mut()
5634 .add_streaming_type(payload_type);
5635 }
5636}
5637
5638fn publish_external_data_command<T>(client_id: ClientId, command: &T)
5639where
5640 T: Any,
5641{
5642 let topic = format!("commands.data.{client_id}");
5643 msgbus::publish_any(topic.into(), command);
5644}
5645
5646#[rustfmt::skip]
5647fn streaming_payload_type(cmd: &SubscribeCommand) -> Option<BusPayloadType> {
5648 match cmd {
5649 SubscribeCommand::Data(cmd) => Some(BusPayloadType::Custom(Ustr::from(
5650 cmd.data_type.type_name(),
5651 ))),
5652 SubscribeCommand::Instrument(_) | SubscribeCommand::Instruments(_) => Some(BusPayloadType::Instrument),
5653 SubscribeCommand::BookDeltas(_) | SubscribeCommand::BookSnapshots(_) => Some(BusPayloadType::OrderBookDeltas),
5654 SubscribeCommand::BookDepth10(_) => Some(BusPayloadType::OrderBookDepth10),
5655 SubscribeCommand::Quotes(_) => Some(BusPayloadType::QuoteTick),
5656 SubscribeCommand::Trades(_) => Some(BusPayloadType::TradeTick),
5657 SubscribeCommand::Bars(_) => Some(BusPayloadType::Bar),
5658 SubscribeCommand::MarkPrices(_) => Some(BusPayloadType::MarkPriceUpdate),
5659 SubscribeCommand::IndexPrices(_) => Some(BusPayloadType::IndexPriceUpdate),
5660 SubscribeCommand::FundingRates(_) => Some(BusPayloadType::FundingRateUpdate),
5661 SubscribeCommand::OptionGreeks(_) => Some(BusPayloadType::OptionGreeks),
5662 SubscribeCommand::InstrumentStatus(_)
5663 | SubscribeCommand::InstrumentClose(_)
5664 | SubscribeCommand::OptionChain(_) => None,
5665 }
5666}
5667
5668fn spread_quote_update_interval_seconds(params: Option<&Params>) -> Option<u64> {
5669 match params.and_then(|params| params.get("update_interval_seconds")) {
5670 Some(value) if value.is_null() => None,
5671 Some(value) => value.as_u64().filter(|interval| *interval > 0),
5672 None => Some(1),
5673 }
5674}
5675
5676fn spread_instrument_legs(instrument: &InstrumentAny) -> Option<Vec<(InstrumentId, i64)>> {
5677 if !instrument.is_spread() {
5678 return None;
5679 }
5680
5681 let instrument_id = instrument.id();
5682 let symbol = instrument_id.symbol.as_str();
5683 if !symbol.contains(GENERIC_SPREAD_ID_SEPARATOR) {
5684 return Some(vec![(instrument_id, 1)]);
5685 }
5686
5687 symbol
5688 .split(GENERIC_SPREAD_ID_SEPARATOR)
5689 .map(|component| parse_spread_leg(component, instrument_id.venue))
5690 .collect()
5691}
5692
5693fn parse_spread_leg(component: &str, venue: Venue) -> Option<(InstrumentId, i64)> {
5694 if let Some(rest) = component.strip_prefix("((") {
5695 let (ratio, symbol) = rest.split_once("))")?;
5696 return parse_spread_leg_parts(ratio, symbol, venue, -1);
5697 }
5698
5699 let rest = component.strip_prefix('(')?;
5700 let (ratio, symbol) = rest.split_once(')')?;
5701 parse_spread_leg_parts(ratio, symbol, venue, 1)
5702}
5703
5704fn parse_spread_leg_parts(
5705 ratio: &str,
5706 symbol: &str,
5707 venue: Venue,
5708 sign: i64,
5709) -> Option<(InstrumentId, i64)> {
5710 if symbol.is_empty() {
5711 return None;
5712 }
5713
5714 let ratio = ratio.parse::<i64>().ok()?.checked_mul(sign)?;
5715 if ratio == 0 {
5716 return None;
5717 }
5718
5719 Some((InstrumentId::new(Symbol::new(symbol), venue), ratio))
5720}
5721
5722#[inline(always)]
5723fn log_error_on_cache_insert<T: Display>(e: &T) {
5724 log::error!("Error on cache insert: {e}");
5725}
5726
5727#[derive(Debug)]
5728struct OptionChainBootstrapper {
5729 engine: WeakCell<DataEngine>,
5730}
5731
5732impl OptionChainBootstrapper {
5733 fn new(engine: &Rc<RefCell<DataEngine>>) -> Self {
5734 Self {
5735 engine: WeakCell::from(Rc::downgrade(engine)),
5736 }
5737 }
5738
5739 fn handle_timeout(&self) {
5740 if let Some(engine) = self.engine.upgrade() {
5741 engine
5742 .borrow_mut()
5743 .handle_option_chain_reference_price_timeout();
5744 }
5745 }
5746}
5747
5748#[derive(Debug)]
5749struct PendingOptionChainRequest {
5750 command: SubscribeOptionChain,
5751 sample_instrument_id: InstrumentId,
5752 deadline_ns: UnixNanos,
5753}
5754
5755#[derive(Debug)]
5756struct OptionChainGreeksBootstrap {
5757 instrument_id: InstrumentId,
5758 client_id: ClientId,
5759 venue: Venue,
5760 ownership_handler: TypedHandler<OptionGreeks>,
5761}
5762
5763#[derive(Clone, Debug)]
5764struct BookSnapshotSource {
5765 command: SubscribeBookSnapshots,
5766 client_command: SubscribeCommand,
5767}
5768
5769#[derive(Clone, Debug)]
5770struct BarAggregationSubscription {
5771 command: SubscribeBars,
5772 source: Option<SubscribeCommand>,
5773}
5774
5775#[derive(Debug)]
5776struct SpreadQuoteState {
5777 aggregator: Rc<RefCell<SpreadQuoteAggregator>>,
5778 handlers: Vec<(InstrumentId, TypedHandler<QuoteTick>)>,
5779 owners: usize,
5780 command: SubscribeQuotes,
5781 sources: Vec<SubscribeCommand>,
5782}
5783
5784#[derive(Debug)]
5790struct ContinuousFutureRoller {
5791 engine: WeakCell<DataEngine>,
5792}
5793
5794impl ContinuousFutureRoller {
5795 fn new(engine: &Rc<RefCell<DataEngine>>) -> Self {
5796 Self {
5797 engine: WeakCell::from(Rc::downgrade(engine)),
5798 }
5799 }
5800
5801 fn handle_transition(&self, event: &TimeEvent) {
5802 if let Some(engine) = self.engine.upgrade() {
5803 engine
5804 .borrow_mut()
5805 .handle_continuous_future_subscription_transition(event);
5806 }
5807 }
5808}
5809
5810#[derive(Debug)]
5811struct ContinuousFutureSubscriptionState {
5812 target_bar_type: BarType,
5813 client_id: Option<ClientId>,
5814 venue: Option<Venue>,
5815 command_id: UUID4,
5816 params: Option<Params>,
5817 request: ContinuousFutureRequest,
5818 active_segment_instrument_id: InstrumentId,
5819 active_source: ContinuousFutureSource,
5820 active_source_subscription: Option<BarAggregatorSubscription>,
5821 next_transition_index: Option<usize>,
5822 timer_name: Option<String>,
5823}
5824
5825fn same_subscription(a: &BarAggregatorSubscription, b: &BarAggregatorSubscription) -> bool {
5826 match (a, b) {
5827 (
5828 BarAggregatorSubscription::Bar { handler: h1, .. },
5829 BarAggregatorSubscription::Bar { handler: h2, .. },
5830 ) => h1.id() == h2.id(),
5831 (
5832 BarAggregatorSubscription::Trade { handler: h1, .. },
5833 BarAggregatorSubscription::Trade { handler: h2, .. },
5834 ) => h1.id() == h2.id(),
5835 (
5836 BarAggregatorSubscription::Quote { handler: h1, .. },
5837 BarAggregatorSubscription::Quote { handler: h2, .. },
5838 ) => h1.id() == h2.id(),
5839 _ => false,
5840 }
5841}
5842
5843fn parse_transition_timer_name(name: &str) -> Option<(BarType, usize)> {
5844 let rest = name.strip_prefix("continuous-future-roll:")?;
5845 let (target, index) = rest.rsplit_once(':')?;
5846 let bar_type = BarType::from_str(target).ok()?;
5847 let index = index.parse::<usize>().ok()?;
5848 Some((bar_type, index))
5849}
5850
5851fn build_continuous_future_subscribe_inner(
5852 source: ContinuousFutureSource,
5853 segment_instrument_id: InstrumentId,
5854 client_id: Option<ClientId>,
5855 _venue: Option<Venue>,
5856 child_params: Params,
5857 correlation_id: UUID4,
5858 ts_init: UnixNanos,
5859) -> DataCommand {
5860 let command_id = UUID4::new();
5861 let child_venue = Some(segment_instrument_id.venue);
5862
5863 match source {
5864 ContinuousFutureSource::Bars(source_bar_type) => {
5865 DataCommand::Subscribe(SubscribeCommand::Bars(SubscribeBars::new(
5866 source_bar_type,
5867 client_id,
5868 child_venue,
5869 command_id,
5870 ts_init,
5871 Some(correlation_id),
5872 Some(child_params),
5873 )))
5874 }
5875 ContinuousFutureSource::Trades => {
5876 DataCommand::Subscribe(SubscribeCommand::Trades(SubscribeTrades::new(
5877 segment_instrument_id,
5878 client_id,
5879 child_venue,
5880 command_id,
5881 ts_init,
5882 Some(correlation_id),
5883 Some(child_params),
5884 )))
5885 }
5886 ContinuousFutureSource::Quotes => {
5887 DataCommand::Subscribe(SubscribeCommand::Quotes(SubscribeQuotes::new(
5888 segment_instrument_id,
5889 client_id,
5890 child_venue,
5891 command_id,
5892 ts_init,
5893 Some(correlation_id),
5894 Some(child_params),
5895 )))
5896 }
5897 }
5898}
5899
5900fn build_continuous_future_unsubscribe_command(
5901 source: ContinuousFutureSource,
5902 segment_instrument_id: InstrumentId,
5903 client_id: Option<ClientId>,
5904 _venue: Option<Venue>,
5905 parent_params: Option<&Params>,
5906 correlation_id: UUID4,
5907 ts_init: UnixNanos,
5908) -> DataCommand {
5909 let mut child_params = parent_params.cloned().unwrap_or_default();
5910 child_params.shift_remove("continuous_future_transitions");
5911 child_params.shift_remove("continuous_future_adjustment_mode");
5912 child_params.shift_remove("last_post_instrument_id");
5913 child_params.shift_remove("first_pre_instrument_id");
5914 child_params.shift_remove("bar_types");
5915 let command_id = UUID4::new();
5916 let child_venue = Some(segment_instrument_id.venue);
5917
5918 match source {
5919 ContinuousFutureSource::Bars(source_bar_type) => {
5920 DataCommand::Unsubscribe(UnsubscribeCommand::Bars(UnsubscribeBars::new(
5921 source_bar_type,
5922 client_id,
5923 child_venue,
5924 command_id,
5925 ts_init,
5926 Some(correlation_id),
5927 Some(child_params),
5928 )))
5929 }
5930 ContinuousFutureSource::Trades => {
5931 DataCommand::Unsubscribe(UnsubscribeCommand::Trades(UnsubscribeTrades::new(
5932 segment_instrument_id,
5933 client_id,
5934 child_venue,
5935 command_id,
5936 ts_init,
5937 Some(correlation_id),
5938 Some(child_params),
5939 )))
5940 }
5941 ContinuousFutureSource::Quotes => {
5942 DataCommand::Unsubscribe(UnsubscribeCommand::Quotes(UnsubscribeQuotes::new(
5943 segment_instrument_id,
5944 client_id,
5945 child_venue,
5946 command_id,
5947 ts_init,
5948 Some(correlation_id),
5949 Some(child_params),
5950 )))
5951 }
5952 }
5953}
5954
5955fn datetime_to_unix_nanos(datetime: jiff::Timestamp) -> anyhow::Result<UnixNanos> {
5956 let timestamp = datetime.as_nanosecond();
5957 let timestamp = u64::try_from(timestamp)
5958 .context("datetime is before the UNIX epoch and cannot be represented as UnixNanos")?;
5959 Ok(UnixNanos::from(timestamp))
5960}
5961
5962fn derive_quote_from_depth(depth: &OrderBookDepth10) -> Option<QuoteTick> {
5965 let bid = depth.bids.first()?;
5966 let ask = depth.asks.first()?;
5967
5968 if bid.side.is_none() || ask.side.is_none() || bid.size.is_zero() || ask.size.is_zero() {
5969 return None;
5970 }
5971
5972 Some(QuoteTick::new(
5973 depth.instrument_id,
5974 bid.price,
5975 ask.price,
5976 bid.size,
5977 ask.size,
5978 depth.ts_event,
5979 depth.ts_init,
5980 ))
5981}
5982
5983fn process_engine_bar(
5987 cache: &Rc<RefCell<Cache>>,
5988 validate_sequence: bool,
5989 publish: bool,
5990 bar: Bar,
5991) {
5992 debug_assert!(
5993 bar.bar_type.is_standard(),
5994 "bars must be published and cached under the standard bar type"
5995 );
5996
5997 if !validate_bar_sequence(cache, validate_sequence, &bar) {
5998 return;
5999 }
6000
6001 if let Err(e) = cache.as_ref().borrow_mut().add_bar(bar) {
6002 log_error_on_cache_insert(&e);
6003 }
6004
6005 if publish {
6006 let topic = switchboard::get_bars_topic(bar.bar_type);
6007 msgbus::publish_bar(topic, &bar);
6008 }
6009}
6010
6011fn validate_bar_sequence(cache: &Rc<RefCell<Cache>>, validate_sequence: bool, bar: &Bar) -> bool {
6012 if !validate_sequence {
6013 return true;
6014 }
6015
6016 let Some(last_bar) = cache.as_ref().borrow().bar(&bar.bar_type).copied() else {
6017 return true;
6018 };
6019
6020 if bar.ts_event < last_bar.ts_event {
6021 log::warn!(
6022 "Bar {bar} was prior to last bar `ts_event` {}",
6023 last_bar.ts_event,
6024 );
6025 return false;
6026 }
6027
6028 if bar.ts_init < last_bar.ts_init {
6029 log::warn!(
6030 "Bar {bar} was prior to last bar `ts_init` {}",
6031 last_bar.ts_init,
6032 );
6033 return false;
6034 }
6035
6036 true
6039}
6040
6041#[inline(always)]
6042fn log_if_empty_response<T, I: Display>(data: &[T], id: &I, correlation_id: &UUID4) -> bool {
6043 if data.is_empty() {
6044 let name = type_name::<T>();
6045 let short_name = name.rsplit("::").next().unwrap_or(name);
6046 log::warn!("Received empty {short_name} response for {id} {correlation_id}");
6047 return true;
6048 }
6049 false
6050}
6051
6052fn rebuild_pipeline_response(
6060 parent_id: UUID4,
6061 parent: Option<&RequestCommand>,
6062 legs: Vec<DataResponse>,
6063) -> Option<DataResponse> {
6064 if legs.is_empty() {
6065 return None;
6066 }
6067
6068 let (parent_start, parent_end) = parent_request_window(parent);
6069
6070 let mut iter = legs.into_iter();
6071 let first = iter.next()?;
6072
6073 match first {
6074 DataResponse::Data(mut acc) => {
6075 let mut data = custom_response_data(&acc, parent_id)?;
6076
6077 for leg in iter {
6078 let DataResponse::Data(other) = leg else {
6079 log::error!("Mixed-variant legs in pipeline {parent_id}");
6080 return None;
6081 };
6082 data.extend(custom_response_data(&other, parent_id)?);
6083 }
6084
6085 data.sort_by_key(CustomData::ts_init);
6086 acc.data = std::sync::Arc::new(data);
6087 acc.correlation_id = parent_id;
6088 if parent_start.is_some() {
6089 acc.start = parent_start;
6090 }
6091
6092 if parent_end.is_some() {
6093 acc.end = parent_end;
6094 }
6095 Some(DataResponse::Data(acc))
6096 }
6097 DataResponse::Quotes(mut acc) => {
6098 for leg in iter {
6099 let DataResponse::Quotes(other) = leg else {
6100 log::error!("Mixed-variant legs in pipeline {parent_id}");
6101 return None;
6102 };
6103 acc.data.extend(other.data);
6104 }
6105 acc.data.sort_by_key(|q| q.ts_init);
6106 acc.correlation_id = parent_id;
6107 if parent_start.is_some() {
6108 acc.start = parent_start;
6109 }
6110
6111 if parent_end.is_some() {
6112 acc.end = parent_end;
6113 }
6114 Some(DataResponse::Quotes(acc))
6115 }
6116 DataResponse::Trades(mut acc) => {
6117 for leg in iter {
6118 let DataResponse::Trades(other) = leg else {
6119 log::error!("Mixed-variant legs in pipeline {parent_id}");
6120 return None;
6121 };
6122 acc.data.extend(other.data);
6123 }
6124 acc.data.sort_by_key(|t| t.ts_init);
6125 acc.correlation_id = parent_id;
6126 if parent_start.is_some() {
6127 acc.start = parent_start;
6128 }
6129
6130 if parent_end.is_some() {
6131 acc.end = parent_end;
6132 }
6133 Some(DataResponse::Trades(acc))
6134 }
6135 DataResponse::FundingRates(mut acc) => {
6136 for leg in iter {
6137 let DataResponse::FundingRates(other) = leg else {
6138 log::error!("Mixed-variant legs in pipeline {parent_id}");
6139 return None;
6140 };
6141 acc.data.extend(other.data);
6142 }
6143 acc.data.sort_by_key(|r| r.ts_init);
6144 acc.correlation_id = parent_id;
6145 if parent_start.is_some() {
6146 acc.start = parent_start;
6147 }
6148
6149 if parent_end.is_some() {
6150 acc.end = parent_end;
6151 }
6152 Some(DataResponse::FundingRates(acc))
6153 }
6154 DataResponse::Bars(mut acc) => {
6155 for leg in iter {
6156 let DataResponse::Bars(other) = leg else {
6157 log::error!("Mixed-variant legs in pipeline {parent_id}");
6158 return None;
6159 };
6160 acc.data.extend(other.data);
6161 }
6162 acc.data.sort_by_key(|b| b.ts_init);
6163 acc.correlation_id = parent_id;
6164 if parent_start.is_some() {
6165 acc.start = parent_start;
6166 }
6167
6168 if parent_end.is_some() {
6169 acc.end = parent_end;
6170 }
6171 Some(DataResponse::Bars(acc))
6172 }
6173 DataResponse::Instruments(mut acc) => {
6174 for leg in iter {
6175 let DataResponse::Instruments(other) = leg else {
6176 log::error!("Mixed-variant legs in pipeline {parent_id}");
6177 return None;
6178 };
6179 acc.data.extend(other.data);
6180 }
6181 acc.correlation_id = parent_id;
6182 Some(DataResponse::Instruments(acc))
6183 }
6184 DataResponse::BookDeltas(mut acc) => {
6185 for leg in iter {
6186 let DataResponse::BookDeltas(other) = leg else {
6187 log::error!("Mixed-variant legs in pipeline {parent_id}");
6188 return None;
6189 };
6190
6191 let same_instrument = other.instrument_id == acc.instrument_id
6196 || (other.instrument_id.symbol.as_str() == acc.instrument_id.symbol.as_str()
6197 && other.instrument_id.venue.as_str() == acc.instrument_id.venue.as_str());
6198
6199 if !same_instrument {
6200 log::error!(
6201 "Mixed-instrument BookDeltas legs in pipeline {parent_id}: {} and {}",
6202 acc.instrument_id,
6203 other.instrument_id,
6204 );
6205 return None;
6206 }
6207
6208 acc.data.extend(other.data);
6209 }
6210 acc.data.sort_by_key(|d| d.ts_init);
6211 acc.correlation_id = parent_id;
6212 if parent_start.is_some() {
6213 acc.start = parent_start;
6214 }
6215
6216 if parent_end.is_some() {
6217 acc.end = parent_end;
6218 }
6219 Some(DataResponse::BookDeltas(acc))
6220 }
6221 DataResponse::BookDepth(mut acc) => {
6222 for leg in iter {
6223 let DataResponse::BookDepth(other) = leg else {
6224 log::error!("Mixed-variant legs in pipeline {parent_id}");
6225 return None;
6226 };
6227 acc.data.extend(other.data);
6228 }
6229 acc.data.sort_by_key(|d| d.ts_init);
6230 acc.correlation_id = parent_id;
6231 if parent_start.is_some() {
6232 acc.start = parent_start;
6233 }
6234
6235 if parent_end.is_some() {
6236 acc.end = parent_end;
6237 }
6238 Some(DataResponse::BookDepth(acc))
6239 }
6240 other => {
6241 log::error!(
6246 "Pipeline rebuild not supported for variant {} (parent {parent_id})",
6247 other.kind(),
6248 );
6249 None
6250 }
6251 }
6252}
6253
6254fn custom_response_data(resp: &CustomDataResponse, parent_id: UUID4) -> Option<Vec<CustomData>> {
6255 if let Some(data) = resp.data.as_ref().downcast_ref::<Vec<CustomData>>() {
6256 return Some(data.clone());
6257 }
6258
6259 if let Some(data) = resp.data.as_ref().downcast_ref::<CustomData>() {
6260 return Some(vec![data.clone()]);
6261 }
6262
6263 if let Some(data) = resp.data.as_ref().downcast_ref::<Vec<Data>>() {
6264 let mut custom = Vec::with_capacity(data.len());
6265 for item in data {
6266 let Data::Custom(value) = item else {
6267 log::error!("Custom data pipeline {parent_id} received non-custom data {item:?}");
6268 return None;
6269 };
6270 custom.push(value.clone());
6271 }
6272 return Some(custom);
6273 }
6274
6275 log::error!(
6276 "Custom data pipeline {parent_id} received unsupported payload for {}",
6277 resp.data_type,
6278 );
6279 None
6280}
6281
6282fn parent_request_window(
6283 parent: Option<&RequestCommand>,
6284) -> (Option<UnixNanos>, Option<UnixNanos>) {
6285 let Some(parent) = parent else {
6286 return (None, None);
6287 };
6288
6289 let (start, end) = match parent {
6290 RequestCommand::Data(cmd) => (cmd.start, cmd.end),
6291 RequestCommand::Instrument(cmd) => (cmd.start, cmd.end),
6292 RequestCommand::Instruments(cmd) => (cmd.start, cmd.end),
6293 RequestCommand::BookDeltas(cmd) => (cmd.start, cmd.end),
6294 RequestCommand::BookDepth(cmd) => (cmd.start, cmd.end),
6295 RequestCommand::Quotes(cmd) => (cmd.start, cmd.end),
6296 RequestCommand::Trades(cmd) => (cmd.start, cmd.end),
6297 RequestCommand::FundingRates(cmd) => (cmd.start, cmd.end),
6298 RequestCommand::Bars(cmd) => (cmd.start, cmd.end),
6299 RequestCommand::Join(cmd) => (cmd.start, cmd.end),
6300 RequestCommand::BookSnapshot(_) | RequestCommand::OptionChainReferencePrice(_) => {
6301 return (None, None);
6302 }
6303 };
6304
6305 (
6306 start.map(datetime_to_unix_nanos_or_zero),
6307 end.map(datetime_to_unix_nanos_or_zero),
6308 )
6309}
6310
6311fn datetime_to_unix_nanos_or_zero(dt: jiff::Timestamp) -> UnixNanos {
6312 UnixNanos::from(u64::try_from(dt.as_nanosecond().max(0)).unwrap_or(0))
6313}
6314
6315fn empty_response_like(
6316 template: &DataResponse,
6317 correlation_id: UUID4,
6318 ts_init: UnixNanos,
6319) -> DataResponse {
6320 match template {
6321 DataResponse::Quotes(r) => DataResponse::Quotes(QuotesResponse::new(
6322 correlation_id,
6323 r.client_id,
6324 r.instrument_id,
6325 Vec::new(),
6326 r.start,
6327 r.end,
6328 ts_init,
6329 r.params.clone(),
6330 )),
6331 DataResponse::Trades(r) => DataResponse::Trades(TradesResponse::new(
6332 correlation_id,
6333 r.client_id,
6334 r.instrument_id,
6335 Vec::new(),
6336 r.start,
6337 r.end,
6338 ts_init,
6339 r.params.clone(),
6340 )),
6341 DataResponse::FundingRates(r) => DataResponse::FundingRates(FundingRatesResponse::new(
6342 correlation_id,
6343 r.client_id,
6344 r.instrument_id,
6345 Vec::new(),
6346 r.start,
6347 r.end,
6348 ts_init,
6349 r.params.clone(),
6350 )),
6351 DataResponse::Bars(r) => DataResponse::Bars(BarsResponse::new(
6352 correlation_id,
6353 r.client_id,
6354 r.bar_type,
6355 Vec::new(),
6356 r.start,
6357 r.end,
6358 ts_init,
6359 r.params.clone(),
6360 )),
6361 DataResponse::BookDeltas(r) => DataResponse::BookDeltas(BookDeltasResponse::new(
6362 correlation_id,
6363 r.client_id,
6364 r.instrument_id,
6365 Vec::new(),
6366 r.start,
6367 r.end,
6368 ts_init,
6369 r.params.clone(),
6370 )),
6371 DataResponse::BookDepth(r) => DataResponse::BookDepth(BookDepthResponse::new(
6372 correlation_id,
6373 r.client_id,
6374 r.instrument_id,
6375 Vec::new(),
6376 r.start,
6377 r.end,
6378 ts_init,
6379 r.params.clone(),
6380 )),
6381 other => {
6382 log::error!(
6383 "Cannot fabricate empty leg response for variant {}",
6384 other.kind(),
6385 );
6386 other.clone()
6387 }
6388 }
6389}
6390
6391fn rebind_response_correlation(mut resp: DataResponse, new_id: UUID4) -> DataResponse {
6392 match &mut resp {
6393 DataResponse::Data(r) => r.correlation_id = new_id,
6394 DataResponse::Instrument(r) => r.correlation_id = new_id,
6395 DataResponse::Instruments(r) => r.correlation_id = new_id,
6396 DataResponse::Book(r) => r.correlation_id = new_id,
6397 DataResponse::BookDeltas(r) => r.correlation_id = new_id,
6398 DataResponse::BookDepth(r) => r.correlation_id = new_id,
6399 DataResponse::Quotes(r) => r.correlation_id = new_id,
6400 DataResponse::Trades(r) => r.correlation_id = new_id,
6401 DataResponse::FundingRates(r) => r.correlation_id = new_id,
6402 DataResponse::OptionChainReferencePrice(r) => r.correlation_id = new_id,
6403 DataResponse::Bars(r) => r.correlation_id = new_id,
6404 }
6405 resp
6406}