Skip to main content

nautilus_common/messages/data/
subscribe.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
16use std::num::NonZeroUsize;
17
18use nautilus_core::{Params, UUID4, UnixNanos};
19use nautilus_model::{
20    data::{BarType, DataType, option_chain::StrikeRange},
21    enums::BookType,
22    identifiers::{ClientId, InstrumentId, OptionSeriesId, Venue},
23};
24use serde::{Deserialize, Serialize};
25
26use super::{
27    IntoBarTypes, IntoInstrumentIds, check_client_id_or_venue,
28    identifiers::{
29        deserialize_bar_types, deserialize_instrument_ids, single_bar_type, single_instrument_id,
30    },
31};
32
33#[derive(Clone, Debug, Serialize, Deserialize)]
34#[cfg_attr(
35    feature = "python",
36    pyo3::pyclass(module = "nautilus_trader.live", frozen)
37)]
38#[cfg_attr(
39    feature = "python",
40    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
41)]
42pub struct SubscribeCustomData {
43    pub client_id: Option<ClientId>,
44    pub venue: Option<Venue>,
45    pub data_type: DataType,
46    pub command_id: UUID4,
47    pub ts_init: UnixNanos,
48    pub correlation_id: Option<UUID4>,
49    pub params: Option<Params>,
50}
51
52impl SubscribeCustomData {
53    /// Creates a new [`SubscribeCustomData`] instance.
54    pub fn new(
55        client_id: Option<ClientId>,
56        venue: Option<Venue>,
57        data_type: DataType,
58        command_id: UUID4,
59        ts_init: UnixNanos,
60        correlation_id: Option<UUID4>,
61        params: Option<Params>,
62    ) -> Self {
63        check_client_id_or_venue(&client_id, &venue);
64        Self {
65            client_id,
66            venue,
67            data_type,
68            command_id,
69            ts_init,
70            correlation_id,
71            params,
72        }
73    }
74}
75
76#[derive(Clone, Debug, Serialize, Deserialize)]
77#[cfg_attr(
78    feature = "python",
79    pyo3::pyclass(module = "nautilus_trader.live", frozen)
80)]
81#[cfg_attr(
82    feature = "python",
83    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
84)]
85pub struct SubscribeInstrument {
86    #[serde(deserialize_with = "deserialize_instrument_ids")]
87    pub instrument_ids: Vec<InstrumentId>,
88    pub client_id: Option<ClientId>,
89    pub venue: Option<Venue>,
90    pub command_id: UUID4,
91    pub ts_init: UnixNanos,
92    pub correlation_id: Option<UUID4>,
93    pub params: Option<Params>,
94}
95
96impl SubscribeInstrument {
97    /// Creates a new [`SubscribeInstrument`] instance.
98    ///
99    /// Duplicate IDs are removed in first-occurrence order.
100    ///
101    /// # Panics
102    ///
103    /// Panics if:
104    /// - The identifier list is empty.
105    /// - Both client and venue are absent.
106    pub fn new(
107        instrument_ids: impl IntoInstrumentIds,
108        client_id: Option<ClientId>,
109        venue: Option<Venue>,
110        command_id: UUID4,
111        ts_init: UnixNanos,
112        correlation_id: Option<UUID4>,
113        params: Option<Params>,
114    ) -> Self {
115        let instrument_ids = instrument_ids
116            .into_instrument_ids()
117            .expect("invalid instrument identifier list");
118        check_client_id_or_venue(&client_id, &venue);
119        Self {
120            instrument_ids,
121            client_id,
122            venue,
123            command_id,
124            ts_init,
125            correlation_id,
126            params,
127        }
128    }
129
130    /// Creates a command for the supplied IDs with the same routing and identity.
131    ///
132    /// # Panics
133    ///
134    /// Panics if the identifier list is empty.
135    #[must_use]
136    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
137        let instrument_ids = instrument_ids
138            .into_instrument_ids()
139            .expect("invalid instrument identifier list");
140
141        Self {
142            instrument_ids,
143            client_id: self.client_id,
144            venue: self.venue,
145            command_id: self.command_id,
146            ts_init: self.ts_init,
147            correlation_id: self.correlation_id,
148            params: self.params.clone(),
149        }
150    }
151
152    /// Returns the instrument ID for a single-instrument adapter path.
153    ///
154    /// # Errors
155    ///
156    /// Returns an error if the command does not contain exactly one instrument.
157    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
158        single_instrument_id(&self.instrument_ids, "subscription")
159    }
160}
161
162#[derive(Clone, Debug, Serialize, Deserialize)]
163#[cfg_attr(
164    feature = "python",
165    pyo3::pyclass(module = "nautilus_trader.live", frozen)
166)]
167#[cfg_attr(
168    feature = "python",
169    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
170)]
171pub struct SubscribeInstruments {
172    pub client_id: Option<ClientId>,
173    pub venue: Venue,
174    pub command_id: UUID4,
175    pub ts_init: UnixNanos,
176    pub correlation_id: Option<UUID4>,
177    pub params: Option<Params>,
178}
179
180impl SubscribeInstruments {
181    /// Creates a new [`SubscribeInstruments`] instance.
182    pub fn new(
183        client_id: Option<ClientId>,
184        venue: Venue,
185        command_id: UUID4,
186        ts_init: UnixNanos,
187        correlation_id: Option<UUID4>,
188        params: Option<Params>,
189    ) -> Self {
190        Self {
191            client_id,
192            venue,
193            command_id,
194            ts_init,
195            correlation_id,
196            params,
197        }
198    }
199}
200
201#[derive(Clone, Debug, Serialize, Deserialize)]
202#[cfg_attr(
203    feature = "python",
204    pyo3::pyclass(module = "nautilus_trader.live", frozen)
205)]
206#[cfg_attr(
207    feature = "python",
208    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
209)]
210pub struct SubscribeBookDeltas {
211    #[serde(deserialize_with = "deserialize_instrument_ids")]
212    pub instrument_ids: Vec<InstrumentId>,
213    pub book_type: BookType,
214    pub client_id: Option<ClientId>,
215    pub venue: Option<Venue>,
216    pub command_id: UUID4,
217    pub ts_init: UnixNanos,
218    pub depth: Option<NonZeroUsize>,
219    pub managed: bool,
220    pub correlation_id: Option<UUID4>,
221    pub params: Option<Params>,
222}
223
224impl SubscribeBookDeltas {
225    /// Creates a new [`SubscribeBookDeltas`] instance.
226    ///
227    /// Duplicate IDs are removed in first-occurrence order.
228    ///
229    /// # Panics
230    ///
231    /// Panics if:
232    /// - The identifier list is empty.
233    /// - Both client and venue are absent.
234    #[expect(clippy::too_many_arguments)]
235    pub fn new(
236        instrument_ids: impl IntoInstrumentIds,
237        book_type: BookType,
238        client_id: Option<ClientId>,
239        venue: Option<Venue>,
240        command_id: UUID4,
241        ts_init: UnixNanos,
242        depth: Option<NonZeroUsize>,
243        managed: bool,
244        correlation_id: Option<UUID4>,
245        params: Option<Params>,
246    ) -> Self {
247        let instrument_ids = instrument_ids
248            .into_instrument_ids()
249            .expect("invalid instrument identifier list");
250        check_client_id_or_venue(&client_id, &venue);
251        Self {
252            instrument_ids,
253            book_type,
254            client_id,
255            venue,
256            command_id,
257            ts_init,
258            depth,
259            managed,
260            correlation_id,
261            params,
262        }
263    }
264
265    /// Creates a command for the supplied IDs with the same routing and identity.
266    ///
267    /// # Panics
268    ///
269    /// Panics if the identifier list is empty.
270    #[must_use]
271    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
272        let instrument_ids = instrument_ids
273            .into_instrument_ids()
274            .expect("invalid instrument identifier list");
275
276        Self {
277            instrument_ids,
278            book_type: self.book_type,
279            client_id: self.client_id,
280            venue: self.venue,
281            command_id: self.command_id,
282            ts_init: self.ts_init,
283            depth: self.depth,
284            managed: self.managed,
285            correlation_id: self.correlation_id,
286            params: self.params.clone(),
287        }
288    }
289
290    /// Returns the instrument ID for a single-instrument adapter path.
291    ///
292    /// # Errors
293    ///
294    /// Returns an error if the command does not contain exactly one instrument.
295    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
296        single_instrument_id(&self.instrument_ids, "subscription")
297    }
298}
299
300#[derive(Clone, Debug, Serialize, Deserialize)]
301#[cfg_attr(
302    feature = "python",
303    pyo3::pyclass(module = "nautilus_trader.live", frozen)
304)]
305#[cfg_attr(
306    feature = "python",
307    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
308)]
309pub struct SubscribeBookDepth {
310    #[serde(deserialize_with = "deserialize_instrument_ids")]
311    pub instrument_ids: Vec<InstrumentId>,
312    pub book_type: BookType,
313    pub client_id: Option<ClientId>,
314    pub venue: Option<Venue>,
315    pub command_id: UUID4,
316    pub ts_init: UnixNanos,
317    pub depth: Option<NonZeroUsize>,
318    pub managed: bool,
319    pub correlation_id: Option<UUID4>,
320    pub params: Option<Params>,
321}
322
323impl SubscribeBookDepth {
324    /// Creates a new [`SubscribeBookDepth`] instance.
325    ///
326    /// Duplicate IDs are removed in first-occurrence order.
327    ///
328    /// # Panics
329    ///
330    /// Panics if:
331    /// - The identifier list is empty.
332    /// - Both client and venue are absent.
333    #[expect(clippy::too_many_arguments)]
334    pub fn new(
335        instrument_ids: impl IntoInstrumentIds,
336        book_type: BookType,
337        client_id: Option<ClientId>,
338        venue: Option<Venue>,
339        command_id: UUID4,
340        ts_init: UnixNanos,
341        depth: Option<NonZeroUsize>,
342        managed: bool,
343        correlation_id: Option<UUID4>,
344        params: Option<Params>,
345    ) -> Self {
346        let instrument_ids = instrument_ids
347            .into_instrument_ids()
348            .expect("invalid instrument identifier list");
349        check_client_id_or_venue(&client_id, &venue);
350        Self {
351            instrument_ids,
352            book_type,
353            client_id,
354            venue,
355            command_id,
356            ts_init,
357            depth,
358            managed,
359            correlation_id,
360            params,
361        }
362    }
363
364    /// Creates a command for the supplied IDs with the same routing and identity.
365    ///
366    /// # Panics
367    ///
368    /// Panics if the identifier list is empty.
369    #[must_use]
370    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
371        let instrument_ids = instrument_ids
372            .into_instrument_ids()
373            .expect("invalid instrument identifier list");
374
375        Self {
376            instrument_ids,
377            book_type: self.book_type,
378            client_id: self.client_id,
379            venue: self.venue,
380            command_id: self.command_id,
381            ts_init: self.ts_init,
382            depth: self.depth,
383            managed: self.managed,
384            correlation_id: self.correlation_id,
385            params: self.params.clone(),
386        }
387    }
388
389    /// Returns the instrument ID for a single-instrument adapter path.
390    ///
391    /// # Errors
392    ///
393    /// Returns an error if the command does not contain exactly one instrument.
394    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
395        single_instrument_id(&self.instrument_ids, "subscription")
396    }
397}
398
399#[derive(Clone, Debug, Serialize, Deserialize)]
400pub struct SubscribeBookSnapshots {
401    #[serde(deserialize_with = "deserialize_instrument_ids")]
402    pub instrument_ids: Vec<InstrumentId>,
403    pub book_type: BookType,
404    pub client_id: Option<ClientId>,
405    pub venue: Option<Venue>,
406    pub command_id: UUID4,
407    pub ts_init: UnixNanos,
408    pub depth: Option<NonZeroUsize>,
409    pub interval_ms: NonZeroUsize,
410    pub correlation_id: Option<UUID4>,
411    pub params: Option<Params>,
412}
413
414impl SubscribeBookSnapshots {
415    /// Creates a new [`SubscribeBookSnapshots`] instance.
416    ///
417    /// Duplicate IDs are removed in first-occurrence order.
418    ///
419    /// # Panics
420    ///
421    /// Panics if:
422    /// - The identifier list is empty.
423    /// - Both client and venue are absent.
424    #[expect(clippy::too_many_arguments)]
425    pub fn new(
426        instrument_ids: impl IntoInstrumentIds,
427        book_type: BookType,
428        client_id: Option<ClientId>,
429        venue: Option<Venue>,
430        command_id: UUID4,
431        ts_init: UnixNanos,
432        depth: Option<NonZeroUsize>,
433        interval_ms: NonZeroUsize,
434        correlation_id: Option<UUID4>,
435        params: Option<Params>,
436    ) -> Self {
437        let instrument_ids = instrument_ids
438            .into_instrument_ids()
439            .expect("invalid instrument identifier list");
440        check_client_id_or_venue(&client_id, &venue);
441        Self {
442            instrument_ids,
443            book_type,
444            client_id,
445            venue,
446            command_id,
447            ts_init,
448            depth,
449            interval_ms,
450            correlation_id,
451            params,
452        }
453    }
454
455    /// Creates a command for the supplied IDs with the same routing and identity.
456    ///
457    /// # Panics
458    ///
459    /// Panics if the identifier list is empty.
460    #[must_use]
461    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
462        let instrument_ids = instrument_ids
463            .into_instrument_ids()
464            .expect("invalid instrument identifier list");
465
466        Self {
467            instrument_ids,
468            book_type: self.book_type,
469            client_id: self.client_id,
470            venue: self.venue,
471            command_id: self.command_id,
472            ts_init: self.ts_init,
473            depth: self.depth,
474            interval_ms: self.interval_ms,
475            correlation_id: self.correlation_id,
476            params: self.params.clone(),
477        }
478    }
479
480    /// Returns the instrument ID for a single-instrument adapter path.
481    ///
482    /// # Errors
483    ///
484    /// Returns an error if the command does not contain exactly one instrument.
485    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
486        single_instrument_id(&self.instrument_ids, "subscription")
487    }
488}
489
490#[derive(Clone, Debug, Serialize, Deserialize)]
491#[cfg_attr(
492    feature = "python",
493    pyo3::pyclass(module = "nautilus_trader.live", frozen)
494)]
495#[cfg_attr(
496    feature = "python",
497    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
498)]
499pub struct SubscribeQuotes {
500    #[serde(deserialize_with = "deserialize_instrument_ids")]
501    pub instrument_ids: Vec<InstrumentId>,
502    pub client_id: Option<ClientId>,
503    pub venue: Option<Venue>,
504    pub command_id: UUID4,
505    pub ts_init: UnixNanos,
506    pub correlation_id: Option<UUID4>,
507    pub params: Option<Params>,
508}
509
510impl SubscribeQuotes {
511    /// Creates a new [`SubscribeQuotes`] instance.
512    ///
513    /// Duplicate IDs are removed in first-occurrence order.
514    ///
515    /// # Panics
516    ///
517    /// Panics if:
518    /// - The identifier list is empty.
519    /// - Both client and venue are absent.
520    pub fn new(
521        instrument_ids: impl IntoInstrumentIds,
522        client_id: Option<ClientId>,
523        venue: Option<Venue>,
524        command_id: UUID4,
525        ts_init: UnixNanos,
526        correlation_id: Option<UUID4>,
527        params: Option<Params>,
528    ) -> Self {
529        let instrument_ids = instrument_ids
530            .into_instrument_ids()
531            .expect("invalid instrument identifier list");
532        check_client_id_or_venue(&client_id, &venue);
533        Self {
534            instrument_ids,
535            client_id,
536            venue,
537            command_id,
538            ts_init,
539            correlation_id,
540            params,
541        }
542    }
543
544    /// Creates a command for the supplied IDs with the same routing and identity.
545    ///
546    /// # Panics
547    ///
548    /// Panics if the identifier list is empty.
549    #[must_use]
550    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
551        let instrument_ids = instrument_ids
552            .into_instrument_ids()
553            .expect("invalid instrument identifier list");
554
555        Self {
556            instrument_ids,
557            client_id: self.client_id,
558            venue: self.venue,
559            command_id: self.command_id,
560            ts_init: self.ts_init,
561            correlation_id: self.correlation_id,
562            params: self.params.clone(),
563        }
564    }
565
566    /// Returns the instrument ID for a single-instrument adapter path.
567    ///
568    /// # Errors
569    ///
570    /// Returns an error if the command does not contain exactly one instrument.
571    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
572        single_instrument_id(&self.instrument_ids, "subscription")
573    }
574}
575
576/// Reports synchronous acceptance during a failed subscribe or unsubscribe operation.
577///
578/// `instrument_ids_accepted` and `bar_types_accepted` identify accepted members of the dispatched
579/// command. Bar operations use full bar types. Unsubscribe acceptance means successful release;
580/// venue acknowledgements arrive asynchronously.
581#[derive(Debug, thiserror::Error)]
582#[error("{source}")]
583#[non_exhaustive]
584pub struct SubscriptionError {
585    pub instrument_ids_accepted: Vec<InstrumentId>,
586    pub bar_types_accepted: Vec<BarType>,
587    #[source]
588    pub source: anyhow::Error,
589}
590
591impl SubscriptionError {
592    /// Creates an error with the exact accepted instrument members.
593    #[must_use]
594    pub fn instruments(instrument_ids_accepted: Vec<InstrumentId>, source: anyhow::Error) -> Self {
595        Self {
596            instrument_ids_accepted,
597            bar_types_accepted: Vec::new(),
598            source,
599        }
600    }
601
602    /// Creates an error with the exact accepted full bar types.
603    #[must_use]
604    pub fn bars(bar_types_accepted: Vec<BarType>, source: anyhow::Error) -> Self {
605        Self {
606            instrument_ids_accepted: Vec::new(),
607            bar_types_accepted,
608            source,
609        }
610    }
611}
612
613#[derive(Clone, Debug, Serialize, Deserialize)]
614#[cfg_attr(
615    feature = "python",
616    pyo3::pyclass(module = "nautilus_trader.live", frozen)
617)]
618#[cfg_attr(
619    feature = "python",
620    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
621)]
622pub struct SubscribeTrades {
623    #[serde(deserialize_with = "deserialize_instrument_ids")]
624    pub instrument_ids: Vec<InstrumentId>,
625    pub client_id: Option<ClientId>,
626    pub venue: Option<Venue>,
627    pub command_id: UUID4,
628    pub ts_init: UnixNanos,
629    pub correlation_id: Option<UUID4>,
630    pub params: Option<Params>,
631}
632
633impl SubscribeTrades {
634    /// Creates a new [`SubscribeTrades`] instance.
635    ///
636    /// Duplicate IDs are removed in first-occurrence order.
637    ///
638    /// # Panics
639    ///
640    /// Panics if:
641    /// - The identifier list is empty.
642    /// - Both client and venue are absent.
643    pub fn new(
644        instrument_ids: impl IntoInstrumentIds,
645        client_id: Option<ClientId>,
646        venue: Option<Venue>,
647        command_id: UUID4,
648        ts_init: UnixNanos,
649        correlation_id: Option<UUID4>,
650        params: Option<Params>,
651    ) -> Self {
652        let instrument_ids = instrument_ids
653            .into_instrument_ids()
654            .expect("invalid instrument identifier list");
655        check_client_id_or_venue(&client_id, &venue);
656        Self {
657            instrument_ids,
658            client_id,
659            venue,
660            command_id,
661            ts_init,
662            correlation_id,
663            params,
664        }
665    }
666
667    /// Creates a command for the supplied IDs with the same routing and identity.
668    ///
669    /// # Panics
670    ///
671    /// Panics if the identifier list is empty.
672    #[must_use]
673    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
674        let instrument_ids = instrument_ids
675            .into_instrument_ids()
676            .expect("invalid instrument identifier list");
677
678        Self {
679            instrument_ids,
680            client_id: self.client_id,
681            venue: self.venue,
682            command_id: self.command_id,
683            ts_init: self.ts_init,
684            correlation_id: self.correlation_id,
685            params: self.params.clone(),
686        }
687    }
688
689    /// Returns the instrument ID for a single-instrument adapter path.
690    ///
691    /// # Errors
692    ///
693    /// Returns an error if the command does not contain exactly one instrument.
694    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
695        single_instrument_id(&self.instrument_ids, "subscription")
696    }
697}
698
699#[derive(Clone, Debug, Serialize, Deserialize)]
700#[cfg_attr(
701    feature = "python",
702    pyo3::pyclass(module = "nautilus_trader.live", frozen)
703)]
704#[cfg_attr(
705    feature = "python",
706    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
707)]
708pub struct SubscribeBars {
709    #[serde(deserialize_with = "deserialize_bar_types")]
710    pub bar_types: Vec<BarType>,
711    pub client_id: Option<ClientId>,
712    pub venue: Option<Venue>,
713    pub command_id: UUID4,
714    pub ts_init: UnixNanos,
715    pub correlation_id: Option<UUID4>,
716    pub params: Option<Params>,
717}
718
719impl SubscribeBars {
720    /// Creates a new [`SubscribeBars`] instance.
721    ///
722    /// Duplicate bar types are removed in first-occurrence order.
723    ///
724    /// # Panics
725    ///
726    /// Panics if:
727    /// - The bar type list is empty.
728    /// - Both client and venue are absent.
729    pub fn new(
730        bar_types: impl IntoBarTypes,
731        client_id: Option<ClientId>,
732        venue: Option<Venue>,
733        command_id: UUID4,
734        ts_init: UnixNanos,
735        correlation_id: Option<UUID4>,
736        params: Option<Params>,
737    ) -> Self {
738        let bar_types = bar_types.into_bar_types().expect("invalid bar type list");
739        check_client_id_or_venue(&client_id, &venue);
740        Self {
741            bar_types,
742            client_id,
743            venue,
744            command_id,
745            ts_init,
746            correlation_id,
747            params,
748        }
749    }
750
751    /// Creates a command for the supplied bar types with the same routing and identity.
752    ///
753    /// # Panics
754    ///
755    /// Panics if the bar type list is empty.
756    #[must_use]
757    pub fn with_bar_types(&self, bar_types: impl IntoBarTypes) -> Self {
758        let bar_types = bar_types.into_bar_types().expect("invalid bar type list");
759
760        Self {
761            bar_types,
762            client_id: self.client_id,
763            venue: self.venue,
764            command_id: self.command_id,
765            ts_init: self.ts_init,
766            correlation_id: self.correlation_id,
767            params: self.params.clone(),
768        }
769    }
770
771    /// Returns the bar type for a single-bar-type adapter path.
772    ///
773    /// # Errors
774    ///
775    /// Returns an error if the command does not contain exactly one bar type.
776    pub fn single_bar_type(&self) -> anyhow::Result<BarType> {
777        single_bar_type(&self.bar_types, "subscription")
778    }
779}
780
781#[derive(Clone, Debug, Serialize, Deserialize)]
782#[cfg_attr(
783    feature = "python",
784    pyo3::pyclass(module = "nautilus_trader.live", frozen)
785)]
786#[cfg_attr(
787    feature = "python",
788    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
789)]
790pub struct SubscribeMarkPrices {
791    #[serde(deserialize_with = "deserialize_instrument_ids")]
792    pub instrument_ids: Vec<InstrumentId>,
793    pub client_id: Option<ClientId>,
794    pub venue: Option<Venue>,
795    pub command_id: UUID4,
796    pub ts_init: UnixNanos,
797    pub correlation_id: Option<UUID4>,
798    pub params: Option<Params>,
799}
800
801impl SubscribeMarkPrices {
802    /// Creates a new [`SubscribeMarkPrices`] instance.
803    ///
804    /// Duplicate IDs are removed in first-occurrence order.
805    ///
806    /// # Panics
807    ///
808    /// Panics if:
809    /// - The identifier list is empty.
810    /// - Both client and venue are absent.
811    pub fn new(
812        instrument_ids: impl IntoInstrumentIds,
813        client_id: Option<ClientId>,
814        venue: Option<Venue>,
815        command_id: UUID4,
816        ts_init: UnixNanos,
817        correlation_id: Option<UUID4>,
818        params: Option<Params>,
819    ) -> Self {
820        let instrument_ids = instrument_ids
821            .into_instrument_ids()
822            .expect("invalid instrument identifier list");
823        check_client_id_or_venue(&client_id, &venue);
824        Self {
825            instrument_ids,
826            client_id,
827            venue,
828            command_id,
829            ts_init,
830            correlation_id,
831            params,
832        }
833    }
834
835    /// Creates a command for the supplied IDs with the same routing and identity.
836    ///
837    /// # Panics
838    ///
839    /// Panics if the identifier list is empty.
840    #[must_use]
841    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
842        let instrument_ids = instrument_ids
843            .into_instrument_ids()
844            .expect("invalid instrument identifier list");
845
846        Self {
847            instrument_ids,
848            client_id: self.client_id,
849            venue: self.venue,
850            command_id: self.command_id,
851            ts_init: self.ts_init,
852            correlation_id: self.correlation_id,
853            params: self.params.clone(),
854        }
855    }
856
857    /// Returns the instrument ID for a single-instrument adapter path.
858    ///
859    /// # Errors
860    ///
861    /// Returns an error if the command does not contain exactly one instrument.
862    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
863        single_instrument_id(&self.instrument_ids, "subscription")
864    }
865}
866
867#[derive(Clone, Debug, Serialize, Deserialize)]
868#[cfg_attr(
869    feature = "python",
870    pyo3::pyclass(module = "nautilus_trader.live", frozen)
871)]
872#[cfg_attr(
873    feature = "python",
874    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
875)]
876pub struct SubscribeIndexPrices {
877    #[serde(deserialize_with = "deserialize_instrument_ids")]
878    pub instrument_ids: Vec<InstrumentId>,
879    pub client_id: Option<ClientId>,
880    pub venue: Option<Venue>,
881    pub command_id: UUID4,
882    pub ts_init: UnixNanos,
883    pub correlation_id: Option<UUID4>,
884    pub params: Option<Params>,
885}
886
887impl SubscribeIndexPrices {
888    /// Creates a new [`SubscribeIndexPrices`] instance.
889    ///
890    /// Duplicate IDs are removed in first-occurrence order.
891    ///
892    /// # Panics
893    ///
894    /// Panics if:
895    /// - The identifier list is empty.
896    /// - Both client and venue are absent.
897    pub fn new(
898        instrument_ids: impl IntoInstrumentIds,
899        client_id: Option<ClientId>,
900        venue: Option<Venue>,
901        command_id: UUID4,
902        ts_init: UnixNanos,
903        correlation_id: Option<UUID4>,
904        params: Option<Params>,
905    ) -> Self {
906        let instrument_ids = instrument_ids
907            .into_instrument_ids()
908            .expect("invalid instrument identifier list");
909        check_client_id_or_venue(&client_id, &venue);
910        Self {
911            instrument_ids,
912            client_id,
913            venue,
914            command_id,
915            ts_init,
916            correlation_id,
917            params,
918        }
919    }
920
921    /// Creates a command for the supplied IDs with the same routing and identity.
922    ///
923    /// # Panics
924    ///
925    /// Panics if the identifier list is empty.
926    #[must_use]
927    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
928        let instrument_ids = instrument_ids
929            .into_instrument_ids()
930            .expect("invalid instrument identifier list");
931
932        Self {
933            instrument_ids,
934            client_id: self.client_id,
935            venue: self.venue,
936            command_id: self.command_id,
937            ts_init: self.ts_init,
938            correlation_id: self.correlation_id,
939            params: self.params.clone(),
940        }
941    }
942
943    /// Returns the instrument ID for a single-instrument adapter path.
944    ///
945    /// # Errors
946    ///
947    /// Returns an error if the command does not contain exactly one instrument.
948    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
949        single_instrument_id(&self.instrument_ids, "subscription")
950    }
951}
952
953#[derive(Clone, Debug, Serialize, Deserialize)]
954#[cfg_attr(
955    feature = "python",
956    pyo3::pyclass(module = "nautilus_trader.live", frozen)
957)]
958#[cfg_attr(
959    feature = "python",
960    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
961)]
962pub struct SubscribeFundingRates {
963    #[serde(deserialize_with = "deserialize_instrument_ids")]
964    pub instrument_ids: Vec<InstrumentId>,
965    pub client_id: Option<ClientId>,
966    pub venue: Option<Venue>,
967    pub command_id: UUID4,
968    pub ts_init: UnixNanos,
969    pub correlation_id: Option<UUID4>,
970    pub params: Option<Params>,
971}
972
973impl SubscribeFundingRates {
974    /// Creates a new [`SubscribeFundingRates`] instance.
975    ///
976    /// Duplicate IDs are removed in first-occurrence order.
977    ///
978    /// # Panics
979    ///
980    /// Panics if:
981    /// - The identifier list is empty.
982    /// - Both client and venue are absent.
983    pub fn new(
984        instrument_ids: impl IntoInstrumentIds,
985        client_id: Option<ClientId>,
986        venue: Option<Venue>,
987        command_id: UUID4,
988        ts_init: UnixNanos,
989        correlation_id: Option<UUID4>,
990        params: Option<Params>,
991    ) -> Self {
992        let instrument_ids = instrument_ids
993            .into_instrument_ids()
994            .expect("invalid instrument identifier list");
995        check_client_id_or_venue(&client_id, &venue);
996        Self {
997            instrument_ids,
998            client_id,
999            venue,
1000            command_id,
1001            ts_init,
1002            correlation_id,
1003            params,
1004        }
1005    }
1006
1007    /// Creates a command for the supplied IDs with the same routing and identity.
1008    ///
1009    /// # Panics
1010    ///
1011    /// Panics if the identifier list is empty.
1012    #[must_use]
1013    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
1014        let instrument_ids = instrument_ids
1015            .into_instrument_ids()
1016            .expect("invalid instrument identifier list");
1017
1018        Self {
1019            instrument_ids,
1020            client_id: self.client_id,
1021            venue: self.venue,
1022            command_id: self.command_id,
1023            ts_init: self.ts_init,
1024            correlation_id: self.correlation_id,
1025            params: self.params.clone(),
1026        }
1027    }
1028
1029    /// Returns the instrument ID for a single-instrument adapter path.
1030    ///
1031    /// # Errors
1032    ///
1033    /// Returns an error if the command does not contain exactly one instrument.
1034    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
1035        single_instrument_id(&self.instrument_ids, "subscription")
1036    }
1037}
1038
1039#[derive(Clone, Debug, Serialize, Deserialize)]
1040#[cfg_attr(
1041    feature = "python",
1042    pyo3::pyclass(module = "nautilus_trader.live", frozen)
1043)]
1044#[cfg_attr(
1045    feature = "python",
1046    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
1047)]
1048pub struct SubscribeInstrumentStatus {
1049    #[serde(deserialize_with = "deserialize_instrument_ids")]
1050    pub instrument_ids: Vec<InstrumentId>,
1051    pub client_id: Option<ClientId>,
1052    pub venue: Option<Venue>,
1053    pub command_id: UUID4,
1054    pub ts_init: UnixNanos,
1055    pub correlation_id: Option<UUID4>,
1056    pub params: Option<Params>,
1057}
1058
1059impl SubscribeInstrumentStatus {
1060    /// Creates a new [`SubscribeInstrumentStatus`] instance.
1061    ///
1062    /// Duplicate IDs are removed in first-occurrence order.
1063    ///
1064    /// # Panics
1065    ///
1066    /// Panics if:
1067    /// - The identifier list is empty.
1068    /// - Both client and venue are absent.
1069    pub fn new(
1070        instrument_ids: impl IntoInstrumentIds,
1071        client_id: Option<ClientId>,
1072        venue: Option<Venue>,
1073        command_id: UUID4,
1074        ts_init: UnixNanos,
1075        correlation_id: Option<UUID4>,
1076        params: Option<Params>,
1077    ) -> Self {
1078        let instrument_ids = instrument_ids
1079            .into_instrument_ids()
1080            .expect("invalid instrument identifier list");
1081        check_client_id_or_venue(&client_id, &venue);
1082        Self {
1083            instrument_ids,
1084            client_id,
1085            venue,
1086            command_id,
1087            ts_init,
1088            correlation_id,
1089            params,
1090        }
1091    }
1092
1093    /// Creates a command for the supplied IDs with the same routing and identity.
1094    ///
1095    /// # Panics
1096    ///
1097    /// Panics if the identifier list is empty.
1098    #[must_use]
1099    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
1100        let instrument_ids = instrument_ids
1101            .into_instrument_ids()
1102            .expect("invalid instrument identifier list");
1103
1104        Self {
1105            instrument_ids,
1106            client_id: self.client_id,
1107            venue: self.venue,
1108            command_id: self.command_id,
1109            ts_init: self.ts_init,
1110            correlation_id: self.correlation_id,
1111            params: self.params.clone(),
1112        }
1113    }
1114
1115    /// Returns the instrument ID for a single-instrument adapter path.
1116    ///
1117    /// # Errors
1118    ///
1119    /// Returns an error if the command does not contain exactly one instrument.
1120    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
1121        single_instrument_id(&self.instrument_ids, "subscription")
1122    }
1123}
1124
1125#[derive(Clone, Debug, Serialize, Deserialize)]
1126#[cfg_attr(
1127    feature = "python",
1128    pyo3::pyclass(module = "nautilus_trader.live", frozen)
1129)]
1130#[cfg_attr(
1131    feature = "python",
1132    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
1133)]
1134pub struct SubscribeOptionGreeks {
1135    #[serde(deserialize_with = "deserialize_instrument_ids")]
1136    pub instrument_ids: Vec<InstrumentId>,
1137    pub client_id: Option<ClientId>,
1138    pub venue: Option<Venue>,
1139    pub command_id: UUID4,
1140    pub ts_init: UnixNanos,
1141    pub correlation_id: Option<UUID4>,
1142    pub params: Option<Params>,
1143}
1144
1145impl SubscribeOptionGreeks {
1146    /// Creates a new [`SubscribeOptionGreeks`] instance.
1147    ///
1148    /// Duplicate IDs are removed in first-occurrence order.
1149    ///
1150    /// # Panics
1151    ///
1152    /// Panics if:
1153    /// - The identifier list is empty.
1154    /// - Both client and venue are absent.
1155    pub fn new(
1156        instrument_ids: impl IntoInstrumentIds,
1157        client_id: Option<ClientId>,
1158        venue: Option<Venue>,
1159        command_id: UUID4,
1160        ts_init: UnixNanos,
1161        correlation_id: Option<UUID4>,
1162        params: Option<Params>,
1163    ) -> Self {
1164        let instrument_ids = instrument_ids
1165            .into_instrument_ids()
1166            .expect("invalid instrument identifier list");
1167        check_client_id_or_venue(&client_id, &venue);
1168        Self {
1169            instrument_ids,
1170            client_id,
1171            venue,
1172            command_id,
1173            ts_init,
1174            correlation_id,
1175            params,
1176        }
1177    }
1178
1179    /// Creates a command for the supplied IDs with the same routing and identity.
1180    ///
1181    /// # Panics
1182    ///
1183    /// Panics if the identifier list is empty.
1184    #[must_use]
1185    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
1186        let instrument_ids = instrument_ids
1187            .into_instrument_ids()
1188            .expect("invalid instrument identifier list");
1189
1190        Self {
1191            instrument_ids,
1192            client_id: self.client_id,
1193            venue: self.venue,
1194            command_id: self.command_id,
1195            ts_init: self.ts_init,
1196            correlation_id: self.correlation_id,
1197            params: self.params.clone(),
1198        }
1199    }
1200
1201    /// Returns the instrument ID for a single-instrument adapter path.
1202    ///
1203    /// # Errors
1204    ///
1205    /// Returns an error if the command does not contain exactly one instrument.
1206    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
1207        single_instrument_id(&self.instrument_ids, "subscription")
1208    }
1209}
1210
1211#[derive(Clone, Debug, Serialize, Deserialize)]
1212#[cfg_attr(
1213    feature = "python",
1214    pyo3::pyclass(module = "nautilus_trader.live", frozen)
1215)]
1216#[cfg_attr(
1217    feature = "python",
1218    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.live")
1219)]
1220pub struct SubscribeInstrumentClose {
1221    #[serde(deserialize_with = "deserialize_instrument_ids")]
1222    pub instrument_ids: Vec<InstrumentId>,
1223    pub client_id: Option<ClientId>,
1224    pub venue: Option<Venue>,
1225    pub command_id: UUID4,
1226    pub ts_init: UnixNanos,
1227    pub correlation_id: Option<UUID4>,
1228    pub params: Option<Params>,
1229}
1230
1231impl SubscribeInstrumentClose {
1232    /// Creates a new [`SubscribeInstrumentClose`] instance.
1233    ///
1234    /// Duplicate IDs are removed in first-occurrence order.
1235    ///
1236    /// # Panics
1237    ///
1238    /// Panics if:
1239    /// - The identifier list is empty.
1240    /// - Both client and venue are absent.
1241    pub fn new(
1242        instrument_ids: impl IntoInstrumentIds,
1243        client_id: Option<ClientId>,
1244        venue: Option<Venue>,
1245        command_id: UUID4,
1246        ts_init: UnixNanos,
1247        correlation_id: Option<UUID4>,
1248        params: Option<Params>,
1249    ) -> Self {
1250        let instrument_ids = instrument_ids
1251            .into_instrument_ids()
1252            .expect("invalid instrument identifier list");
1253        check_client_id_or_venue(&client_id, &venue);
1254        Self {
1255            instrument_ids,
1256            client_id,
1257            venue,
1258            command_id,
1259            ts_init,
1260            correlation_id,
1261            params,
1262        }
1263    }
1264
1265    /// Creates a command for the supplied IDs with the same routing and identity.
1266    ///
1267    /// # Panics
1268    ///
1269    /// Panics if the identifier list is empty.
1270    #[must_use]
1271    pub fn with_instrument_ids(&self, instrument_ids: impl IntoInstrumentIds) -> Self {
1272        let instrument_ids = instrument_ids
1273            .into_instrument_ids()
1274            .expect("invalid instrument identifier list");
1275
1276        Self {
1277            instrument_ids,
1278            client_id: self.client_id,
1279            venue: self.venue,
1280            command_id: self.command_id,
1281            ts_init: self.ts_init,
1282            correlation_id: self.correlation_id,
1283            params: self.params.clone(),
1284        }
1285    }
1286
1287    /// Returns the instrument ID for a single-instrument adapter path.
1288    ///
1289    /// # Errors
1290    ///
1291    /// Returns an error if the command does not contain exactly one instrument.
1292    pub fn single_instrument_id(&self) -> anyhow::Result<InstrumentId> {
1293        single_instrument_id(&self.instrument_ids, "subscription")
1294    }
1295}
1296
1297#[derive(Clone, Debug, Serialize, Deserialize)]
1298pub struct SubscribeOptionChain {
1299    pub series_id: OptionSeriesId,
1300    pub strike_range: StrikeRange,
1301    pub snapshot_interval_ms: Option<u64>,
1302    pub command_id: UUID4,
1303    pub ts_init: UnixNanos,
1304    pub client_id: Option<ClientId>,
1305    pub venue: Option<Venue>,
1306    #[serde(default)]
1307    pub correlation_id: Option<UUID4>,
1308    pub params: Option<Params>,
1309}
1310
1311impl SubscribeOptionChain {
1312    /// Creates a new [`SubscribeOptionChain`] instance.
1313    #[expect(
1314        clippy::too_many_arguments,
1315        reason = "constructor exposes the option chain subscription fields"
1316    )]
1317    pub fn new(
1318        series_id: OptionSeriesId,
1319        strike_range: StrikeRange,
1320        snapshot_interval_ms: Option<u64>,
1321        command_id: UUID4,
1322        ts_init: UnixNanos,
1323        client_id: Option<ClientId>,
1324        venue: Option<Venue>,
1325        params: Option<Params>,
1326    ) -> Self {
1327        check_client_id_or_venue(&client_id, &venue);
1328        Self {
1329            series_id,
1330            strike_range,
1331            snapshot_interval_ms,
1332            command_id,
1333            ts_init,
1334            client_id,
1335            venue,
1336            correlation_id: None,
1337            params,
1338        }
1339    }
1340}