nautilus_common/clients/data.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Data client trait definition.
17
18use async_trait::async_trait;
19use nautilus_model::{
20 data::BarType,
21 identifiers::{ClientId, InstrumentId, Venue},
22};
23
24use super::log_not_implemented;
25use crate::messages::data::{
26 RequestBars, RequestBookDeltas, RequestBookDepth, RequestBookSnapshot, RequestCustomData,
27 RequestFundingRates, RequestInstrument, RequestInstruments, RequestOptionChainReferencePrice,
28 RequestQuotes, RequestTrades, SubscribeBars, SubscribeBookDeltas, SubscribeBookDepth,
29 SubscribeCustomData, SubscribeFundingRates, SubscribeIndexPrices, SubscribeInstrument,
30 SubscribeInstrumentClose, SubscribeInstrumentStatus, SubscribeInstruments, SubscribeMarkPrices,
31 SubscribeOptionGreeks, SubscribeQuotes, SubscribeTrades, SubscriptionError, UnsubscribeBars,
32 UnsubscribeBookDeltas, UnsubscribeBookDepth, UnsubscribeCustomData, UnsubscribeFundingRates,
33 UnsubscribeIndexPrices, UnsubscribeInstrument, UnsubscribeInstrumentClose,
34 UnsubscribeInstrumentStatus, UnsubscribeInstruments, UnsubscribeMarkPrices,
35 UnsubscribeOptionGreeks, UnsubscribeQuotes, UnsubscribeTrades,
36};
37#[cfg(feature = "defi")]
38use crate::messages::defi::{
39 RequestPoolSnapshot, SubscribeBlocks, SubscribePool, SubscribePoolFeeCollects,
40 SubscribePoolFlashEvents, SubscribePoolLiquidityUpdates, SubscribePoolSwaps, UnsubscribeBlocks,
41 UnsubscribePool, UnsubscribePoolFeeCollects, UnsubscribePoolFlashEvents,
42 UnsubscribePoolLiquidityUpdates, UnsubscribePoolSwaps,
43};
44
45/// Defines the interface for a data client, managing connections, subscriptions, and requests.
46///
47/// Instrument and bar commands carry ordered member lists in both directions. A hook limited to
48/// one member rejects a larger list before effects. Supported lists execute best effort without
49/// rolling back accepted members. Validate command-wide settings before effects.
50///
51/// `Ok(())` accepts every member. [`SubscriptionError`] lists the accepted members exactly.
52/// Acceptance is synchronous; other errors accept none. Bar outcomes use full bar types, and
53/// unsubscribe acceptance means successful release. Venue acknowledgements remain asynchronous.
54/// Adapters can batch wire requests or iterate locally.
55///
56/// # Thread Safety
57///
58/// Client instances are not intended to be sent across threads. The `?Send` bound
59/// allows implementations to hold non-Send state for any Python interop.
60#[async_trait(?Send)]
61pub trait DataClient {
62 /// Returns the unique identifier for this data client.
63 fn client_id(&self) -> ClientId;
64
65 /// Returns the optional venue this client is associated with.
66 fn venue(&self) -> Option<Venue>;
67
68 /// Starts the data client.
69 ///
70 /// # Errors
71 ///
72 /// Returns an error if the operation fails.
73 fn start(&mut self) -> anyhow::Result<()>;
74
75 /// Stops the data client.
76 ///
77 /// # Errors
78 ///
79 /// Returns an error if the operation fails.
80 fn stop(&mut self) -> anyhow::Result<()>;
81
82 /// Resets the data client to its initial state.
83 ///
84 /// # Errors
85 ///
86 /// Returns an error if the operation fails.
87 fn reset(&mut self) -> anyhow::Result<()>;
88
89 /// Disposes of client resources and cleans up.
90 ///
91 /// # Errors
92 ///
93 /// Returns an error if the operation fails.
94 fn dispose(&mut self) -> anyhow::Result<()>;
95
96 /// Returns `true` if the client is currently connected.
97 fn is_connected(&self) -> bool;
98
99 /// Returns `true` if the client is currently disconnected.
100 fn is_disconnected(&self) -> bool;
101
102 /// Connects the client to the data provider.
103 ///
104 /// For live clients, this triggers the actual connection to external APIs.
105 /// For backtest clients, this is a no-op.
106 ///
107 /// # Errors
108 ///
109 /// Returns an error if the connection fails.
110 async fn connect(&mut self) -> anyhow::Result<()> {
111 Ok(())
112 }
113
114 /// Disconnects the client from the data provider.
115 ///
116 /// For live clients, this closes connections to external APIs.
117 /// For backtest clients, this is a no-op.
118 ///
119 /// # Errors
120 ///
121 /// Returns an error if the disconnection fails.
122 async fn disconnect(&mut self) -> anyhow::Result<()> {
123 Ok(())
124 }
125
126 /// Subscribes to custom data types according to the command.
127 ///
128 /// # Errors
129 ///
130 /// Returns an error if the subscribe operation fails.
131 fn subscribe(&mut self, cmd: SubscribeCustomData) -> anyhow::Result<()> {
132 log_not_implemented(self.client_id(), &cmd);
133 Ok(())
134 }
135
136 /// Subscribes to instruments list for the specified venue.
137 ///
138 /// # Errors
139 ///
140 /// Returns an error if the subscribe operation fails.
141 fn subscribe_instruments(&mut self, cmd: SubscribeInstruments) -> anyhow::Result<()> {
142 log_not_implemented(self.client_id(), &cmd);
143 Ok(())
144 }
145
146 /// Subscribes to definitions for the specified instruments.
147 ///
148 /// # Errors
149 ///
150 /// Returns an error if the subscribe operation fails.
151 fn subscribe_instrument(&mut self, cmd: SubscribeInstrument) -> anyhow::Result<()> {
152 log_not_implemented(self.client_id(), &cmd);
153 Ok(())
154 }
155
156 /// Subscribes to order book delta updates for the specified instruments.
157 ///
158 /// # Errors
159 ///
160 /// Returns an error if the subscribe operation fails.
161 fn subscribe_book_deltas(&mut self, cmd: SubscribeBookDeltas) -> anyhow::Result<()> {
162 log_not_implemented(self.client_id(), &cmd);
163 Ok(())
164 }
165
166 /// Subscribes to order book depth snapshot updates for the specified instruments.
167 ///
168 /// # Errors
169 ///
170 /// Returns an error if the subscribe operation fails.
171 fn subscribe_book_depth(&mut self, cmd: SubscribeBookDepth) -> anyhow::Result<()> {
172 log_not_implemented(self.client_id(), &cmd);
173 Ok(())
174 }
175
176 /// Subscribes to quote updates for the specified instruments.
177 ///
178 /// If a failed dispatch accepted any IDs, the client returns
179 /// [`SubscriptionError`](crate::messages::data::SubscriptionError) with exactly those IDs.
180 /// Accepted IDs must belong to the command. Acceptance records client dispatch;
181 /// venue acknowledgements arrive asynchronously.
182 ///
183 /// # Errors
184 ///
185 /// Returns an error if the subscribe operation fails.
186 fn subscribe_quotes(&mut self, cmd: SubscribeQuotes) -> anyhow::Result<()> {
187 log_not_implemented(self.client_id(), &cmd);
188 Ok(())
189 }
190
191 /// Subscribes to trade updates for the specified instruments.
192 ///
193 /// If a failed dispatch accepted any IDs, the client returns
194 /// [`SubscriptionError`](crate::messages::data::SubscriptionError) with exactly those IDs.
195 /// Accepted IDs must belong to the command. Acceptance records client dispatch;
196 /// venue acknowledgements arrive asynchronously.
197 ///
198 /// # Errors
199 ///
200 /// Returns an error if the subscribe operation fails.
201 fn subscribe_trades(&mut self, cmd: SubscribeTrades) -> anyhow::Result<()> {
202 log_not_implemented(self.client_id(), &cmd);
203 Ok(())
204 }
205
206 /// Subscribes to mark price updates for the specified instruments.
207 ///
208 /// # Errors
209 ///
210 /// Returns an error if the subscribe operation fails.
211 fn subscribe_mark_prices(&mut self, cmd: SubscribeMarkPrices) -> anyhow::Result<()> {
212 log_not_implemented(self.client_id(), &cmd);
213 Ok(())
214 }
215
216 /// Subscribes to index price updates for the specified instruments.
217 ///
218 /// # Errors
219 ///
220 /// Returns an error if the subscribe operation fails.
221 fn subscribe_index_prices(&mut self, cmd: SubscribeIndexPrices) -> anyhow::Result<()> {
222 log_not_implemented(self.client_id(), &cmd);
223 Ok(())
224 }
225
226 /// Subscribes to funding rate updates for the specified instruments.
227 ///
228 /// # Errors
229 ///
230 /// Returns an error if the subscribe operation fails.
231 fn subscribe_funding_rates(&mut self, cmd: SubscribeFundingRates) -> anyhow::Result<()> {
232 log_not_implemented(self.client_id(), &cmd);
233 Ok(())
234 }
235
236 /// Subscribes to bar updates of the specified full bar types.
237 ///
238 /// # Errors
239 ///
240 /// Returns an error if the subscribe operation fails.
241 fn subscribe_bars(&mut self, cmd: SubscribeBars) -> anyhow::Result<()> {
242 log_not_implemented(self.client_id(), &cmd);
243 Ok(())
244 }
245
246 /// Subscribes to status updates for the specified instruments.
247 ///
248 /// # Errors
249 ///
250 /// Returns an error if the subscribe operation fails.
251 fn subscribe_instrument_status(
252 &mut self,
253 cmd: SubscribeInstrumentStatus,
254 ) -> anyhow::Result<()> {
255 log_not_implemented(self.client_id(), &cmd);
256 Ok(())
257 }
258
259 /// Subscribes to instrument close events for the specified instruments.
260 ///
261 /// # Errors
262 ///
263 /// Returns an error if the subscription operation fails.
264 fn subscribe_instrument_close(&mut self, cmd: SubscribeInstrumentClose) -> anyhow::Result<()> {
265 log_not_implemented(self.client_id(), &cmd);
266 Ok(())
267 }
268
269 /// Subscribes to exchange-provided option greeks for the specified instruments.
270 ///
271 /// # Errors
272 ///
273 /// Returns an error if the subscription operation fails.
274 fn subscribe_option_greeks(&mut self, cmd: SubscribeOptionGreeks) -> anyhow::Result<()> {
275 log_not_implemented(self.client_id(), &cmd);
276 Ok(())
277 }
278
279 #[cfg(feature = "defi")]
280 /// Subscribes to blocks for a specified blockchain.
281 ///
282 /// # Errors
283 ///
284 /// Returns an error if the subscription operation fails.
285 fn subscribe_blocks(&mut self, cmd: SubscribeBlocks) -> anyhow::Result<()> {
286 log_not_implemented(self.client_id(), &cmd);
287 Ok(())
288 }
289
290 #[cfg(feature = "defi")]
291 /// Subscribes to pool definition updates for a specified AMM pool.
292 ///
293 /// # Errors
294 ///
295 /// Returns an error if the subscription operation fails.
296 fn subscribe_pool(&mut self, cmd: SubscribePool) -> anyhow::Result<()> {
297 log_not_implemented(self.client_id(), &cmd);
298 Ok(())
299 }
300
301 #[cfg(feature = "defi")]
302 /// Subscribes to pool swaps for a specified AMM pool.
303 ///
304 /// # Errors
305 ///
306 /// Returns an error if the subscription operation fails.
307 fn subscribe_pool_swaps(&mut self, cmd: SubscribePoolSwaps) -> anyhow::Result<()> {
308 log_not_implemented(self.client_id(), &cmd);
309 Ok(())
310 }
311
312 #[cfg(feature = "defi")]
313 /// Subscribes to pool liquidity updates for a specified AMM pool.
314 ///
315 /// # Errors
316 ///
317 /// Returns an error if the subscription operation fails.
318 fn subscribe_pool_liquidity_updates(
319 &mut self,
320 cmd: SubscribePoolLiquidityUpdates,
321 ) -> anyhow::Result<()> {
322 log_not_implemented(self.client_id(), &cmd);
323 Ok(())
324 }
325
326 #[cfg(feature = "defi")]
327 /// Subscribes to pool fee collects for a specified AMM pool.
328 ///
329 /// # Errors
330 ///
331 /// Returns an error if the subscription operation fails.
332 fn subscribe_pool_fee_collects(&mut self, cmd: SubscribePoolFeeCollects) -> anyhow::Result<()> {
333 log_not_implemented(self.client_id(), &cmd);
334 Ok(())
335 }
336
337 #[cfg(feature = "defi")]
338 /// Subscribes to pool flash loan events for a specified AMM pool.
339 ///
340 /// # Errors
341 ///
342 /// Returns an error if the subscription operation fails.
343 fn subscribe_pool_flash_events(&mut self, cmd: SubscribePoolFlashEvents) -> anyhow::Result<()> {
344 log_not_implemented(self.client_id(), &cmd);
345 Ok(())
346 }
347
348 /// Unsubscribes from custom data types according to the command.
349 ///
350 /// # Errors
351 ///
352 /// Returns an error if the unsubscribe operation fails.
353 fn unsubscribe(&mut self, cmd: &UnsubscribeCustomData) -> anyhow::Result<()> {
354 log_not_implemented(self.client_id(), cmd);
355 Ok(())
356 }
357
358 /// Unsubscribes from instruments list for the specified venue.
359 ///
360 /// # Errors
361 ///
362 /// Returns an error if the unsubscribe operation fails.
363 fn unsubscribe_instruments(&mut self, cmd: &UnsubscribeInstruments) -> anyhow::Result<()> {
364 log_not_implemented(self.client_id(), cmd);
365 Ok(())
366 }
367
368 /// Unsubscribes from data for the specified instruments.
369 ///
370 /// # Errors
371 ///
372 /// Returns an error if the unsubscribe operation fails.
373 fn unsubscribe_instrument(&mut self, cmd: &UnsubscribeInstrument) -> anyhow::Result<()> {
374 log_not_implemented(self.client_id(), cmd);
375 Ok(())
376 }
377
378 /// Unsubscribes from order book delta updates for the specified instruments.
379 ///
380 /// # Errors
381 ///
382 /// Returns an error if the unsubscribe operation fails.
383 fn unsubscribe_book_deltas(&mut self, cmd: &UnsubscribeBookDeltas) -> anyhow::Result<()> {
384 log_not_implemented(self.client_id(), cmd);
385 Ok(())
386 }
387
388 /// Unsubscribes from order book depth snapshot updates for the specified instruments.
389 ///
390 /// # Errors
391 ///
392 /// Returns an error if the unsubscribe operation fails.
393 fn unsubscribe_book_depth(&mut self, cmd: &UnsubscribeBookDepth) -> anyhow::Result<()> {
394 log_not_implemented(self.client_id(), cmd);
395 Ok(())
396 }
397
398 /// Unsubscribes from quote updates for the specified instruments.
399 ///
400 /// # Errors
401 ///
402 /// Returns an error if the unsubscribe operation fails.
403 fn unsubscribe_quotes(&mut self, cmd: &UnsubscribeQuotes) -> anyhow::Result<()> {
404 log_not_implemented(self.client_id(), cmd);
405 Ok(())
406 }
407
408 /// Unsubscribes from trade updates for the specified instruments.
409 ///
410 /// # Errors
411 ///
412 /// Returns an error if the unsubscribe operation fails.
413 fn unsubscribe_trades(&mut self, cmd: &UnsubscribeTrades) -> anyhow::Result<()> {
414 log_not_implemented(self.client_id(), cmd);
415 Ok(())
416 }
417
418 /// Unsubscribes from mark price updates for the specified instruments.
419 ///
420 /// # Errors
421 ///
422 /// Returns an error if the unsubscribe operation fails.
423 fn unsubscribe_mark_prices(&mut self, cmd: &UnsubscribeMarkPrices) -> anyhow::Result<()> {
424 log_not_implemented(self.client_id(), cmd);
425 Ok(())
426 }
427
428 /// Unsubscribes from index price updates for the specified instruments.
429 ///
430 /// # Errors
431 ///
432 /// Returns an error if the unsubscribe operation fails.
433 fn unsubscribe_index_prices(&mut self, cmd: &UnsubscribeIndexPrices) -> anyhow::Result<()> {
434 log_not_implemented(self.client_id(), cmd);
435 Ok(())
436 }
437
438 /// Unsubscribes from funding rate updates for the specified instruments.
439 ///
440 /// # Errors
441 ///
442 /// Returns an error if the unsubscribe operation fails.
443 fn unsubscribe_funding_rates(&mut self, cmd: &UnsubscribeFundingRates) -> anyhow::Result<()> {
444 log_not_implemented(self.client_id(), cmd);
445 Ok(())
446 }
447
448 /// Unsubscribes from bar updates of the specified full bar types.
449 ///
450 /// # Errors
451 ///
452 /// Returns an error if the unsubscribe operation fails.
453 fn unsubscribe_bars(&mut self, cmd: &UnsubscribeBars) -> anyhow::Result<()> {
454 log_not_implemented(self.client_id(), cmd);
455 Ok(())
456 }
457
458 /// Unsubscribes from instrument status updates for the specified instruments.
459 ///
460 /// # Errors
461 ///
462 /// Returns an error if the unsubscribe operation fails.
463 fn unsubscribe_instrument_status(
464 &mut self,
465 cmd: &UnsubscribeInstrumentStatus,
466 ) -> anyhow::Result<()> {
467 log_not_implemented(self.client_id(), cmd);
468 Ok(())
469 }
470
471 /// Unsubscribes from instrument close events for the specified instruments.
472 ///
473 /// # Errors
474 ///
475 /// Returns an error if the unsubscribe operation fails.
476 fn unsubscribe_instrument_close(
477 &mut self,
478 cmd: &UnsubscribeInstrumentClose,
479 ) -> anyhow::Result<()> {
480 log_not_implemented(self.client_id(), cmd);
481 Ok(())
482 }
483
484 /// Unsubscribes from exchange-provided option greeks for the specified instruments.
485 ///
486 /// # Errors
487 ///
488 /// Returns an error if the unsubscribe operation fails.
489 fn unsubscribe_option_greeks(&mut self, cmd: &UnsubscribeOptionGreeks) -> anyhow::Result<()> {
490 log_not_implemented(self.client_id(), cmd);
491 Ok(())
492 }
493
494 #[cfg(feature = "defi")]
495 /// Unsubscribes from blocks for a specified blockchain.
496 ///
497 /// # Errors
498 ///
499 /// Returns an error if the subscription operation fails.
500 fn unsubscribe_blocks(&mut self, cmd: &UnsubscribeBlocks) -> anyhow::Result<()> {
501 log_not_implemented(self.client_id(), cmd);
502 Ok(())
503 }
504
505 #[cfg(feature = "defi")]
506 /// Unsubscribes from pool definition updates for a specified AMM pool.
507 ///
508 /// # Errors
509 ///
510 /// Returns an error if the subscription operation fails.
511 fn unsubscribe_pool(&mut self, cmd: &UnsubscribePool) -> anyhow::Result<()> {
512 log_not_implemented(self.client_id(), cmd);
513 Ok(())
514 }
515
516 #[cfg(feature = "defi")]
517 /// Unsubscribes from swaps for a specified AMM pool.
518 ///
519 /// # Errors
520 ///
521 /// Returns an error if the subscription operation fails.
522 fn unsubscribe_pool_swaps(&mut self, cmd: &UnsubscribePoolSwaps) -> anyhow::Result<()> {
523 log_not_implemented(self.client_id(), cmd);
524 Ok(())
525 }
526
527 #[cfg(feature = "defi")]
528 /// Unsubscribes from pool liquidity updates for a specified AMM pool.
529 ///
530 /// # Errors
531 ///
532 /// Returns an error if the subscription operation fails.
533 fn unsubscribe_pool_liquidity_updates(
534 &mut self,
535 cmd: &UnsubscribePoolLiquidityUpdates,
536 ) -> anyhow::Result<()> {
537 log_not_implemented(self.client_id(), cmd);
538 Ok(())
539 }
540
541 #[cfg(feature = "defi")]
542 /// Unsubscribes from pool fee collects for a specified AMM pool.
543 ///
544 /// # Errors
545 ///
546 /// Returns an error if the subscription operation fails.
547 fn unsubscribe_pool_fee_collects(
548 &mut self,
549 cmd: &UnsubscribePoolFeeCollects,
550 ) -> anyhow::Result<()> {
551 log_not_implemented(self.client_id(), cmd);
552 Ok(())
553 }
554
555 #[cfg(feature = "defi")]
556 /// Unsubscribes from pool flash loan events for a specified AMM pool.
557 ///
558 /// # Errors
559 ///
560 /// Returns an error if the subscription operation fails.
561 fn unsubscribe_pool_flash_events(
562 &mut self,
563 cmd: &UnsubscribePoolFlashEvents,
564 ) -> anyhow::Result<()> {
565 log_not_implemented(self.client_id(), cmd);
566 Ok(())
567 }
568
569 /// Sends a custom data request to the provider.
570 ///
571 /// # Errors
572 ///
573 /// Returns an error if the data request fails.
574 fn request_data(&self, _request: RequestCustomData) -> anyhow::Result<()> {
575 anyhow::bail!(
576 "Data client {} does not support RequestCustomData",
577 self.client_id()
578 )
579 }
580
581 /// Requests a list of instruments from the provider for a given venue.
582 ///
583 /// # Errors
584 ///
585 /// Returns an error if the instruments request fails.
586 fn request_instruments(&self, _request: RequestInstruments) -> anyhow::Result<()> {
587 anyhow::bail!(
588 "Data client {} does not support RequestInstruments",
589 self.client_id()
590 )
591 }
592
593 /// Requests detailed data for a single instrument.
594 ///
595 /// # Errors
596 ///
597 /// Returns an error if the instrument request fails.
598 fn request_instrument(&self, _request: RequestInstrument) -> anyhow::Result<()> {
599 anyhow::bail!(
600 "Data client {} does not support RequestInstrument",
601 self.client_id()
602 )
603 }
604
605 /// Requests a snapshot of the order book for a specified instrument.
606 ///
607 /// # Errors
608 ///
609 /// Returns an error if the book snapshot request fails.
610 fn request_book_snapshot(&self, _request: RequestBookSnapshot) -> anyhow::Result<()> {
611 anyhow::bail!(
612 "Data client {} does not support RequestBookSnapshot",
613 self.client_id()
614 )
615 }
616
617 /// Requests historical or streaming quote data for a specified instrument.
618 ///
619 /// # Errors
620 ///
621 /// Returns an error if the quotes request fails.
622 fn request_quotes(&self, _request: RequestQuotes) -> anyhow::Result<()> {
623 anyhow::bail!(
624 "Data client {} does not support RequestQuotes",
625 self.client_id()
626 )
627 }
628
629 /// Requests historical or streaming trade data for a specified instrument.
630 ///
631 /// # Errors
632 ///
633 /// Returns an error if the trades request fails.
634 fn request_trades(&self, _request: RequestTrades) -> anyhow::Result<()> {
635 anyhow::bail!(
636 "Data client {} does not support RequestTrades",
637 self.client_id()
638 )
639 }
640
641 /// Requests historical or streaming funding rate data for a specified instrument.
642 ///
643 /// # Errors
644 ///
645 /// Returns an error if the trades request fails.
646 fn request_funding_rates(&self, _request: RequestFundingRates) -> anyhow::Result<()> {
647 anyhow::bail!(
648 "Data client {} does not support RequestFundingRates",
649 self.client_id()
650 )
651 }
652
653 /// Requests a reference price for an option-chain bootstrap.
654 ///
655 /// # Errors
656 ///
657 /// Returns an error if the option-chain reference price request fails.
658 fn request_option_chain_reference_price(
659 &self,
660 request: RequestOptionChainReferencePrice,
661 ) -> anyhow::Result<()> {
662 log_not_implemented(self.client_id(), &request);
663 anyhow::bail!("option-chain reference price requests are not supported")
664 }
665
666 /// Requests historical or streaming bar data for a specified instrument and bar type.
667 ///
668 /// # Errors
669 ///
670 /// Returns an error if the bars request fails.
671 fn request_bars(&self, _request: RequestBars) -> anyhow::Result<()> {
672 anyhow::bail!(
673 "Data client {} does not support RequestBars",
674 self.client_id()
675 )
676 }
677
678 /// Requests historical order book depth data for a specified instrument.
679 ///
680 /// # Errors
681 ///
682 /// Returns an error if the order book depths request fails.
683 fn request_book_depth(&self, _request: RequestBookDepth) -> anyhow::Result<()> {
684 anyhow::bail!(
685 "Data client {} does not support RequestBookDepth",
686 self.client_id()
687 )
688 }
689
690 /// Requests historical order book delta data for a specified instrument.
691 ///
692 /// # Errors
693 ///
694 /// Returns an error if the order book deltas request fails.
695 fn request_book_deltas(&self, _request: RequestBookDeltas) -> anyhow::Result<()> {
696 anyhow::bail!(
697 "Data client {} does not support RequestBookDeltas",
698 self.client_id()
699 )
700 }
701
702 #[cfg(feature = "defi")]
703 /// Requests a snapshot of a specific AMM pool.
704 ///
705 /// # Errors
706 ///
707 /// Returns an error if the pool snapshot request fails.
708 fn request_pool_snapshot(&self, request: RequestPoolSnapshot) -> anyhow::Result<()> {
709 log_not_implemented(self.client_id(), &request);
710 Ok(())
711 }
712}
713
714/// Executes each instrument operation in order and reports synchronous acceptance.
715///
716/// Validate command-wide settings before calling this function. A member failure does not stop
717/// later members; the returned error preserves the first failure and lists only accepted members.
718///
719/// # Errors
720///
721/// Returns an error if any member fails, with a [`SubscriptionError`] listing accepted members.
722pub fn execute_instrument_subscriptions(
723 members: &[InstrumentId],
724 execute: impl FnMut(InstrumentId) -> anyhow::Result<()>,
725) -> anyhow::Result<()> {
726 execute_members(members, execute)
727 .map_err(|(accepted, source)| SubscriptionError::instruments(accepted, source).into())
728}
729
730/// Executes each full bar type operation in order and reports synchronous acceptance.
731///
732/// Validate command-wide settings before calling this function. A member failure does not stop
733/// later members; acceptance uses full bar types, including types on the same instrument.
734///
735/// # Errors
736///
737/// Returns an error if any member fails, with a [`SubscriptionError`] listing accepted members.
738pub fn execute_bar_subscriptions(
739 members: &[BarType],
740 execute: impl FnMut(BarType) -> anyhow::Result<()>,
741) -> anyhow::Result<()> {
742 execute_members(members, execute)
743 .map_err(|(accepted, source)| SubscriptionError::bars(accepted, source).into())
744}
745
746fn execute_members<T: Copy>(
747 members: &[T],
748 mut execute: impl FnMut(T) -> anyhow::Result<()>,
749) -> Result<(), (Vec<T>, anyhow::Error)> {
750 let mut accepted = Vec::new();
751 let mut failure = None;
752
753 for &member in members {
754 match execute(member) {
755 Ok(()) => accepted.push(member),
756 Err(e) if failure.is_none() => failure = Some(e),
757 Err(_) => {}
758 }
759 }
760
761 match failure {
762 Some(e) => Err((accepted, e)),
763 None => Ok(()),
764 }
765}