Skip to main content

nautilus_common/messages/data/
mod.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 specific messages such as subscriptions and requests.
17
18use std::{any::Any, sync::Arc};
19
20use nautilus_core::{Params, UUID4, UnixNanos};
21use nautilus_model::{
22    data::BarType,
23    identifiers::{ClientId, Venue},
24};
25use serde::{Deserialize, Serialize};
26
27pub mod request;
28pub mod response;
29pub mod subscribe;
30pub mod unsubscribe;
31
32/// Params key used to flag a book subscription as targeting a parent symbol.
33///
34/// When the boolean value is `true`, the subscription fans out across all
35/// instruments that resolve from the parent components (see
36/// [`InstrumentId::parse_parent_components`]). When absent or `false`, the
37/// subscription is routed to the concrete instrument id only.
38///
39/// [`InstrumentId::parse_parent_components`]: nautilus_model::identifiers::InstrumentId::parse_parent_components
40pub const PARAMS_IS_PARENT: &str = "is_parent";
41
42// Re-exports
43pub use request::{
44    RequestBars, RequestBookDeltas, RequestBookDepth, RequestBookSnapshot, RequestCustomData,
45    RequestForwardPrices, RequestFundingRates, RequestInstrument, RequestInstruments, RequestJoin,
46    RequestQuotes, RequestTrades,
47};
48pub use response::{
49    BarsResponse, BookDeltasResponse, BookDepthResponse, BookResponse, CustomDataResponse,
50    ForwardPricesResponse, FundingRatesResponse, InstrumentResponse, InstrumentsResponse,
51    QuotesResponse, TradesResponse,
52};
53pub use subscribe::{
54    SubscribeBars, SubscribeBookDeltas, SubscribeBookDepth10, SubscribeBookSnapshots,
55    SubscribeCustomData, SubscribeFundingRates, SubscribeIndexPrices, SubscribeInstrument,
56    SubscribeInstrumentClose, SubscribeInstrumentStatus, SubscribeInstruments, SubscribeMarkPrices,
57    SubscribeOptionChain, SubscribeOptionGreeks, SubscribeQuotes, SubscribeTrades,
58};
59pub use unsubscribe::{
60    UnsubscribeBars, UnsubscribeBookDeltas, UnsubscribeBookDepth10, UnsubscribeBookSnapshots,
61    UnsubscribeCustomData, UnsubscribeFundingRates, UnsubscribeIndexPrices, UnsubscribeInstrument,
62    UnsubscribeInstrumentClose, UnsubscribeInstrumentStatus, UnsubscribeInstruments,
63    UnsubscribeMarkPrices, UnsubscribeOptionChain, UnsubscribeOptionGreeks, UnsubscribeQuotes,
64    UnsubscribeTrades,
65};
66
67#[cfg(feature = "defi")]
68use crate::messages::defi::{DefiRequestCommand, DefiSubscribeCommand, DefiUnsubscribeCommand};
69
70#[non_exhaustive]
71#[derive(Clone, Debug, PartialEq)]
72pub enum DataCommand {
73    Request(RequestCommand),
74    Subscribe(SubscribeCommand),
75    Unsubscribe(UnsubscribeCommand),
76    #[cfg(feature = "defi")]
77    DefiRequest(DefiRequestCommand),
78    #[cfg(feature = "defi")]
79    DefiSubscribe(DefiSubscribeCommand),
80    #[cfg(feature = "defi")]
81    DefiUnsubscribe(DefiUnsubscribeCommand),
82}
83
84impl DataCommand {
85    /// Converts the command to a dyn Any trait object for messaging.
86    pub fn as_any(&self) -> &dyn Any {
87        self
88    }
89}
90
91#[derive(Clone, Debug, Serialize, Deserialize)]
92pub enum SubscribeCommand {
93    Data(SubscribeCustomData),
94    Instrument(SubscribeInstrument),
95    Instruments(SubscribeInstruments),
96    BookDeltas(SubscribeBookDeltas),
97    BookDepth10(SubscribeBookDepth10),
98    BookSnapshots(SubscribeBookSnapshots),
99    Quotes(SubscribeQuotes),
100    Trades(SubscribeTrades),
101    Bars(SubscribeBars),
102    MarkPrices(SubscribeMarkPrices),
103    IndexPrices(SubscribeIndexPrices),
104    FundingRates(SubscribeFundingRates),
105    InstrumentStatus(SubscribeInstrumentStatus),
106    InstrumentClose(SubscribeInstrumentClose),
107    OptionGreeks(SubscribeOptionGreeks),
108    OptionChain(SubscribeOptionChain),
109}
110
111impl PartialEq for SubscribeCommand {
112    fn eq(&self, other: &Self) -> bool {
113        self.command_id() == other.command_id()
114    }
115}
116
117impl SubscribeCommand {
118    /// Converts the command to a dyn Any trait object for messaging.
119    pub fn as_any(&self) -> &dyn Any {
120        self
121    }
122
123    pub fn command_id(&self) -> UUID4 {
124        match self {
125            Self::Data(cmd) => cmd.command_id,
126            Self::Instrument(cmd) => cmd.command_id,
127            Self::Instruments(cmd) => cmd.command_id,
128            Self::BookDeltas(cmd) => cmd.command_id,
129            Self::BookDepth10(cmd) => cmd.command_id,
130            Self::BookSnapshots(cmd) => cmd.command_id,
131            Self::Quotes(cmd) => cmd.command_id,
132            Self::Trades(cmd) => cmd.command_id,
133            Self::Bars(cmd) => cmd.command_id,
134            Self::MarkPrices(cmd) => cmd.command_id,
135            Self::IndexPrices(cmd) => cmd.command_id,
136            Self::FundingRates(cmd) => cmd.command_id,
137            Self::InstrumentStatus(cmd) => cmd.command_id,
138            Self::InstrumentClose(cmd) => cmd.command_id,
139            Self::OptionGreeks(cmd) => cmd.command_id,
140            Self::OptionChain(cmd) => cmd.command_id,
141        }
142    }
143
144    pub fn client_id(&self) -> Option<&ClientId> {
145        match self {
146            Self::Data(cmd) => cmd.client_id.as_ref(),
147            Self::Instrument(cmd) => cmd.client_id.as_ref(),
148            Self::Instruments(cmd) => cmd.client_id.as_ref(),
149            Self::BookDeltas(cmd) => cmd.client_id.as_ref(),
150            Self::BookDepth10(cmd) => cmd.client_id.as_ref(),
151            Self::BookSnapshots(cmd) => cmd.client_id.as_ref(),
152            Self::Quotes(cmd) => cmd.client_id.as_ref(),
153            Self::Trades(cmd) => cmd.client_id.as_ref(),
154            Self::MarkPrices(cmd) => cmd.client_id.as_ref(),
155            Self::IndexPrices(cmd) => cmd.client_id.as_ref(),
156            Self::FundingRates(cmd) => cmd.client_id.as_ref(),
157            Self::Bars(cmd) => cmd.client_id.as_ref(),
158            Self::InstrumentStatus(cmd) => cmd.client_id.as_ref(),
159            Self::InstrumentClose(cmd) => cmd.client_id.as_ref(),
160            Self::OptionGreeks(cmd) => cmd.client_id.as_ref(),
161            Self::OptionChain(cmd) => cmd.client_id.as_ref(),
162        }
163    }
164
165    pub fn venue(&self) -> Option<&Venue> {
166        match self {
167            Self::Data(cmd) => cmd.venue.as_ref(),
168            Self::Instrument(cmd) => cmd.venue.as_ref(),
169            Self::Instruments(cmd) => Some(&cmd.venue),
170            Self::BookDeltas(cmd) => cmd.venue.as_ref(),
171            Self::BookDepth10(cmd) => cmd.venue.as_ref(),
172            Self::BookSnapshots(cmd) => cmd.venue.as_ref(),
173            Self::Quotes(cmd) => cmd.venue.as_ref(),
174            Self::Trades(cmd) => cmd.venue.as_ref(),
175            Self::MarkPrices(cmd) => cmd.venue.as_ref(),
176            Self::IndexPrices(cmd) => cmd.venue.as_ref(),
177            Self::FundingRates(cmd) => cmd.venue.as_ref(),
178            Self::Bars(cmd) => cmd.venue.as_ref(),
179            Self::InstrumentStatus(cmd) => cmd.venue.as_ref(),
180            Self::InstrumentClose(cmd) => cmd.venue.as_ref(),
181            Self::OptionGreeks(cmd) => cmd.venue.as_ref(),
182            Self::OptionChain(cmd) => cmd.venue.as_ref(),
183        }
184    }
185
186    pub fn ts_init(&self) -> UnixNanos {
187        match self {
188            Self::Data(cmd) => cmd.ts_init,
189            Self::Instrument(cmd) => cmd.ts_init,
190            Self::Instruments(cmd) => cmd.ts_init,
191            Self::BookDeltas(cmd) => cmd.ts_init,
192            Self::BookDepth10(cmd) => cmd.ts_init,
193            Self::BookSnapshots(cmd) => cmd.ts_init,
194            Self::Quotes(cmd) => cmd.ts_init,
195            Self::Trades(cmd) => cmd.ts_init,
196            Self::MarkPrices(cmd) => cmd.ts_init,
197            Self::IndexPrices(cmd) => cmd.ts_init,
198            Self::FundingRates(cmd) => cmd.ts_init,
199            Self::Bars(cmd) => cmd.ts_init,
200            Self::InstrumentStatus(cmd) => cmd.ts_init,
201            Self::InstrumentClose(cmd) => cmd.ts_init,
202            Self::OptionGreeks(cmd) => cmd.ts_init,
203            Self::OptionChain(cmd) => cmd.ts_init,
204        }
205    }
206
207    pub fn correlation_id(&self) -> Option<UUID4> {
208        match self {
209            Self::Data(cmd) => cmd.correlation_id,
210            Self::Instrument(cmd) => cmd.correlation_id,
211            Self::Instruments(cmd) => cmd.correlation_id,
212            Self::BookDeltas(cmd) => cmd.correlation_id,
213            Self::BookDepth10(cmd) => cmd.correlation_id,
214            Self::BookSnapshots(cmd) => cmd.correlation_id,
215            Self::Quotes(cmd) => cmd.correlation_id,
216            Self::Trades(cmd) => cmd.correlation_id,
217            Self::MarkPrices(cmd) => cmd.correlation_id,
218            Self::IndexPrices(cmd) => cmd.correlation_id,
219            Self::FundingRates(cmd) => cmd.correlation_id,
220            Self::Bars(cmd) => cmd.correlation_id,
221            Self::InstrumentStatus(cmd) => cmd.correlation_id,
222            Self::InstrumentClose(cmd) => cmd.correlation_id,
223            Self::OptionGreeks(cmd) => cmd.correlation_id,
224            Self::OptionChain(_) => None,
225        }
226    }
227
228    pub fn params(&self) -> Option<&Params> {
229        match self {
230            Self::Data(cmd) => cmd.params.as_ref(),
231            Self::Instrument(cmd) => cmd.params.as_ref(),
232            Self::Instruments(cmd) => cmd.params.as_ref(),
233            Self::BookDeltas(cmd) => cmd.params.as_ref(),
234            Self::BookDepth10(cmd) => cmd.params.as_ref(),
235            Self::BookSnapshots(cmd) => cmd.params.as_ref(),
236            Self::Quotes(cmd) => cmd.params.as_ref(),
237            Self::Trades(cmd) => cmd.params.as_ref(),
238            Self::Bars(cmd) => cmd.params.as_ref(),
239            Self::MarkPrices(cmd) => cmd.params.as_ref(),
240            Self::IndexPrices(cmd) => cmd.params.as_ref(),
241            Self::FundingRates(cmd) => cmd.params.as_ref(),
242            Self::InstrumentStatus(cmd) => cmd.params.as_ref(),
243            Self::InstrumentClose(cmd) => cmd.params.as_ref(),
244            Self::OptionGreeks(cmd) => cmd.params.as_ref(),
245            Self::OptionChain(cmd) => cmd.params.as_ref(),
246        }
247    }
248}
249
250#[derive(Clone, Debug, Serialize, Deserialize)]
251pub enum UnsubscribeCommand {
252    Data(UnsubscribeCustomData),
253    Instrument(UnsubscribeInstrument),
254    Instruments(UnsubscribeInstruments),
255    BookDeltas(UnsubscribeBookDeltas),
256    BookDepth10(UnsubscribeBookDepth10),
257    BookSnapshots(UnsubscribeBookSnapshots),
258    Quotes(UnsubscribeQuotes),
259    Trades(UnsubscribeTrades),
260    Bars(UnsubscribeBars),
261    MarkPrices(UnsubscribeMarkPrices),
262    IndexPrices(UnsubscribeIndexPrices),
263    FundingRates(UnsubscribeFundingRates),
264    InstrumentStatus(UnsubscribeInstrumentStatus),
265    InstrumentClose(UnsubscribeInstrumentClose),
266    OptionGreeks(UnsubscribeOptionGreeks),
267    OptionChain(UnsubscribeOptionChain),
268}
269
270impl PartialEq for UnsubscribeCommand {
271    fn eq(&self, other: &Self) -> bool {
272        self.command_id() == other.command_id()
273    }
274}
275
276impl UnsubscribeCommand {
277    /// Converts the command to a dyn Any trait object for messaging.
278    pub fn as_any(&self) -> &dyn Any {
279        self
280    }
281
282    pub fn command_id(&self) -> UUID4 {
283        match self {
284            Self::Data(cmd) => cmd.command_id,
285            Self::Instrument(cmd) => cmd.command_id,
286            Self::Instruments(cmd) => cmd.command_id,
287            Self::BookDeltas(cmd) => cmd.command_id,
288            Self::BookDepth10(cmd) => cmd.command_id,
289            Self::BookSnapshots(cmd) => cmd.command_id,
290            Self::Quotes(cmd) => cmd.command_id,
291            Self::Trades(cmd) => cmd.command_id,
292            Self::Bars(cmd) => cmd.command_id,
293            Self::MarkPrices(cmd) => cmd.command_id,
294            Self::IndexPrices(cmd) => cmd.command_id,
295            Self::FundingRates(cmd) => cmd.command_id,
296            Self::InstrumentStatus(cmd) => cmd.command_id,
297            Self::InstrumentClose(cmd) => cmd.command_id,
298            Self::OptionGreeks(cmd) => cmd.command_id,
299            Self::OptionChain(cmd) => cmd.command_id,
300        }
301    }
302
303    pub fn client_id(&self) -> Option<&ClientId> {
304        match self {
305            Self::Data(cmd) => cmd.client_id.as_ref(),
306            Self::Instrument(cmd) => cmd.client_id.as_ref(),
307            Self::Instruments(cmd) => cmd.client_id.as_ref(),
308            Self::BookDeltas(cmd) => cmd.client_id.as_ref(),
309            Self::BookDepth10(cmd) => cmd.client_id.as_ref(),
310            Self::BookSnapshots(cmd) => cmd.client_id.as_ref(),
311            Self::Quotes(cmd) => cmd.client_id.as_ref(),
312            Self::Trades(cmd) => cmd.client_id.as_ref(),
313            Self::Bars(cmd) => cmd.client_id.as_ref(),
314            Self::MarkPrices(cmd) => cmd.client_id.as_ref(),
315            Self::IndexPrices(cmd) => cmd.client_id.as_ref(),
316            Self::FundingRates(cmd) => cmd.client_id.as_ref(),
317            Self::InstrumentStatus(cmd) => cmd.client_id.as_ref(),
318            Self::InstrumentClose(cmd) => cmd.client_id.as_ref(),
319            Self::OptionGreeks(cmd) => cmd.client_id.as_ref(),
320            Self::OptionChain(cmd) => cmd.client_id.as_ref(),
321        }
322    }
323
324    pub fn venue(&self) -> Option<&Venue> {
325        match self {
326            Self::Data(cmd) => cmd.venue.as_ref(),
327            Self::Instrument(cmd) => cmd.venue.as_ref(),
328            Self::Instruments(cmd) => Some(&cmd.venue),
329            Self::BookDeltas(cmd) => cmd.venue.as_ref(),
330            Self::BookDepth10(cmd) => cmd.venue.as_ref(),
331            Self::BookSnapshots(cmd) => cmd.venue.as_ref(),
332            Self::Quotes(cmd) => cmd.venue.as_ref(),
333            Self::Trades(cmd) => cmd.venue.as_ref(),
334            Self::Bars(cmd) => cmd.venue.as_ref(),
335            Self::MarkPrices(cmd) => cmd.venue.as_ref(),
336            Self::IndexPrices(cmd) => cmd.venue.as_ref(),
337            Self::FundingRates(cmd) => cmd.venue.as_ref(),
338            Self::InstrumentStatus(cmd) => cmd.venue.as_ref(),
339            Self::InstrumentClose(cmd) => cmd.venue.as_ref(),
340            Self::OptionGreeks(cmd) => cmd.venue.as_ref(),
341            Self::OptionChain(cmd) => cmd.venue.as_ref(),
342        }
343    }
344
345    pub fn ts_init(&self) -> UnixNanos {
346        match self {
347            Self::Data(cmd) => cmd.ts_init,
348            Self::Instrument(cmd) => cmd.ts_init,
349            Self::Instruments(cmd) => cmd.ts_init,
350            Self::BookDeltas(cmd) => cmd.ts_init,
351            Self::BookDepth10(cmd) => cmd.ts_init,
352            Self::BookSnapshots(cmd) => cmd.ts_init,
353            Self::Quotes(cmd) => cmd.ts_init,
354            Self::Trades(cmd) => cmd.ts_init,
355            Self::MarkPrices(cmd) => cmd.ts_init,
356            Self::IndexPrices(cmd) => cmd.ts_init,
357            Self::FundingRates(cmd) => cmd.ts_init,
358            Self::Bars(cmd) => cmd.ts_init,
359            Self::InstrumentStatus(cmd) => cmd.ts_init,
360            Self::InstrumentClose(cmd) => cmd.ts_init,
361            Self::OptionGreeks(cmd) => cmd.ts_init,
362            Self::OptionChain(cmd) => cmd.ts_init,
363        }
364    }
365
366    pub fn correlation_id(&self) -> Option<UUID4> {
367        match self {
368            Self::Data(cmd) => cmd.correlation_id,
369            Self::Instrument(cmd) => cmd.correlation_id,
370            Self::Instruments(cmd) => cmd.correlation_id,
371            Self::BookDeltas(cmd) => cmd.correlation_id,
372            Self::BookDepth10(cmd) => cmd.correlation_id,
373            Self::BookSnapshots(cmd) => cmd.correlation_id,
374            Self::Quotes(cmd) => cmd.correlation_id,
375            Self::Trades(cmd) => cmd.correlation_id,
376            Self::MarkPrices(cmd) => cmd.correlation_id,
377            Self::IndexPrices(cmd) => cmd.correlation_id,
378            Self::FundingRates(cmd) => cmd.correlation_id,
379            Self::Bars(cmd) => cmd.correlation_id,
380            Self::InstrumentStatus(cmd) => cmd.correlation_id,
381            Self::InstrumentClose(cmd) => cmd.correlation_id,
382            Self::OptionGreeks(cmd) => cmd.correlation_id,
383            Self::OptionChain(_) => None,
384        }
385    }
386}
387
388#[allow(
389    clippy::ref_option,
390    reason = "callers pass borrowed Option fields directly"
391)]
392fn check_client_id_or_venue(client_id: &Option<ClientId>, venue: &Option<Venue>) {
393    assert!(
394        client_id.is_some() || venue.is_some(),
395        "Both `client_id` and `venue` were None"
396    );
397}
398
399#[derive(Clone, Debug, Serialize, Deserialize)]
400pub enum RequestCommand {
401    Data(RequestCustomData),
402    Instrument(RequestInstrument),
403    Instruments(RequestInstruments),
404    BookSnapshot(RequestBookSnapshot),
405    BookDeltas(RequestBookDeltas),
406    BookDepth(RequestBookDepth),
407    Quotes(RequestQuotes),
408    Trades(RequestTrades),
409    FundingRates(RequestFundingRates),
410    ForwardPrices(RequestForwardPrices),
411    Bars(RequestBars),
412    Join(RequestJoin),
413}
414
415impl PartialEq for RequestCommand {
416    fn eq(&self, other: &Self) -> bool {
417        self.request_id() == other.request_id()
418    }
419}
420
421impl RequestCommand {
422    /// Converts the command to a dyn Any trait object for messaging.
423    pub fn as_any(&self) -> &dyn Any {
424        self
425    }
426
427    pub fn request_id(&self) -> &UUID4 {
428        match self {
429            Self::Data(cmd) => &cmd.request_id,
430            Self::Instrument(cmd) => &cmd.request_id,
431            Self::Instruments(cmd) => &cmd.request_id,
432            Self::BookSnapshot(cmd) => &cmd.request_id,
433            Self::BookDeltas(cmd) => &cmd.request_id,
434            Self::BookDepth(cmd) => &cmd.request_id,
435            Self::Quotes(cmd) => &cmd.request_id,
436            Self::Trades(cmd) => &cmd.request_id,
437            Self::FundingRates(cmd) => &cmd.request_id,
438            Self::ForwardPrices(cmd) => &cmd.request_id,
439            Self::Bars(cmd) => &cmd.request_id,
440            Self::Join(cmd) => &cmd.request_id,
441        }
442    }
443
444    pub fn client_id(&self) -> Option<&ClientId> {
445        match self {
446            Self::Data(cmd) => Some(&cmd.client_id),
447            Self::Instrument(cmd) => cmd.client_id.as_ref(),
448            Self::Instruments(cmd) => cmd.client_id.as_ref(),
449            Self::BookSnapshot(cmd) => cmd.client_id.as_ref(),
450            Self::BookDeltas(cmd) => cmd.client_id.as_ref(),
451            Self::BookDepth(cmd) => cmd.client_id.as_ref(),
452            Self::Quotes(cmd) => cmd.client_id.as_ref(),
453            Self::Trades(cmd) => cmd.client_id.as_ref(),
454            Self::FundingRates(cmd) => cmd.client_id.as_ref(),
455            Self::ForwardPrices(cmd) => cmd.client_id.as_ref(),
456            Self::Bars(cmd) => cmd.client_id.as_ref(),
457            Self::Join(_) => None,
458        }
459    }
460
461    pub fn venue(&self) -> Option<&Venue> {
462        match self {
463            Self::Data(_) => None,
464            Self::Instrument(cmd) => Some(&cmd.instrument_id.venue),
465            Self::Instruments(cmd) => cmd.venue.as_ref(),
466            Self::BookSnapshot(cmd) => Some(&cmd.instrument_id.venue),
467            Self::BookDeltas(cmd) => Some(&cmd.instrument_id.venue),
468            Self::BookDepth(cmd) => Some(&cmd.instrument_id.venue),
469            Self::Quotes(cmd) => Some(&cmd.instrument_id.venue),
470            Self::Trades(cmd) => Some(&cmd.instrument_id.venue),
471            Self::FundingRates(cmd) => Some(&cmd.instrument_id.venue),
472            Self::ForwardPrices(cmd) => Some(&cmd.venue),
473            // TODO: Extract the below somewhere
474            Self::Bars(cmd) => match &cmd.bar_type {
475                BarType::Standard { instrument_id, .. } => Some(&instrument_id.venue),
476                BarType::Composite { instrument_id, .. } => Some(&instrument_id.venue),
477            },
478            Self::Join(_) => None,
479        }
480    }
481
482    pub fn ts_init(&self) -> UnixNanos {
483        match self {
484            Self::Data(cmd) => cmd.ts_init,
485            Self::Instrument(cmd) => cmd.ts_init,
486            Self::Instruments(cmd) => cmd.ts_init,
487            Self::BookSnapshot(cmd) => cmd.ts_init,
488            Self::BookDeltas(cmd) => cmd.ts_init,
489            Self::BookDepth(cmd) => cmd.ts_init,
490            Self::Quotes(cmd) => cmd.ts_init,
491            Self::Trades(cmd) => cmd.ts_init,
492            Self::FundingRates(cmd) => cmd.ts_init,
493            Self::ForwardPrices(cmd) => cmd.ts_init,
494            Self::Bars(cmd) => cmd.ts_init,
495            Self::Join(cmd) => cmd.ts_init,
496        }
497    }
498}
499
500#[derive(Clone, Debug)]
501pub enum DataResponse {
502    Data(CustomDataResponse),
503    Instrument(Box<InstrumentResponse>),
504    Instruments(InstrumentsResponse),
505    Book(BookResponse),
506    BookDeltas(BookDeltasResponse),
507    BookDepth(BookDepthResponse),
508    Quotes(QuotesResponse),
509    Trades(TradesResponse),
510    FundingRates(FundingRatesResponse),
511    ForwardPrices(ForwardPricesResponse),
512    Bars(BarsResponse),
513}
514
515impl DataResponse {
516    /// Converts the command to a dyn Any trait object for messaging.
517    pub fn as_any(&self) -> &dyn Any {
518        self
519    }
520
521    pub fn correlation_id(&self) -> &UUID4 {
522        match self {
523            Self::Data(resp) => &resp.correlation_id,
524            Self::Instrument(resp) => &resp.correlation_id,
525            Self::Instruments(resp) => &resp.correlation_id,
526            Self::Book(resp) => &resp.correlation_id,
527            Self::BookDeltas(resp) => &resp.correlation_id,
528            Self::BookDepth(resp) => &resp.correlation_id,
529            Self::Quotes(resp) => &resp.correlation_id,
530            Self::Trades(resp) => &resp.correlation_id,
531            Self::FundingRates(resp) => &resp.correlation_id,
532            Self::ForwardPrices(resp) => &resp.correlation_id,
533            Self::Bars(resp) => &resp.correlation_id,
534        }
535    }
536
537    /// Returns a short variant name for compact logging.
538    #[must_use]
539    pub fn kind(&self) -> &'static str {
540        match self {
541            Self::Data(_) => "Data",
542            Self::Instrument(_) => "Instrument",
543            Self::Instruments(_) => "Instruments",
544            Self::Book(_) => "Book",
545            Self::BookDeltas(_) => "BookDeltas",
546            Self::BookDepth(_) => "BookDepth",
547            Self::Quotes(_) => "Quotes",
548            Self::Trades(_) => "Trades",
549            Self::FundingRates(_) => "FundingRates",
550            Self::ForwardPrices(_) => "ForwardPrices",
551            Self::Bars(_) => "Bars",
552        }
553    }
554
555    /// Returns the number of records carried by the response, where defined.
556    ///
557    /// Returns `None` for singular or opaque variants (`Data`, `Instrument`, `Book`)
558    /// where a record count is not meaningful.
559    #[must_use]
560    pub fn record_count(&self) -> Option<usize> {
561        match self {
562            Self::Data(_) | Self::Instrument(_) | Self::Book(_) => None,
563            Self::Instruments(resp) => Some(resp.data.len()),
564            Self::BookDeltas(resp) => Some(resp.data.len()),
565            Self::BookDepth(resp) => Some(resp.data.len()),
566            Self::Quotes(resp) => Some(resp.data.len()),
567            Self::Trades(resp) => Some(resp.data.len()),
568            Self::FundingRates(resp) => Some(resp.data.len()),
569            Self::ForwardPrices(resp) => Some(resp.data.len()),
570            Self::Bars(resp) => Some(resp.data.len()),
571        }
572    }
573
574    /// Trims vector payloads to the inclusive `[start, end]` window on `ts_init`.
575    ///
576    /// Applies to variants whose payload elements implement `HasTsInit`
577    /// (`BookDeltas`, `BookDepth`, `Quotes`, `Trades`, `FundingRates`,
578    /// `Bars`, `Instruments`). Other variants are untouched: singular payloads
579    /// (`Instrument`, `Book`),
580    /// `ForwardPrices` (no per-item `ts_init`), and the opaque custom
581    /// `Data` variant.
582    pub fn trim_to_bounds(&mut self) {
583        match self {
584            Self::Quotes(r) => response::trim_data_to_bounds(&mut r.data, r.start, r.end),
585            Self::Trades(r) => response::trim_data_to_bounds(&mut r.data, r.start, r.end),
586            Self::FundingRates(r) => response::trim_data_to_bounds(&mut r.data, r.start, r.end),
587            Self::Bars(r) => response::trim_data_to_bounds(&mut r.data, r.start, r.end),
588            Self::Instruments(r) => response::trim_data_to_bounds(&mut r.data, r.start, r.end),
589            Self::BookDeltas(r) => response::trim_data_to_bounds(&mut r.data, r.start, r.end),
590            Self::BookDepth(r) => response::trim_data_to_bounds(&mut r.data, r.start, r.end),
591            Self::Data(_) | Self::Instrument(_) | Self::Book(_) | Self::ForwardPrices(_) => {}
592        }
593    }
594}
595
596pub type Payload = Arc<dyn Any + Send + Sync>;
597
598/// Returns `true` when `params` carries the [`PARAMS_IS_PARENT`] flag set to `true`.
599///
600/// Absent or non-boolean values resolve to `false`, keeping the default subscription
601/// path concrete (exact topic).
602#[must_use]
603pub fn is_parent_subscription(params: Option<&Params>) -> bool {
604    params
605        .and_then(|p| p.get_bool(PARAMS_IS_PARENT))
606        .unwrap_or(false)
607}