Skip to main content

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}