1use std::{
22 fmt::{Debug, Display},
23 hash::Hash,
24 ops::{Deref, DerefMut},
25};
26
27use ahash::AHashSet;
28use indexmap::IndexMap;
29#[cfg(feature = "defi")]
30use nautilus_common::messages::defi::DefiSubscribeCommand;
31use nautilus_common::{
32 clients::{DataClient, log_command_error},
33 enums::LogColor,
34 log_info,
35 messages::data::{
36 RequestBars, RequestBookDepth, RequestBookSnapshot, RequestCustomData, RequestFundingRates,
37 RequestInstrument, RequestInstruments, RequestOptionChainReferencePrice, RequestQuotes,
38 RequestTrades, SubscribeBars, SubscribeBookDeltas, SubscribeBookDepth, SubscribeCommand,
39 SubscribeCustomData, SubscribeFundingRates, SubscribeIndexPrices, SubscribeInstrument,
40 SubscribeInstrumentClose, SubscribeInstrumentStatus, SubscribeInstruments,
41 SubscribeMarkPrices, SubscribeOptionGreeks, SubscribeQuotes, SubscribeTrades,
42 SubscriptionError, UnsubscribeBars, UnsubscribeBookDeltas, UnsubscribeBookDepth,
43 UnsubscribeCommand, UnsubscribeCustomData, UnsubscribeFundingRates, UnsubscribeIndexPrices,
44 UnsubscribeInstrument, UnsubscribeInstrumentClose, UnsubscribeInstrumentStatus,
45 UnsubscribeInstruments, UnsubscribeMarkPrices, UnsubscribeOptionGreeks, UnsubscribeQuotes,
46 UnsubscribeTrades, ensure_routable_bar_types, ensure_routable_instrument_ids,
47 },
48};
49#[cfg(feature = "defi")]
50use nautilus_model::defi::Blockchain;
51use nautilus_model::{
52 data::{BarType, DataType},
53 identifiers::{ClientId, InstrumentId, Venue},
54};
55
56#[cfg(feature = "defi")]
57#[allow(unused_imports)] use crate::defi::client as _;
59#[cfg(feature = "defi")]
60use crate::subscription::DefiSubscriptionKey;
61use crate::subscription::{
62 SubscriptionKey, SubscriptionRegistry, SubscriptionRelease, split_subscribe, split_unsubscribe,
63};
64
65pub struct DataClientAdapter {
67 pub(crate) client: Box<dyn DataClient>,
68 pub(crate) is_stopped: bool,
69 pub client_id: ClientId,
70 pub venue: Option<Venue>,
71 pub handles_book_deltas: bool,
72 pub handles_book_snapshots: bool,
73 pub subscriptions_custom: AHashSet<DataType>,
74 pub subscriptions_book_deltas: AHashSet<InstrumentId>,
75 pub subscriptions_book_depth: AHashSet<InstrumentId>,
76 pub subscriptions_quotes: AHashSet<InstrumentId>,
77 pub subscriptions_trades: AHashSet<InstrumentId>,
78 pub subscriptions_bars: AHashSet<BarType>,
79 pub subscriptions_instrument_status: AHashSet<InstrumentId>,
80 pub subscriptions_instrument_close: AHashSet<InstrumentId>,
81 pub subscriptions_instrument: AHashSet<InstrumentId>,
82 pub subscriptions_instrument_venue: AHashSet<Venue>,
83 pub subscriptions_mark_prices: AHashSet<InstrumentId>,
84 pub subscriptions_index_prices: AHashSet<InstrumentId>,
85 pub subscriptions_funding_rates: AHashSet<InstrumentId>,
86 pub subscriptions_option_greeks: AHashSet<InstrumentId>,
87 subscriptions_active: SubscriptionRegistry<SubscriptionKey, SubscribeCommand>,
88 #[cfg(feature = "defi")]
89 pub(crate) subscriptions_active_defi:
90 SubscriptionRegistry<DefiSubscriptionKey, DefiSubscribeCommand>,
91 #[cfg(feature = "defi")]
92 pub subscriptions_blocks: AHashSet<Blockchain>,
93 #[cfg(feature = "defi")]
94 pub subscriptions_pools: AHashSet<InstrumentId>,
95 #[cfg(feature = "defi")]
96 pub subscriptions_pool_swaps: AHashSet<InstrumentId>,
97 #[cfg(feature = "defi")]
98 pub subscriptions_pool_liquidity_updates: AHashSet<InstrumentId>,
99 #[cfg(feature = "defi")]
100 pub subscriptions_pool_fee_collects: AHashSet<InstrumentId>,
101 #[cfg(feature = "defi")]
102 pub subscriptions_pool_flash: AHashSet<InstrumentId>,
103}
104
105impl Deref for DataClientAdapter {
106 type Target = Box<dyn DataClient>;
107
108 fn deref(&self) -> &Self::Target {
109 &self.client
110 }
111}
112
113impl DerefMut for DataClientAdapter {
114 fn deref_mut(&mut self) -> &mut Self::Target {
115 &mut self.client
116 }
117}
118
119impl Debug for DataClientAdapter {
120 #[rustfmt::skip]
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 f.debug_struct(stringify!(DataClientAdapter))
123 .field("client_id", &self.client_id)
124 .field("venue", &self.venue)
125 .field("handles_book_deltas", &self.handles_book_deltas)
126 .field("handles_book_snapshots", &self.handles_book_snapshots)
127 .field("subscriptions_custom", &self.subscriptions_custom)
128 .field("subscriptions_book_deltas", &self.subscriptions_book_deltas)
129 .field("subscriptions_book_depth", &self.subscriptions_book_depth)
130 .field("subscriptions_quotes", &self.subscriptions_quotes)
131 .field("subscriptions_trades", &self.subscriptions_trades)
132 .field("subscriptions_bars", &self.subscriptions_bars)
133 .field("subscriptions_mark_prices", &self.subscriptions_mark_prices)
134 .field("subscriptions_index_prices", &self.subscriptions_index_prices)
135 .field("subscriptions_instrument_status", &self.subscriptions_instrument_status)
136 .field("subscriptions_instrument_close", &self.subscriptions_instrument_close)
137 .field("subscriptions_instrument", &self.subscriptions_instrument)
138 .field("subscriptions_instrument_venue", &self.subscriptions_instrument_venue)
139 .finish()
140 }
141}
142
143impl DataClientAdapter {
144 #[must_use]
146 pub fn new(
147 client_id: ClientId,
148 venue: Option<Venue>,
149 handles_order_book_deltas: bool,
150 handles_order_book_snapshots: bool,
151 client: Box<dyn DataClient>,
152 ) -> Self {
153 Self {
154 client,
155 is_stopped: false,
156 client_id,
157 venue,
158 handles_book_deltas: handles_order_book_deltas,
159 handles_book_snapshots: handles_order_book_snapshots,
160 subscriptions_custom: AHashSet::new(),
161 subscriptions_book_deltas: AHashSet::new(),
162 subscriptions_book_depth: AHashSet::new(),
163 subscriptions_quotes: AHashSet::new(),
164 subscriptions_trades: AHashSet::new(),
165 subscriptions_mark_prices: AHashSet::new(),
166 subscriptions_index_prices: AHashSet::new(),
167 subscriptions_funding_rates: AHashSet::new(),
168 subscriptions_option_greeks: AHashSet::new(),
169 subscriptions_bars: AHashSet::new(),
170 subscriptions_instrument_status: AHashSet::new(),
171 subscriptions_instrument_close: AHashSet::new(),
172 subscriptions_instrument: AHashSet::new(),
173 subscriptions_instrument_venue: AHashSet::new(),
174 subscriptions_active: SubscriptionRegistry::default(),
175 #[cfg(feature = "defi")]
176 subscriptions_active_defi: SubscriptionRegistry::default(),
177 #[cfg(feature = "defi")]
178 subscriptions_blocks: AHashSet::new(),
179 #[cfg(feature = "defi")]
180 subscriptions_pools: AHashSet::new(),
181 #[cfg(feature = "defi")]
182 subscriptions_pool_swaps: AHashSet::new(),
183 #[cfg(feature = "defi")]
184 subscriptions_pool_liquidity_updates: AHashSet::new(),
185 #[cfg(feature = "defi")]
186 subscriptions_pool_fee_collects: AHashSet::new(),
187 #[cfg(feature = "defi")]
188 subscriptions_pool_flash: AHashSet::new(),
189 }
190 }
191
192 #[expect(clippy::borrowed_box)]
193 #[must_use]
194 pub fn get_client(&self) -> &Box<dyn DataClient> {
195 &self.client
196 }
197
198 pub async fn connect(&mut self) -> anyhow::Result<()> {
204 self.client.connect().await
205 }
206
207 pub async fn disconnect(&mut self) -> anyhow::Result<()> {
213 self.client.disconnect().await
214 }
215
216 #[inline]
217 pub fn execute_subscribe(&mut self, cmd: SubscribeCommand) {
218 self.execute_subscribe_with_retained(cmd.clone(), cmd, false);
219 }
220
221 pub(crate) fn execute_subscribe_intent(&mut self, cmd: SubscribeCommand) {
222 self.execute_subscribe_with_retained(cmd.clone(), cmd, true);
223 }
224
225 pub(crate) fn execute_subscribe_with_retained(
226 &mut self,
227 cmd: SubscribeCommand,
228 retained: SubscribeCommand,
229 retain_on_failure: bool,
230 ) {
231 for command in [&cmd, &retained] {
232 if let Some(bar_types) = command.bar_types()
233 && let Err(e) = ensure_routable_bar_types(bar_types, command.client_id().is_some())
234 {
235 log_command_error(
236 &format_args!(
237 "Subscribe {} command {}",
238 self.client_id(),
239 command.command_id()
240 ),
241 &e,
242 );
243 return;
244 }
245
246 if let Some(ids) = command.instrument_ids()
247 && let Err(e) = ensure_routable_instrument_ids(ids, command.client_id().is_some())
248 {
249 log_command_error(
250 &format_args!(
251 "Subscribe {} command {}",
252 self.client_id(),
253 command.command_id()
254 ),
255 &e,
256 );
257 return;
258 }
259 }
260
261 self.subscribe_retained(cmd, retained, retain_on_failure);
262 }
263
264 pub(crate) fn retain_intent(&mut self, command: &SubscribeCommand) {
265 for member in split_subscribe(command.clone()) {
266 self.subscriptions_active.retain(
267 SubscriptionKey::from_subscribe(&member),
268 member.command_id(),
269 member,
270 );
271 }
272 }
273
274 fn subscribe_retained(
275 &mut self,
276 cmd: SubscribeCommand,
277 retained: SubscribeCommand,
278 retain_on_failure: bool,
279 ) {
280 let mut pending = Vec::new();
281
282 for member in split_subscribe(retained) {
283 let key = SubscriptionKey::from_subscribe(&member);
284 if self.has_active_subscription(&member) {
285 self.subscriptions_active
286 .retain(key, member.command_id(), member);
287 } else {
288 pending.push((key, member));
289 }
290 }
291
292 if pending.is_empty() {
293 return;
294 }
295
296 let command_id = cmd.command_id();
297
298 if self.is_stopped {
299 self.retain_subscriptions(pending, false, retain_on_failure);
300 log::debug!(
301 "Skipped subscribe after stop: client_id={}, command_id={command_id}",
302 self.client_id()
303 );
304 return;
305 }
306
307 let result = match cmd {
308 SubscribeCommand::Data(cmd) => self.subscribe(cmd),
309 SubscribeCommand::Instrument(cmd) => self.subscribe_instrument(&cmd),
310 SubscribeCommand::Instruments(cmd) => self.subscribe_instruments(cmd),
311 SubscribeCommand::BookDeltas(cmd) => self.subscribe_book_deltas(&cmd),
312 SubscribeCommand::BookDepth(cmd) => self.subscribe_book_depth(&cmd),
313 SubscribeCommand::BookSnapshots(_) => Ok(()), SubscribeCommand::Quotes(cmd) => self.subscribe_quotes(&cmd),
315 SubscribeCommand::Trades(cmd) => self.subscribe_trades(&cmd),
316 SubscribeCommand::MarkPrices(cmd) => self.subscribe_mark_prices(&cmd),
317 SubscribeCommand::IndexPrices(cmd) => self.subscribe_index_prices(&cmd),
318 SubscribeCommand::FundingRates(cmd) => self.subscribe_funding_rates(&cmd),
319 SubscribeCommand::Bars(cmd) => self.subscribe_bars(&cmd),
320 SubscribeCommand::InstrumentStatus(cmd) => self.subscribe_instrument_status(&cmd),
321 SubscribeCommand::InstrumentClose(cmd) => self.subscribe_instrument_close(&cmd),
322 SubscribeCommand::OptionGreeks(cmd) => self.subscribe_option_greeks(&cmd),
323 SubscribeCommand::OptionChain(_) => Ok(()), };
325
326 if let Err(e) = &result {
327 let subscription = pending
328 .iter()
329 .filter(|(_, member)| !self.has_active_subscription(member))
330 .map(|(key, _)| key.log_context())
331 .collect::<Vec<_>>()
332 .join(", ");
333
334 log_command_error(
335 &format_args!(
336 "Subscribe {} unaccepted [{subscription}] command {command_id}",
337 self.client_id()
338 ),
339 e,
340 );
341 }
342
343 self.retain_subscriptions(pending, result.is_ok(), retain_on_failure);
344 }
345
346 fn retain_subscriptions(
347 &mut self,
348 pending: Vec<(SubscriptionKey, SubscribeCommand)>,
349 succeeded: bool,
350 retain_on_failure: bool,
351 ) {
352 for (key, member) in pending {
353 let accepted = succeeded || self.has_active_subscription(&member);
354 if !accepted && !retain_on_failure {
356 continue;
357 }
358
359 if accepted && let Some(subscription) = self.subscriptions_active.get_mut(&key) {
360 subscription.command = member.clone();
361 }
362
363 self.subscriptions_active
364 .retain(key, member.command_id(), member);
365 }
366 }
367
368 #[must_use]
369 #[rustfmt::skip]
370 pub(crate) fn has_active_subscription(&self, cmd: &SubscribeCommand) -> bool {
371 match cmd {
372 SubscribeCommand::Data(cmd) => self.subscriptions_custom.contains(&cmd.data_type),
373 SubscribeCommand::Instrument(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_instrument.contains(id)),
374 SubscribeCommand::Instruments(cmd) => self.subscriptions_instrument_venue.contains(&cmd.venue),
375 SubscribeCommand::BookDeltas(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_book_deltas.contains(id)),
376 SubscribeCommand::BookDepth(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_book_depth.contains(id)),
377 SubscribeCommand::Quotes(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_quotes.contains(id)),
378 SubscribeCommand::Trades(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_trades.contains(id)),
379 SubscribeCommand::Bars(cmd) => !cmd.bar_types.is_empty() && cmd.bar_types.iter().all(|bar_type| self.subscriptions_bars.contains(bar_type)),
380 SubscribeCommand::MarkPrices(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_mark_prices.contains(id)),
381 SubscribeCommand::IndexPrices(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_index_prices.contains(id)),
382 SubscribeCommand::FundingRates(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_funding_rates.contains(id)),
383 SubscribeCommand::InstrumentStatus(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_instrument_status.contains(id)),
384 SubscribeCommand::InstrumentClose(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_instrument_close.contains(id)),
385 SubscribeCommand::OptionGreeks(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_option_greeks.contains(id)),
386 SubscribeCommand::BookSnapshots(_) | SubscribeCommand::OptionChain(_) => self.subscriptions_active.contains(&SubscriptionKey::from_subscribe(cmd)),
387 }
388 }
389
390 pub(crate) fn clear_subscription_state(&mut self) {
391 self.subscriptions_custom.clear();
392 self.subscriptions_book_deltas.clear();
393 self.subscriptions_book_depth.clear();
394 self.subscriptions_quotes.clear();
395 self.subscriptions_trades.clear();
396 self.subscriptions_bars.clear();
397 self.subscriptions_instrument_status.clear();
398 self.subscriptions_instrument_close.clear();
399 self.subscriptions_instrument.clear();
400 self.subscriptions_instrument_venue.clear();
401 self.subscriptions_mark_prices.clear();
402 self.subscriptions_index_prices.clear();
403 self.subscriptions_funding_rates.clear();
404 self.subscriptions_option_greeks.clear();
405 self.subscriptions_active.clear();
406
407 #[cfg(feature = "defi")]
408 {
409 self.subscriptions_active_defi.clear();
410 self.subscriptions_blocks.clear();
411 self.subscriptions_pools.clear();
412 self.subscriptions_pool_swaps.clear();
413 self.subscriptions_pool_liquidity_updates.clear();
414 self.subscriptions_pool_fee_collects.clear();
415 self.subscriptions_pool_flash.clear();
416 }
417 }
418
419 #[inline]
420 pub fn execute_unsubscribe(&mut self, cmd: &UnsubscribeCommand) {
421 if let Some(bar_types) = cmd.bar_types()
422 && let Err(e) = ensure_routable_bar_types(bar_types, cmd.client_id().is_some())
423 {
424 log_command_error(
425 &format_args!(
426 "Unsubscribe {} command {}",
427 self.client_id(),
428 cmd.command_id()
429 ),
430 &e,
431 );
432 return;
433 }
434
435 if let Some(ids) = cmd.instrument_ids()
436 && let Err(e) = ensure_routable_instrument_ids(ids, cmd.client_id().is_some())
437 {
438 log_command_error(
439 &format_args!(
440 "Unsubscribe {} command {}",
441 self.client_id(),
442 cmd.command_id()
443 ),
444 &e,
445 );
446 return;
447 }
448
449 let mut releases = IndexMap::new();
450
451 for member in split_unsubscribe(cmd) {
452 let key = SubscriptionKey::from_unsubscribe(&member);
453
454 let (acquisition_id, command) = match self.subscriptions_active.release(&key) {
455 SubscriptionRelease::Retained => continue,
456 SubscriptionRelease::Final(subscribe) => (
457 subscribe.command_id(),
458 subscribe.into_unsubscribe(
459 cmd.command_id(),
460 cmd.ts_init(),
461 cmd.correlation_id(),
462 ),
463 ),
464 SubscriptionRelease::Untracked => (cmd.command_id(), member),
465 };
466
467 releases
468 .entry(acquisition_id)
469 .or_insert_with(Vec::new)
470 .push(command);
471 }
472
473 for command in releases.into_values().flat_map(UnsubscribeCommand::group) {
474 self.release_subscription(&command);
475 }
476 }
477
478 fn release_subscription(&mut self, command: &UnsubscribeCommand) {
479 let result = match command {
480 UnsubscribeCommand::Data(cmd) => self.unsubscribe(cmd),
481 UnsubscribeCommand::Instrument(cmd) => self.unsubscribe_instrument(cmd),
482 UnsubscribeCommand::Instruments(cmd) => self.unsubscribe_instruments(cmd),
483 UnsubscribeCommand::BookDeltas(cmd) => self.unsubscribe_book_deltas(cmd),
484 UnsubscribeCommand::BookDepth(cmd) => self.unsubscribe_book_depth(cmd),
485 UnsubscribeCommand::BookSnapshots(_) => Ok(()), UnsubscribeCommand::Quotes(cmd) => self.unsubscribe_quotes(cmd),
487 UnsubscribeCommand::Trades(cmd) => self.unsubscribe_trades(cmd),
488 UnsubscribeCommand::Bars(cmd) => self.unsubscribe_bars(cmd),
489 UnsubscribeCommand::MarkPrices(cmd) => self.unsubscribe_mark_prices(cmd),
490 UnsubscribeCommand::IndexPrices(cmd) => self.unsubscribe_index_prices(cmd),
491 UnsubscribeCommand::FundingRates(cmd) => self.unsubscribe_funding_rates(cmd),
492 UnsubscribeCommand::InstrumentStatus(cmd) => self.unsubscribe_instrument_status(cmd),
493 UnsubscribeCommand::InstrumentClose(cmd) => self.unsubscribe_instrument_close(cmd),
494 UnsubscribeCommand::OptionGreeks(cmd) => self.unsubscribe_option_greeks(cmd),
495 UnsubscribeCommand::OptionChain(_) => Ok(()), };
497
498 let members = split_unsubscribe(command);
499 for member in &members {
500 let key = SubscriptionKey::from_unsubscribe(member);
501 let subscribe = self
502 .subscriptions_active
503 .get_mut(&key)
504 .map(|entry| entry.command.clone());
505
506 if subscribe.is_none_or(|subscribe| !self.has_active_subscription(&subscribe)) {
507 self.subscriptions_active.remove(&key);
508 }
509 }
510
511 if let Err(e) = result {
512 let subscription = members
513 .iter()
514 .map(SubscriptionKey::from_unsubscribe)
515 .filter(|key| self.subscriptions_active.contains(key))
516 .map(|key| key.log_context())
517 .collect::<Vec<_>>()
518 .join(", ");
519
520 log_command_error(
521 &format_args!(
522 "Unsubscribe {} unreleased [{subscription}] command {}",
523 self.client_id(),
524 command.command_id()
525 ),
526 &e,
527 );
528 }
529 }
530
531 pub fn subscribe(&mut self, cmd: SubscribeCustomData) -> anyhow::Result<()> {
537 let data_type = cmd.data_type.clone();
538 Self::execute_tracked_subscribe(
539 self.client.as_mut(),
540 &mut self.subscriptions_custom,
541 data_type,
542 "",
543 |client| client.subscribe(cmd),
544 )
545 }
546
547 pub fn unsubscribe(&mut self, cmd: &UnsubscribeCustomData) -> anyhow::Result<()> {
553 Self::execute_tracked_unsubscribe(
554 self.client.as_mut(),
555 &mut self.subscriptions_custom,
556 &cmd.data_type,
557 "",
558 |client| client.unsubscribe(cmd),
559 )
560 }
561
562 fn subscribe_instruments(&mut self, cmd: SubscribeInstruments) -> anyhow::Result<()> {
568 Self::execute_tracked_subscribe(
569 self.client.as_mut(),
570 &mut self.subscriptions_instrument_venue,
571 cmd.venue,
572 "instruments",
573 |client| client.subscribe_instruments(cmd),
574 )
575 }
576
577 fn unsubscribe_instruments(&mut self, cmd: &UnsubscribeInstruments) -> anyhow::Result<()> {
583 Self::execute_tracked_unsubscribe(
584 self.client.as_mut(),
585 &mut self.subscriptions_instrument_venue,
586 &cmd.venue,
587 "instruments",
588 |client| client.unsubscribe_instruments(cmd),
589 )
590 }
591
592 fn subscribe_instrument(&mut self, cmd: &SubscribeInstrument) -> anyhow::Result<()> {
598 Self::subscribe_batch(
599 &mut self.subscriptions_instrument,
600 &cmd.instrument_ids,
601 "instrument",
602 |partial| &partial.instrument_ids_accepted,
603 |ids| {
604 self.client
605 .subscribe_instrument(cmd.with_instrument_ids(ids))
606 },
607 )
608 }
609
610 fn unsubscribe_instrument(&mut self, cmd: &UnsubscribeInstrument) -> anyhow::Result<()> {
616 Self::unsubscribe_batch(
617 &mut self.subscriptions_instrument,
618 &cmd.instrument_ids,
619 "instrument",
620 |partial| &partial.instrument_ids_accepted,
621 |ids| {
622 self.client
623 .unsubscribe_instrument(&cmd.with_instrument_ids(ids))
624 },
625 )
626 }
627
628 fn subscribe_book_deltas(&mut self, cmd: &SubscribeBookDeltas) -> anyhow::Result<()> {
634 Self::subscribe_batch(
635 &mut self.subscriptions_book_deltas,
636 &cmd.instrument_ids,
637 "order book deltas",
638 |partial| &partial.instrument_ids_accepted,
639 |ids| {
640 self.client
641 .subscribe_book_deltas(cmd.with_instrument_ids(ids))
642 },
643 )
644 }
645
646 fn unsubscribe_book_deltas(&mut self, cmd: &UnsubscribeBookDeltas) -> anyhow::Result<()> {
652 Self::unsubscribe_batch(
653 &mut self.subscriptions_book_deltas,
654 &cmd.instrument_ids,
655 "order book deltas",
656 |partial| &partial.instrument_ids_accepted,
657 |ids| {
658 self.client
659 .unsubscribe_book_deltas(&cmd.with_instrument_ids(ids))
660 },
661 )
662 }
663
664 fn subscribe_book_depth(&mut self, cmd: &SubscribeBookDepth) -> anyhow::Result<()> {
670 Self::subscribe_batch(
671 &mut self.subscriptions_book_depth,
672 &cmd.instrument_ids,
673 "order book depth",
674 |partial| &partial.instrument_ids_accepted,
675 |ids| {
676 self.client
677 .subscribe_book_depth(cmd.with_instrument_ids(ids))
678 },
679 )
680 }
681
682 fn unsubscribe_book_depth(&mut self, cmd: &UnsubscribeBookDepth) -> anyhow::Result<()> {
688 Self::unsubscribe_batch(
689 &mut self.subscriptions_book_depth,
690 &cmd.instrument_ids,
691 "order book depth",
692 |partial| &partial.instrument_ids_accepted,
693 |ids| {
694 self.client
695 .unsubscribe_book_depth(&cmd.with_instrument_ids(ids))
696 },
697 )
698 }
699
700 fn subscribe_quotes(&mut self, cmd: &SubscribeQuotes) -> anyhow::Result<()> {
706 Self::subscribe_batch(
707 &mut self.subscriptions_quotes,
708 &cmd.instrument_ids,
709 "quotes",
710 |partial| &partial.instrument_ids_accepted,
711 |ids| self.client.subscribe_quotes(cmd.with_instrument_ids(ids)),
712 )
713 }
714
715 fn unsubscribe_quotes(&mut self, cmd: &UnsubscribeQuotes) -> anyhow::Result<()> {
721 Self::unsubscribe_batch(
722 &mut self.subscriptions_quotes,
723 &cmd.instrument_ids,
724 "quotes",
725 |partial| &partial.instrument_ids_accepted,
726 |ids| {
727 self.client
728 .unsubscribe_quotes(&cmd.with_instrument_ids(ids))
729 },
730 )
731 }
732
733 fn subscribe_trades(&mut self, cmd: &SubscribeTrades) -> anyhow::Result<()> {
739 Self::subscribe_batch(
740 &mut self.subscriptions_trades,
741 &cmd.instrument_ids,
742 "trades",
743 |partial| &partial.instrument_ids_accepted,
744 |ids| self.client.subscribe_trades(cmd.with_instrument_ids(ids)),
745 )
746 }
747
748 fn unsubscribe_trades(&mut self, cmd: &UnsubscribeTrades) -> anyhow::Result<()> {
754 Self::unsubscribe_batch(
755 &mut self.subscriptions_trades,
756 &cmd.instrument_ids,
757 "trades",
758 |partial| &partial.instrument_ids_accepted,
759 |ids| {
760 self.client
761 .unsubscribe_trades(&cmd.with_instrument_ids(ids))
762 },
763 )
764 }
765
766 fn subscribe_batch<T: Copy + Eq + Hash + Display>(
767 subscriptions: &mut AHashSet<T>,
768 members: &[T],
769 label: &str,
770 accepted: fn(&SubscriptionError) -> &[T],
771 dispatch: impl FnOnce(&[T]) -> anyhow::Result<()>,
772 ) -> anyhow::Result<()> {
773 let ids: Vec<_> = members
774 .iter()
775 .copied()
776 .filter(|id| !subscriptions.contains(id))
777 .collect();
778
779 if ids.is_empty() {
780 return Ok(());
781 }
782
783 let result = dispatch(&ids);
784
785 let reported: AHashSet<_> = match &result {
786 Ok(()) => AHashSet::new(),
787 Err(e) => e
788 .downcast_ref::<SubscriptionError>()
789 .map_or(&[][..], accepted)
790 .iter()
791 .copied()
792 .collect(),
793 };
794
795 for id in ids
796 .iter()
797 .filter(|id| result.is_ok() || reported.contains(*id))
798 {
799 subscriptions.insert(*id);
800 log_info!("Subscribed {id} {label}", color = LogColor::Blue);
801 }
802
803 result
804 }
805
806 fn unsubscribe_batch<T: Copy + Eq + Hash + Display>(
807 subscriptions: &mut AHashSet<T>,
808 members: &[T],
809 label: &str,
810 accepted: fn(&SubscriptionError) -> &[T],
811 dispatch: impl FnOnce(&[T]) -> anyhow::Result<()>,
812 ) -> anyhow::Result<()> {
813 let ids: Vec<_> = members
814 .iter()
815 .copied()
816 .filter(|id| subscriptions.contains(id))
817 .collect();
818
819 if ids.is_empty() {
820 return Ok(());
821 }
822
823 let result = dispatch(&ids);
824
825 let reported: AHashSet<_> = match &result {
826 Ok(()) => AHashSet::new(),
827 Err(e) => e
828 .downcast_ref::<SubscriptionError>()
829 .map_or(&[][..], accepted)
830 .iter()
831 .copied()
832 .collect(),
833 };
834
835 for id in ids
836 .iter()
837 .filter(|id| result.is_ok() || reported.contains(*id))
838 {
839 subscriptions.remove(id);
840 log_info!("Unsubscribed {id} {label}", color = LogColor::Blue);
841 }
842
843 result
844 }
845
846 fn subscribe_bars(&mut self, cmd: &SubscribeBars) -> anyhow::Result<()> {
852 Self::subscribe_batch(
853 &mut self.subscriptions_bars,
854 &cmd.bar_types,
855 "bars",
856 |partial| &partial.bar_types_accepted,
857 |ids| self.client.subscribe_bars(cmd.with_bar_types(ids)),
858 )
859 }
860
861 fn unsubscribe_bars(&mut self, cmd: &UnsubscribeBars) -> anyhow::Result<()> {
867 Self::unsubscribe_batch(
868 &mut self.subscriptions_bars,
869 &cmd.bar_types,
870 "bars",
871 |partial| &partial.bar_types_accepted,
872 |ids| self.client.unsubscribe_bars(&cmd.with_bar_types(ids)),
873 )
874 }
875
876 fn subscribe_mark_prices(&mut self, cmd: &SubscribeMarkPrices) -> anyhow::Result<()> {
882 Self::subscribe_batch(
883 &mut self.subscriptions_mark_prices,
884 &cmd.instrument_ids,
885 "mark prices",
886 |partial| &partial.instrument_ids_accepted,
887 |ids| {
888 self.client
889 .subscribe_mark_prices(cmd.with_instrument_ids(ids))
890 },
891 )
892 }
893
894 fn unsubscribe_mark_prices(&mut self, cmd: &UnsubscribeMarkPrices) -> anyhow::Result<()> {
900 Self::unsubscribe_batch(
901 &mut self.subscriptions_mark_prices,
902 &cmd.instrument_ids,
903 "mark prices",
904 |partial| &partial.instrument_ids_accepted,
905 |ids| {
906 self.client
907 .unsubscribe_mark_prices(&cmd.with_instrument_ids(ids))
908 },
909 )
910 }
911
912 fn subscribe_index_prices(&mut self, cmd: &SubscribeIndexPrices) -> anyhow::Result<()> {
918 Self::subscribe_batch(
919 &mut self.subscriptions_index_prices,
920 &cmd.instrument_ids,
921 "index prices",
922 |partial| &partial.instrument_ids_accepted,
923 |ids| {
924 self.client
925 .subscribe_index_prices(cmd.with_instrument_ids(ids))
926 },
927 )
928 }
929
930 fn unsubscribe_index_prices(&mut self, cmd: &UnsubscribeIndexPrices) -> anyhow::Result<()> {
936 Self::unsubscribe_batch(
937 &mut self.subscriptions_index_prices,
938 &cmd.instrument_ids,
939 "index prices",
940 |partial| &partial.instrument_ids_accepted,
941 |ids| {
942 self.client
943 .unsubscribe_index_prices(&cmd.with_instrument_ids(ids))
944 },
945 )
946 }
947
948 fn subscribe_funding_rates(&mut self, cmd: &SubscribeFundingRates) -> anyhow::Result<()> {
954 Self::subscribe_batch(
955 &mut self.subscriptions_funding_rates,
956 &cmd.instrument_ids,
957 "funding rates",
958 |partial| &partial.instrument_ids_accepted,
959 |ids| {
960 self.client
961 .subscribe_funding_rates(cmd.with_instrument_ids(ids))
962 },
963 )
964 }
965
966 fn unsubscribe_funding_rates(&mut self, cmd: &UnsubscribeFundingRates) -> anyhow::Result<()> {
972 Self::unsubscribe_batch(
973 &mut self.subscriptions_funding_rates,
974 &cmd.instrument_ids,
975 "funding rates",
976 |partial| &partial.instrument_ids_accepted,
977 |ids| {
978 self.client
979 .unsubscribe_funding_rates(&cmd.with_instrument_ids(ids))
980 },
981 )
982 }
983
984 fn subscribe_instrument_status(
990 &mut self,
991 cmd: &SubscribeInstrumentStatus,
992 ) -> anyhow::Result<()> {
993 Self::subscribe_batch(
994 &mut self.subscriptions_instrument_status,
995 &cmd.instrument_ids,
996 "instrument status",
997 |partial| &partial.instrument_ids_accepted,
998 |ids| {
999 self.client
1000 .subscribe_instrument_status(cmd.with_instrument_ids(ids))
1001 },
1002 )
1003 }
1004
1005 fn unsubscribe_instrument_status(
1011 &mut self,
1012 cmd: &UnsubscribeInstrumentStatus,
1013 ) -> anyhow::Result<()> {
1014 Self::unsubscribe_batch(
1015 &mut self.subscriptions_instrument_status,
1016 &cmd.instrument_ids,
1017 "instrument status",
1018 |partial| &partial.instrument_ids_accepted,
1019 |ids| {
1020 self.client
1021 .unsubscribe_instrument_status(&cmd.with_instrument_ids(ids))
1022 },
1023 )
1024 }
1025
1026 fn subscribe_instrument_close(&mut self, cmd: &SubscribeInstrumentClose) -> anyhow::Result<()> {
1032 Self::subscribe_batch(
1033 &mut self.subscriptions_instrument_close,
1034 &cmd.instrument_ids,
1035 "instrument close",
1036 |partial| &partial.instrument_ids_accepted,
1037 |ids| {
1038 self.client
1039 .subscribe_instrument_close(cmd.with_instrument_ids(ids))
1040 },
1041 )
1042 }
1043
1044 fn unsubscribe_instrument_close(
1050 &mut self,
1051 cmd: &UnsubscribeInstrumentClose,
1052 ) -> anyhow::Result<()> {
1053 Self::unsubscribe_batch(
1054 &mut self.subscriptions_instrument_close,
1055 &cmd.instrument_ids,
1056 "instrument close",
1057 |partial| &partial.instrument_ids_accepted,
1058 |ids| {
1059 self.client
1060 .unsubscribe_instrument_close(&cmd.with_instrument_ids(ids))
1061 },
1062 )
1063 }
1064
1065 fn subscribe_option_greeks(&mut self, cmd: &SubscribeOptionGreeks) -> anyhow::Result<()> {
1071 Self::subscribe_batch(
1072 &mut self.subscriptions_option_greeks,
1073 &cmd.instrument_ids,
1074 "option greeks",
1075 |partial| &partial.instrument_ids_accepted,
1076 |ids| {
1077 self.client
1078 .subscribe_option_greeks(cmd.with_instrument_ids(ids))
1079 },
1080 )
1081 }
1082
1083 fn unsubscribe_option_greeks(&mut self, cmd: &UnsubscribeOptionGreeks) -> anyhow::Result<()> {
1089 Self::unsubscribe_batch(
1090 &mut self.subscriptions_option_greeks,
1091 &cmd.instrument_ids,
1092 "option greeks",
1093 |partial| &partial.instrument_ids_accepted,
1094 |ids| {
1095 self.client
1096 .unsubscribe_option_greeks(&cmd.with_instrument_ids(ids))
1097 },
1098 )
1099 }
1100
1101 pub(crate) fn execute_tracked_subscribe<T>(
1102 client: &mut dyn DataClient,
1103 set: &mut AHashSet<T>,
1104 key: T,
1105 data_type: &str,
1106 subscribe: impl FnOnce(&mut dyn DataClient) -> anyhow::Result<()>,
1107 ) -> anyhow::Result<()>
1108 where
1109 T: Eq + Hash + Display,
1110 {
1111 if set.contains(&key) {
1112 return Ok(());
1113 }
1114
1115 subscribe(client)?;
1116
1117 if data_type.is_empty() {
1118 log_info!("Subscribed {key}", color = LogColor::Blue);
1119 } else {
1120 log_info!("Subscribed {key} {data_type}", color = LogColor::Blue);
1121 }
1122 set.insert(key);
1123 Ok(())
1124 }
1125
1126 pub(crate) fn execute_tracked_unsubscribe<T>(
1127 client: &mut dyn DataClient,
1128 set: &mut AHashSet<T>,
1129 key: &T,
1130 data_type: &str,
1131 unsubscribe: impl FnOnce(&mut dyn DataClient) -> anyhow::Result<()>,
1132 ) -> anyhow::Result<()>
1133 where
1134 T: Eq + Hash + Display,
1135 {
1136 if !set.contains(key) {
1137 return Ok(());
1138 }
1139
1140 unsubscribe(client)?;
1141 set.remove(key);
1142 if data_type.is_empty() {
1143 log_info!("Unsubscribed {key}", color = LogColor::Blue);
1144 } else {
1145 log_info!("Unsubscribed {key} {data_type}", color = LogColor::Blue);
1146 }
1147 Ok(())
1148 }
1149
1150 pub fn request_data(&self, req: RequestCustomData) -> anyhow::Result<()> {
1156 self.client.request_data(req)
1157 }
1158
1159 pub fn request_instrument(&self, req: RequestInstrument) -> anyhow::Result<()> {
1165 self.client.request_instrument(req)
1166 }
1167
1168 pub fn request_instruments(&self, req: RequestInstruments) -> anyhow::Result<()> {
1174 self.client.request_instruments(req)
1175 }
1176
1177 pub fn request_book_snapshot(&self, req: RequestBookSnapshot) -> anyhow::Result<()> {
1183 self.client.request_book_snapshot(req)
1184 }
1185
1186 pub fn request_quotes(&self, req: RequestQuotes) -> anyhow::Result<()> {
1192 self.client.request_quotes(req)
1193 }
1194
1195 pub fn request_trades(&self, req: RequestTrades) -> anyhow::Result<()> {
1201 self.client.request_trades(req)
1202 }
1203
1204 pub fn request_funding_rates(&self, req: RequestFundingRates) -> anyhow::Result<()> {
1210 self.client.request_funding_rates(req)
1211 }
1212
1213 pub fn request_option_chain_reference_price(
1219 &self,
1220 req: RequestOptionChainReferencePrice,
1221 ) -> anyhow::Result<()> {
1222 self.client.request_option_chain_reference_price(req)
1223 }
1224
1225 pub fn request_bars(&self, req: RequestBars) -> anyhow::Result<()> {
1231 self.client.request_bars(req)
1232 }
1233
1234 pub fn request_book_depth(&self, req: RequestBookDepth) -> anyhow::Result<()> {
1240 self.client.request_book_depth(req)
1241 }
1242}