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 client_id: ClientId,
69 pub venue: Option<Venue>,
70 pub handles_book_deltas: bool,
71 pub handles_book_snapshots: bool,
72 pub subscriptions_custom: AHashSet<DataType>,
73 pub subscriptions_book_deltas: AHashSet<InstrumentId>,
74 pub subscriptions_book_depth: AHashSet<InstrumentId>,
75 pub subscriptions_quotes: AHashSet<InstrumentId>,
76 pub subscriptions_trades: AHashSet<InstrumentId>,
77 pub subscriptions_bars: AHashSet<BarType>,
78 pub subscriptions_instrument_status: AHashSet<InstrumentId>,
79 pub subscriptions_instrument_close: AHashSet<InstrumentId>,
80 pub subscriptions_instrument: AHashSet<InstrumentId>,
81 pub subscriptions_instrument_venue: AHashSet<Venue>,
82 pub subscriptions_mark_prices: AHashSet<InstrumentId>,
83 pub subscriptions_index_prices: AHashSet<InstrumentId>,
84 pub subscriptions_funding_rates: AHashSet<InstrumentId>,
85 pub subscriptions_option_greeks: AHashSet<InstrumentId>,
86 subscriptions_active: SubscriptionRegistry<SubscriptionKey, SubscribeCommand>,
87 #[cfg(feature = "defi")]
88 pub(crate) subscriptions_active_defi:
89 SubscriptionRegistry<DefiSubscriptionKey, DefiSubscribeCommand>,
90 #[cfg(feature = "defi")]
91 pub subscriptions_blocks: AHashSet<Blockchain>,
92 #[cfg(feature = "defi")]
93 pub subscriptions_pools: AHashSet<InstrumentId>,
94 #[cfg(feature = "defi")]
95 pub subscriptions_pool_swaps: AHashSet<InstrumentId>,
96 #[cfg(feature = "defi")]
97 pub subscriptions_pool_liquidity_updates: AHashSet<InstrumentId>,
98 #[cfg(feature = "defi")]
99 pub subscriptions_pool_fee_collects: AHashSet<InstrumentId>,
100 #[cfg(feature = "defi")]
101 pub subscriptions_pool_flash: AHashSet<InstrumentId>,
102}
103
104impl Deref for DataClientAdapter {
105 type Target = Box<dyn DataClient>;
106
107 fn deref(&self) -> &Self::Target {
108 &self.client
109 }
110}
111
112impl DerefMut for DataClientAdapter {
113 fn deref_mut(&mut self) -> &mut Self::Target {
114 &mut self.client
115 }
116}
117
118impl Debug for DataClientAdapter {
119 #[rustfmt::skip]
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 f.debug_struct(stringify!(DataClientAdapter))
122 .field("client_id", &self.client_id)
123 .field("venue", &self.venue)
124 .field("handles_book_deltas", &self.handles_book_deltas)
125 .field("handles_book_snapshots", &self.handles_book_snapshots)
126 .field("subscriptions_custom", &self.subscriptions_custom)
127 .field("subscriptions_book_deltas", &self.subscriptions_book_deltas)
128 .field("subscriptions_book_depth", &self.subscriptions_book_depth)
129 .field("subscriptions_quotes", &self.subscriptions_quotes)
130 .field("subscriptions_trades", &self.subscriptions_trades)
131 .field("subscriptions_bars", &self.subscriptions_bars)
132 .field("subscriptions_mark_prices", &self.subscriptions_mark_prices)
133 .field("subscriptions_index_prices", &self.subscriptions_index_prices)
134 .field("subscriptions_instrument_status", &self.subscriptions_instrument_status)
135 .field("subscriptions_instrument_close", &self.subscriptions_instrument_close)
136 .field("subscriptions_instrument", &self.subscriptions_instrument)
137 .field("subscriptions_instrument_venue", &self.subscriptions_instrument_venue)
138 .finish()
139 }
140}
141
142impl DataClientAdapter {
143 #[must_use]
145 pub fn new(
146 client_id: ClientId,
147 venue: Option<Venue>,
148 handles_order_book_deltas: bool,
149 handles_order_book_snapshots: bool,
150 client: Box<dyn DataClient>,
151 ) -> Self {
152 Self {
153 client,
154 client_id,
155 venue,
156 handles_book_deltas: handles_order_book_deltas,
157 handles_book_snapshots: handles_order_book_snapshots,
158 subscriptions_custom: AHashSet::new(),
159 subscriptions_book_deltas: AHashSet::new(),
160 subscriptions_book_depth: AHashSet::new(),
161 subscriptions_quotes: AHashSet::new(),
162 subscriptions_trades: AHashSet::new(),
163 subscriptions_mark_prices: AHashSet::new(),
164 subscriptions_index_prices: AHashSet::new(),
165 subscriptions_funding_rates: AHashSet::new(),
166 subscriptions_option_greeks: AHashSet::new(),
167 subscriptions_bars: AHashSet::new(),
168 subscriptions_instrument_status: AHashSet::new(),
169 subscriptions_instrument_close: AHashSet::new(),
170 subscriptions_instrument: AHashSet::new(),
171 subscriptions_instrument_venue: AHashSet::new(),
172 subscriptions_active: SubscriptionRegistry::default(),
173 #[cfg(feature = "defi")]
174 subscriptions_active_defi: SubscriptionRegistry::default(),
175 #[cfg(feature = "defi")]
176 subscriptions_blocks: AHashSet::new(),
177 #[cfg(feature = "defi")]
178 subscriptions_pools: AHashSet::new(),
179 #[cfg(feature = "defi")]
180 subscriptions_pool_swaps: AHashSet::new(),
181 #[cfg(feature = "defi")]
182 subscriptions_pool_liquidity_updates: AHashSet::new(),
183 #[cfg(feature = "defi")]
184 subscriptions_pool_fee_collects: AHashSet::new(),
185 #[cfg(feature = "defi")]
186 subscriptions_pool_flash: AHashSet::new(),
187 }
188 }
189
190 #[expect(clippy::borrowed_box)]
191 #[must_use]
192 pub fn get_client(&self) -> &Box<dyn DataClient> {
193 &self.client
194 }
195
196 pub async fn connect(&mut self) -> anyhow::Result<()> {
202 self.client.connect().await
203 }
204
205 pub async fn disconnect(&mut self) -> anyhow::Result<()> {
211 self.client.disconnect().await
212 }
213
214 #[inline]
215 pub fn execute_subscribe(&mut self, cmd: SubscribeCommand) {
216 self.execute_subscribe_with_retained(cmd.clone(), cmd, false);
217 }
218
219 pub(crate) fn execute_subscribe_intent(&mut self, cmd: SubscribeCommand) {
220 self.execute_subscribe_with_retained(cmd.clone(), cmd, true);
221 }
222
223 pub(crate) fn execute_subscribe_with_retained(
224 &mut self,
225 cmd: SubscribeCommand,
226 retained: SubscribeCommand,
227 retain_on_failure: bool,
228 ) {
229 for command in [&cmd, &retained] {
230 if let Some(bar_types) = command.bar_types()
231 && let Err(e) = ensure_routable_bar_types(bar_types, command.client_id().is_some())
232 {
233 log_command_error(
234 &format_args!(
235 "Subscribe {} command {}",
236 self.client_id(),
237 command.command_id()
238 ),
239 &e,
240 );
241 return;
242 }
243
244 if let Some(ids) = command.instrument_ids()
245 && let Err(e) = ensure_routable_instrument_ids(ids, command.client_id().is_some())
246 {
247 log_command_error(
248 &format_args!(
249 "Subscribe {} command {}",
250 self.client_id(),
251 command.command_id()
252 ),
253 &e,
254 );
255 return;
256 }
257 }
258
259 self.subscribe_retained(cmd, retained, retain_on_failure);
260 }
261
262 pub(crate) fn retain_intent(&mut self, command: &SubscribeCommand) {
263 for member in split_subscribe(command.clone()) {
264 self.subscriptions_active.retain(
265 SubscriptionKey::from_subscribe(&member),
266 member.command_id(),
267 member,
268 );
269 }
270 }
271
272 fn subscribe_retained(
273 &mut self,
274 cmd: SubscribeCommand,
275 retained: SubscribeCommand,
276 retain_on_failure: bool,
277 ) {
278 let mut pending = Vec::new();
279
280 for member in split_subscribe(retained) {
281 let key = SubscriptionKey::from_subscribe(&member);
282 if self.has_active_subscription(&member) {
283 self.subscriptions_active
284 .retain(key, member.command_id(), member);
285 } else {
286 pending.push((key, member));
287 }
288 }
289
290 if pending.is_empty() {
291 return;
292 }
293
294 let command_id = cmd.command_id();
295
296 let result = match cmd {
297 SubscribeCommand::Data(cmd) => self.subscribe(cmd),
298 SubscribeCommand::Instrument(cmd) => self.subscribe_instrument(&cmd),
299 SubscribeCommand::Instruments(cmd) => self.subscribe_instruments(cmd),
300 SubscribeCommand::BookDeltas(cmd) => self.subscribe_book_deltas(&cmd),
301 SubscribeCommand::BookDepth(cmd) => self.subscribe_book_depth(&cmd),
302 SubscribeCommand::BookSnapshots(_) => Ok(()), SubscribeCommand::Quotes(cmd) => self.subscribe_quotes(&cmd),
304 SubscribeCommand::Trades(cmd) => self.subscribe_trades(&cmd),
305 SubscribeCommand::MarkPrices(cmd) => self.subscribe_mark_prices(&cmd),
306 SubscribeCommand::IndexPrices(cmd) => self.subscribe_index_prices(&cmd),
307 SubscribeCommand::FundingRates(cmd) => self.subscribe_funding_rates(&cmd),
308 SubscribeCommand::Bars(cmd) => self.subscribe_bars(&cmd),
309 SubscribeCommand::InstrumentStatus(cmd) => self.subscribe_instrument_status(&cmd),
310 SubscribeCommand::InstrumentClose(cmd) => self.subscribe_instrument_close(&cmd),
311 SubscribeCommand::OptionGreeks(cmd) => self.subscribe_option_greeks(&cmd),
312 SubscribeCommand::OptionChain(_) => Ok(()), };
314
315 if let Err(e) = &result {
316 let subscription = pending
317 .iter()
318 .filter(|(_, member)| !self.has_active_subscription(member))
319 .map(|(key, _)| key.log_context())
320 .collect::<Vec<_>>()
321 .join(", ");
322
323 log_command_error(
324 &format_args!(
325 "Subscribe {} unaccepted [{subscription}] command {command_id}",
326 self.client_id()
327 ),
328 e,
329 );
330 }
331
332 self.retain_subscriptions(pending, &result, retain_on_failure);
333 }
334
335 fn retain_subscriptions(
336 &mut self,
337 pending: Vec<(SubscriptionKey, SubscribeCommand)>,
338 result: &anyhow::Result<()>,
339 retain_on_failure: bool,
340 ) {
341 for (key, member) in pending {
342 let accepted = result.is_ok() || self.has_active_subscription(&member);
343 if !accepted && !retain_on_failure {
345 continue;
346 }
347
348 if accepted && let Some(subscription) = self.subscriptions_active.get_mut(&key) {
349 subscription.command = member.clone();
350 }
351
352 self.subscriptions_active
353 .retain(key, member.command_id(), member);
354 }
355 }
356
357 #[must_use]
358 #[rustfmt::skip]
359 pub(crate) fn has_active_subscription(&self, cmd: &SubscribeCommand) -> bool {
360 match cmd {
361 SubscribeCommand::Data(cmd) => self.subscriptions_custom.contains(&cmd.data_type),
362 SubscribeCommand::Instrument(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_instrument.contains(id)),
363 SubscribeCommand::Instruments(cmd) => self.subscriptions_instrument_venue.contains(&cmd.venue),
364 SubscribeCommand::BookDeltas(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_book_deltas.contains(id)),
365 SubscribeCommand::BookDepth(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_book_depth.contains(id)),
366 SubscribeCommand::Quotes(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_quotes.contains(id)),
367 SubscribeCommand::Trades(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_trades.contains(id)),
368 SubscribeCommand::Bars(cmd) => !cmd.bar_types.is_empty() && cmd.bar_types.iter().all(|bar_type| self.subscriptions_bars.contains(bar_type)),
369 SubscribeCommand::MarkPrices(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_mark_prices.contains(id)),
370 SubscribeCommand::IndexPrices(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_index_prices.contains(id)),
371 SubscribeCommand::FundingRates(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_funding_rates.contains(id)),
372 SubscribeCommand::InstrumentStatus(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_instrument_status.contains(id)),
373 SubscribeCommand::InstrumentClose(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_instrument_close.contains(id)),
374 SubscribeCommand::OptionGreeks(cmd) => !cmd.instrument_ids.is_empty() && cmd.instrument_ids.iter().all(|id| self.subscriptions_option_greeks.contains(id)),
375 SubscribeCommand::BookSnapshots(_) | SubscribeCommand::OptionChain(_) => self.subscriptions_active.contains(&SubscriptionKey::from_subscribe(cmd)),
376 }
377 }
378
379 pub(crate) fn clear_subscription_state(&mut self) {
380 self.subscriptions_custom.clear();
381 self.subscriptions_book_deltas.clear();
382 self.subscriptions_book_depth.clear();
383 self.subscriptions_quotes.clear();
384 self.subscriptions_trades.clear();
385 self.subscriptions_bars.clear();
386 self.subscriptions_instrument_status.clear();
387 self.subscriptions_instrument_close.clear();
388 self.subscriptions_instrument.clear();
389 self.subscriptions_instrument_venue.clear();
390 self.subscriptions_mark_prices.clear();
391 self.subscriptions_index_prices.clear();
392 self.subscriptions_funding_rates.clear();
393 self.subscriptions_option_greeks.clear();
394 self.subscriptions_active.clear();
395
396 #[cfg(feature = "defi")]
397 {
398 self.subscriptions_active_defi.clear();
399 self.subscriptions_blocks.clear();
400 self.subscriptions_pools.clear();
401 self.subscriptions_pool_swaps.clear();
402 self.subscriptions_pool_liquidity_updates.clear();
403 self.subscriptions_pool_fee_collects.clear();
404 self.subscriptions_pool_flash.clear();
405 }
406 }
407
408 #[inline]
409 pub fn execute_unsubscribe(&mut self, cmd: &UnsubscribeCommand) {
410 if let Some(bar_types) = cmd.bar_types()
411 && let Err(e) = ensure_routable_bar_types(bar_types, cmd.client_id().is_some())
412 {
413 log_command_error(
414 &format_args!(
415 "Unsubscribe {} command {}",
416 self.client_id(),
417 cmd.command_id()
418 ),
419 &e,
420 );
421 return;
422 }
423
424 if let Some(ids) = cmd.instrument_ids()
425 && let Err(e) = ensure_routable_instrument_ids(ids, cmd.client_id().is_some())
426 {
427 log_command_error(
428 &format_args!(
429 "Unsubscribe {} command {}",
430 self.client_id(),
431 cmd.command_id()
432 ),
433 &e,
434 );
435 return;
436 }
437
438 let mut releases = IndexMap::new();
439
440 for member in split_unsubscribe(cmd) {
441 let key = SubscriptionKey::from_unsubscribe(&member);
442
443 let (acquisition_id, command) = match self.subscriptions_active.release(&key) {
444 SubscriptionRelease::Retained => continue,
445 SubscriptionRelease::Final(subscribe) => (
446 subscribe.command_id(),
447 subscribe.into_unsubscribe(
448 cmd.command_id(),
449 cmd.ts_init(),
450 cmd.correlation_id(),
451 ),
452 ),
453 SubscriptionRelease::Untracked => (cmd.command_id(), member),
454 };
455
456 releases
457 .entry(acquisition_id)
458 .or_insert_with(Vec::new)
459 .push(command);
460 }
461
462 for command in releases.into_values().flat_map(UnsubscribeCommand::group) {
463 self.release_subscription(&command);
464 }
465 }
466
467 fn release_subscription(&mut self, command: &UnsubscribeCommand) {
468 let result = match command {
469 UnsubscribeCommand::Data(cmd) => self.unsubscribe(cmd),
470 UnsubscribeCommand::Instrument(cmd) => self.unsubscribe_instrument(cmd),
471 UnsubscribeCommand::Instruments(cmd) => self.unsubscribe_instruments(cmd),
472 UnsubscribeCommand::BookDeltas(cmd) => self.unsubscribe_book_deltas(cmd),
473 UnsubscribeCommand::BookDepth(cmd) => self.unsubscribe_book_depth(cmd),
474 UnsubscribeCommand::BookSnapshots(_) => Ok(()), UnsubscribeCommand::Quotes(cmd) => self.unsubscribe_quotes(cmd),
476 UnsubscribeCommand::Trades(cmd) => self.unsubscribe_trades(cmd),
477 UnsubscribeCommand::Bars(cmd) => self.unsubscribe_bars(cmd),
478 UnsubscribeCommand::MarkPrices(cmd) => self.unsubscribe_mark_prices(cmd),
479 UnsubscribeCommand::IndexPrices(cmd) => self.unsubscribe_index_prices(cmd),
480 UnsubscribeCommand::FundingRates(cmd) => self.unsubscribe_funding_rates(cmd),
481 UnsubscribeCommand::InstrumentStatus(cmd) => self.unsubscribe_instrument_status(cmd),
482 UnsubscribeCommand::InstrumentClose(cmd) => self.unsubscribe_instrument_close(cmd),
483 UnsubscribeCommand::OptionGreeks(cmd) => self.unsubscribe_option_greeks(cmd),
484 UnsubscribeCommand::OptionChain(_) => Ok(()), };
486
487 let members = split_unsubscribe(command);
488 for member in &members {
489 let key = SubscriptionKey::from_unsubscribe(member);
490 let subscribe = self
491 .subscriptions_active
492 .get_mut(&key)
493 .map(|entry| entry.command.clone());
494
495 if subscribe.is_none_or(|subscribe| !self.has_active_subscription(&subscribe)) {
496 self.subscriptions_active.remove(&key);
497 }
498 }
499
500 if let Err(e) = result {
501 let subscription = members
502 .iter()
503 .map(SubscriptionKey::from_unsubscribe)
504 .filter(|key| self.subscriptions_active.contains(key))
505 .map(|key| key.log_context())
506 .collect::<Vec<_>>()
507 .join(", ");
508
509 log_command_error(
510 &format_args!(
511 "Unsubscribe {} unreleased [{subscription}] command {}",
512 self.client_id(),
513 command.command_id()
514 ),
515 &e,
516 );
517 }
518 }
519
520 pub fn subscribe(&mut self, cmd: SubscribeCustomData) -> anyhow::Result<()> {
526 let data_type = cmd.data_type.clone();
527 Self::execute_tracked_subscribe(
528 self.client.as_mut(),
529 &mut self.subscriptions_custom,
530 data_type,
531 "",
532 |client| client.subscribe(cmd),
533 )
534 }
535
536 pub fn unsubscribe(&mut self, cmd: &UnsubscribeCustomData) -> anyhow::Result<()> {
542 Self::execute_tracked_unsubscribe(
543 self.client.as_mut(),
544 &mut self.subscriptions_custom,
545 &cmd.data_type,
546 "",
547 |client| client.unsubscribe(cmd),
548 )
549 }
550
551 fn subscribe_instruments(&mut self, cmd: SubscribeInstruments) -> anyhow::Result<()> {
557 Self::execute_tracked_subscribe(
558 self.client.as_mut(),
559 &mut self.subscriptions_instrument_venue,
560 cmd.venue,
561 "instruments",
562 |client| client.subscribe_instruments(cmd),
563 )
564 }
565
566 fn unsubscribe_instruments(&mut self, cmd: &UnsubscribeInstruments) -> anyhow::Result<()> {
572 Self::execute_tracked_unsubscribe(
573 self.client.as_mut(),
574 &mut self.subscriptions_instrument_venue,
575 &cmd.venue,
576 "instruments",
577 |client| client.unsubscribe_instruments(cmd),
578 )
579 }
580
581 fn subscribe_instrument(&mut self, cmd: &SubscribeInstrument) -> anyhow::Result<()> {
587 Self::subscribe_batch(
588 &mut self.subscriptions_instrument,
589 &cmd.instrument_ids,
590 "instrument",
591 |partial| &partial.instrument_ids_accepted,
592 |ids| {
593 self.client
594 .subscribe_instrument(cmd.with_instrument_ids(ids))
595 },
596 )
597 }
598
599 fn unsubscribe_instrument(&mut self, cmd: &UnsubscribeInstrument) -> anyhow::Result<()> {
605 Self::unsubscribe_batch(
606 &mut self.subscriptions_instrument,
607 &cmd.instrument_ids,
608 "instrument",
609 |partial| &partial.instrument_ids_accepted,
610 |ids| {
611 self.client
612 .unsubscribe_instrument(&cmd.with_instrument_ids(ids))
613 },
614 )
615 }
616
617 fn subscribe_book_deltas(&mut self, cmd: &SubscribeBookDeltas) -> anyhow::Result<()> {
623 Self::subscribe_batch(
624 &mut self.subscriptions_book_deltas,
625 &cmd.instrument_ids,
626 "order book deltas",
627 |partial| &partial.instrument_ids_accepted,
628 |ids| {
629 self.client
630 .subscribe_book_deltas(cmd.with_instrument_ids(ids))
631 },
632 )
633 }
634
635 fn unsubscribe_book_deltas(&mut self, cmd: &UnsubscribeBookDeltas) -> anyhow::Result<()> {
641 Self::unsubscribe_batch(
642 &mut self.subscriptions_book_deltas,
643 &cmd.instrument_ids,
644 "order book deltas",
645 |partial| &partial.instrument_ids_accepted,
646 |ids| {
647 self.client
648 .unsubscribe_book_deltas(&cmd.with_instrument_ids(ids))
649 },
650 )
651 }
652
653 fn subscribe_book_depth(&mut self, cmd: &SubscribeBookDepth) -> anyhow::Result<()> {
659 Self::subscribe_batch(
660 &mut self.subscriptions_book_depth,
661 &cmd.instrument_ids,
662 "order book depth",
663 |partial| &partial.instrument_ids_accepted,
664 |ids| {
665 self.client
666 .subscribe_book_depth(cmd.with_instrument_ids(ids))
667 },
668 )
669 }
670
671 fn unsubscribe_book_depth(&mut self, cmd: &UnsubscribeBookDepth) -> anyhow::Result<()> {
677 Self::unsubscribe_batch(
678 &mut self.subscriptions_book_depth,
679 &cmd.instrument_ids,
680 "order book depth",
681 |partial| &partial.instrument_ids_accepted,
682 |ids| {
683 self.client
684 .unsubscribe_book_depth(&cmd.with_instrument_ids(ids))
685 },
686 )
687 }
688
689 fn subscribe_quotes(&mut self, cmd: &SubscribeQuotes) -> anyhow::Result<()> {
695 Self::subscribe_batch(
696 &mut self.subscriptions_quotes,
697 &cmd.instrument_ids,
698 "quotes",
699 |partial| &partial.instrument_ids_accepted,
700 |ids| self.client.subscribe_quotes(cmd.with_instrument_ids(ids)),
701 )
702 }
703
704 fn unsubscribe_quotes(&mut self, cmd: &UnsubscribeQuotes) -> anyhow::Result<()> {
710 Self::unsubscribe_batch(
711 &mut self.subscriptions_quotes,
712 &cmd.instrument_ids,
713 "quotes",
714 |partial| &partial.instrument_ids_accepted,
715 |ids| {
716 self.client
717 .unsubscribe_quotes(&cmd.with_instrument_ids(ids))
718 },
719 )
720 }
721
722 fn subscribe_trades(&mut self, cmd: &SubscribeTrades) -> anyhow::Result<()> {
728 Self::subscribe_batch(
729 &mut self.subscriptions_trades,
730 &cmd.instrument_ids,
731 "trades",
732 |partial| &partial.instrument_ids_accepted,
733 |ids| self.client.subscribe_trades(cmd.with_instrument_ids(ids)),
734 )
735 }
736
737 fn unsubscribe_trades(&mut self, cmd: &UnsubscribeTrades) -> anyhow::Result<()> {
743 Self::unsubscribe_batch(
744 &mut self.subscriptions_trades,
745 &cmd.instrument_ids,
746 "trades",
747 |partial| &partial.instrument_ids_accepted,
748 |ids| {
749 self.client
750 .unsubscribe_trades(&cmd.with_instrument_ids(ids))
751 },
752 )
753 }
754
755 fn subscribe_batch<T: Copy + Eq + Hash + Display>(
756 subscriptions: &mut AHashSet<T>,
757 members: &[T],
758 label: &str,
759 accepted: fn(&SubscriptionError) -> &[T],
760 dispatch: impl FnOnce(&[T]) -> anyhow::Result<()>,
761 ) -> anyhow::Result<()> {
762 let ids: Vec<_> = members
763 .iter()
764 .copied()
765 .filter(|id| !subscriptions.contains(id))
766 .collect();
767
768 if ids.is_empty() {
769 return Ok(());
770 }
771
772 let result = dispatch(&ids);
773
774 let reported: AHashSet<_> = match &result {
775 Ok(()) => AHashSet::new(),
776 Err(e) => e
777 .downcast_ref::<SubscriptionError>()
778 .map_or(&[][..], accepted)
779 .iter()
780 .copied()
781 .collect(),
782 };
783
784 for id in ids
785 .iter()
786 .filter(|id| result.is_ok() || reported.contains(*id))
787 {
788 subscriptions.insert(*id);
789 log_info!("Subscribed {id} {label}", color = LogColor::Blue);
790 }
791
792 result
793 }
794
795 fn unsubscribe_batch<T: Copy + Eq + Hash + Display>(
796 subscriptions: &mut AHashSet<T>,
797 members: &[T],
798 label: &str,
799 accepted: fn(&SubscriptionError) -> &[T],
800 dispatch: impl FnOnce(&[T]) -> anyhow::Result<()>,
801 ) -> anyhow::Result<()> {
802 let ids: Vec<_> = members
803 .iter()
804 .copied()
805 .filter(|id| subscriptions.contains(id))
806 .collect();
807
808 if ids.is_empty() {
809 return Ok(());
810 }
811
812 let result = dispatch(&ids);
813
814 let reported: AHashSet<_> = match &result {
815 Ok(()) => AHashSet::new(),
816 Err(e) => e
817 .downcast_ref::<SubscriptionError>()
818 .map_or(&[][..], accepted)
819 .iter()
820 .copied()
821 .collect(),
822 };
823
824 for id in ids
825 .iter()
826 .filter(|id| result.is_ok() || reported.contains(*id))
827 {
828 subscriptions.remove(id);
829 log_info!("Unsubscribed {id} {label}", color = LogColor::Blue);
830 }
831
832 result
833 }
834
835 fn subscribe_bars(&mut self, cmd: &SubscribeBars) -> anyhow::Result<()> {
841 Self::subscribe_batch(
842 &mut self.subscriptions_bars,
843 &cmd.bar_types,
844 "bars",
845 |partial| &partial.bar_types_accepted,
846 |ids| self.client.subscribe_bars(cmd.with_bar_types(ids)),
847 )
848 }
849
850 fn unsubscribe_bars(&mut self, cmd: &UnsubscribeBars) -> anyhow::Result<()> {
856 Self::unsubscribe_batch(
857 &mut self.subscriptions_bars,
858 &cmd.bar_types,
859 "bars",
860 |partial| &partial.bar_types_accepted,
861 |ids| self.client.unsubscribe_bars(&cmd.with_bar_types(ids)),
862 )
863 }
864
865 fn subscribe_mark_prices(&mut self, cmd: &SubscribeMarkPrices) -> anyhow::Result<()> {
871 Self::subscribe_batch(
872 &mut self.subscriptions_mark_prices,
873 &cmd.instrument_ids,
874 "mark prices",
875 |partial| &partial.instrument_ids_accepted,
876 |ids| {
877 self.client
878 .subscribe_mark_prices(cmd.with_instrument_ids(ids))
879 },
880 )
881 }
882
883 fn unsubscribe_mark_prices(&mut self, cmd: &UnsubscribeMarkPrices) -> anyhow::Result<()> {
889 Self::unsubscribe_batch(
890 &mut self.subscriptions_mark_prices,
891 &cmd.instrument_ids,
892 "mark prices",
893 |partial| &partial.instrument_ids_accepted,
894 |ids| {
895 self.client
896 .unsubscribe_mark_prices(&cmd.with_instrument_ids(ids))
897 },
898 )
899 }
900
901 fn subscribe_index_prices(&mut self, cmd: &SubscribeIndexPrices) -> anyhow::Result<()> {
907 Self::subscribe_batch(
908 &mut self.subscriptions_index_prices,
909 &cmd.instrument_ids,
910 "index prices",
911 |partial| &partial.instrument_ids_accepted,
912 |ids| {
913 self.client
914 .subscribe_index_prices(cmd.with_instrument_ids(ids))
915 },
916 )
917 }
918
919 fn unsubscribe_index_prices(&mut self, cmd: &UnsubscribeIndexPrices) -> anyhow::Result<()> {
925 Self::unsubscribe_batch(
926 &mut self.subscriptions_index_prices,
927 &cmd.instrument_ids,
928 "index prices",
929 |partial| &partial.instrument_ids_accepted,
930 |ids| {
931 self.client
932 .unsubscribe_index_prices(&cmd.with_instrument_ids(ids))
933 },
934 )
935 }
936
937 fn subscribe_funding_rates(&mut self, cmd: &SubscribeFundingRates) -> anyhow::Result<()> {
943 Self::subscribe_batch(
944 &mut self.subscriptions_funding_rates,
945 &cmd.instrument_ids,
946 "funding rates",
947 |partial| &partial.instrument_ids_accepted,
948 |ids| {
949 self.client
950 .subscribe_funding_rates(cmd.with_instrument_ids(ids))
951 },
952 )
953 }
954
955 fn unsubscribe_funding_rates(&mut self, cmd: &UnsubscribeFundingRates) -> anyhow::Result<()> {
961 Self::unsubscribe_batch(
962 &mut self.subscriptions_funding_rates,
963 &cmd.instrument_ids,
964 "funding rates",
965 |partial| &partial.instrument_ids_accepted,
966 |ids| {
967 self.client
968 .unsubscribe_funding_rates(&cmd.with_instrument_ids(ids))
969 },
970 )
971 }
972
973 fn subscribe_instrument_status(
979 &mut self,
980 cmd: &SubscribeInstrumentStatus,
981 ) -> anyhow::Result<()> {
982 Self::subscribe_batch(
983 &mut self.subscriptions_instrument_status,
984 &cmd.instrument_ids,
985 "instrument status",
986 |partial| &partial.instrument_ids_accepted,
987 |ids| {
988 self.client
989 .subscribe_instrument_status(cmd.with_instrument_ids(ids))
990 },
991 )
992 }
993
994 fn unsubscribe_instrument_status(
1000 &mut self,
1001 cmd: &UnsubscribeInstrumentStatus,
1002 ) -> anyhow::Result<()> {
1003 Self::unsubscribe_batch(
1004 &mut self.subscriptions_instrument_status,
1005 &cmd.instrument_ids,
1006 "instrument status",
1007 |partial| &partial.instrument_ids_accepted,
1008 |ids| {
1009 self.client
1010 .unsubscribe_instrument_status(&cmd.with_instrument_ids(ids))
1011 },
1012 )
1013 }
1014
1015 fn subscribe_instrument_close(&mut self, cmd: &SubscribeInstrumentClose) -> anyhow::Result<()> {
1021 Self::subscribe_batch(
1022 &mut self.subscriptions_instrument_close,
1023 &cmd.instrument_ids,
1024 "instrument close",
1025 |partial| &partial.instrument_ids_accepted,
1026 |ids| {
1027 self.client
1028 .subscribe_instrument_close(cmd.with_instrument_ids(ids))
1029 },
1030 )
1031 }
1032
1033 fn unsubscribe_instrument_close(
1039 &mut self,
1040 cmd: &UnsubscribeInstrumentClose,
1041 ) -> anyhow::Result<()> {
1042 Self::unsubscribe_batch(
1043 &mut self.subscriptions_instrument_close,
1044 &cmd.instrument_ids,
1045 "instrument close",
1046 |partial| &partial.instrument_ids_accepted,
1047 |ids| {
1048 self.client
1049 .unsubscribe_instrument_close(&cmd.with_instrument_ids(ids))
1050 },
1051 )
1052 }
1053
1054 fn subscribe_option_greeks(&mut self, cmd: &SubscribeOptionGreeks) -> anyhow::Result<()> {
1060 Self::subscribe_batch(
1061 &mut self.subscriptions_option_greeks,
1062 &cmd.instrument_ids,
1063 "option greeks",
1064 |partial| &partial.instrument_ids_accepted,
1065 |ids| {
1066 self.client
1067 .subscribe_option_greeks(cmd.with_instrument_ids(ids))
1068 },
1069 )
1070 }
1071
1072 fn unsubscribe_option_greeks(&mut self, cmd: &UnsubscribeOptionGreeks) -> anyhow::Result<()> {
1078 Self::unsubscribe_batch(
1079 &mut self.subscriptions_option_greeks,
1080 &cmd.instrument_ids,
1081 "option greeks",
1082 |partial| &partial.instrument_ids_accepted,
1083 |ids| {
1084 self.client
1085 .unsubscribe_option_greeks(&cmd.with_instrument_ids(ids))
1086 },
1087 )
1088 }
1089
1090 pub(crate) fn execute_tracked_subscribe<T>(
1091 client: &mut dyn DataClient,
1092 set: &mut AHashSet<T>,
1093 key: T,
1094 data_type: &str,
1095 subscribe: impl FnOnce(&mut dyn DataClient) -> anyhow::Result<()>,
1096 ) -> anyhow::Result<()>
1097 where
1098 T: Eq + Hash + Display,
1099 {
1100 if set.contains(&key) {
1101 return Ok(());
1102 }
1103
1104 subscribe(client)?;
1105
1106 if data_type.is_empty() {
1107 log_info!("Subscribed {key}", color = LogColor::Blue);
1108 } else {
1109 log_info!("Subscribed {key} {data_type}", color = LogColor::Blue);
1110 }
1111 set.insert(key);
1112 Ok(())
1113 }
1114
1115 pub(crate) fn execute_tracked_unsubscribe<T>(
1116 client: &mut dyn DataClient,
1117 set: &mut AHashSet<T>,
1118 key: &T,
1119 data_type: &str,
1120 unsubscribe: impl FnOnce(&mut dyn DataClient) -> anyhow::Result<()>,
1121 ) -> anyhow::Result<()>
1122 where
1123 T: Eq + Hash + Display,
1124 {
1125 if !set.contains(key) {
1126 return Ok(());
1127 }
1128
1129 unsubscribe(client)?;
1130 set.remove(key);
1131 if data_type.is_empty() {
1132 log_info!("Unsubscribed {key}", color = LogColor::Blue);
1133 } else {
1134 log_info!("Unsubscribed {key} {data_type}", color = LogColor::Blue);
1135 }
1136 Ok(())
1137 }
1138
1139 pub fn request_data(&self, req: RequestCustomData) -> anyhow::Result<()> {
1145 self.client.request_data(req)
1146 }
1147
1148 pub fn request_instrument(&self, req: RequestInstrument) -> anyhow::Result<()> {
1154 self.client.request_instrument(req)
1155 }
1156
1157 pub fn request_instruments(&self, req: RequestInstruments) -> anyhow::Result<()> {
1163 self.client.request_instruments(req)
1164 }
1165
1166 pub fn request_book_snapshot(&self, req: RequestBookSnapshot) -> anyhow::Result<()> {
1172 self.client.request_book_snapshot(req)
1173 }
1174
1175 pub fn request_quotes(&self, req: RequestQuotes) -> anyhow::Result<()> {
1181 self.client.request_quotes(req)
1182 }
1183
1184 pub fn request_trades(&self, req: RequestTrades) -> anyhow::Result<()> {
1190 self.client.request_trades(req)
1191 }
1192
1193 pub fn request_funding_rates(&self, req: RequestFundingRates) -> anyhow::Result<()> {
1199 self.client.request_funding_rates(req)
1200 }
1201
1202 pub fn request_option_chain_reference_price(
1208 &self,
1209 req: RequestOptionChainReferencePrice,
1210 ) -> anyhow::Result<()> {
1211 self.client.request_option_chain_reference_price(req)
1212 }
1213
1214 pub fn request_bars(&self, req: RequestBars) -> anyhow::Result<()> {
1220 self.client.request_bars(req)
1221 }
1222
1223 pub fn request_book_depth(&self, req: RequestBookDepth) -> anyhow::Result<()> {
1229 self.client.request_book_depth(req)
1230 }
1231}