nautilus_model/data/
mod.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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 types for the trading domain model.
17
18pub mod bar;
19pub mod bet;
20pub mod close;
21pub mod delta;
22pub mod deltas;
23pub mod depth;
24pub mod greeks;
25pub mod order;
26pub mod prices;
27pub mod quote;
28pub mod status;
29pub mod trade;
30
31#[cfg(feature = "stubs")]
32pub mod stubs;
33
34use std::{
35    fmt::{Debug, Display},
36    hash::{Hash, Hasher},
37    num::NonZeroU64,
38    str::FromStr,
39};
40
41use indexmap::IndexMap;
42use nautilus_core::UnixNanos;
43use serde::{Deserialize, Serialize};
44use serde_json::to_string;
45
46// Re-exports
47#[rustfmt::skip]  // Keep these grouped
48pub use bar::{Bar, BarSpecification, BarType};
49pub use delta::OrderBookDelta;
50pub use deltas::{OrderBookDeltas, OrderBookDeltas_API};
51pub use depth::{DEPTH10_LEN, OrderBookDepth10};
52pub use greeks::{BlackScholesGreeksResult, black_scholes_greeks};
53pub use order::{BookOrder, NULL_ORDER};
54pub use prices::{IndexPriceUpdate, MarkPriceUpdate};
55pub use quote::QuoteTick;
56pub use status::InstrumentStatus;
57pub use trade::TradeTick;
58
59use crate::{
60    enums::BookType,
61    identifiers::{InstrumentId, Venue},
62};
63
64/// A built-in Nautilus data type.
65///
66/// Not recommended for storing large amounts of data, as the largest variant is significantly
67/// larger (10x) than the smallest.
68#[repr(C)]
69#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
70pub enum Data {
71    Delta(OrderBookDelta),
72    Deltas(OrderBookDeltas_API),
73    Depth10(Box<OrderBookDepth10>), // This variant is significantly larger
74    Quote(QuoteTick),
75    Trade(TradeTick),
76    Bar(Bar),
77}
78
79macro_rules! impl_try_from_data {
80    ($variant:ident, $type:ty) => {
81        impl TryFrom<Data> for $type {
82            type Error = ();
83
84            fn try_from(value: Data) -> Result<Self, Self::Error> {
85                match value {
86                    Data::$variant(x) => Ok(x),
87                    _ => Err(()),
88                }
89            }
90        }
91    };
92}
93
94impl TryFrom<Data> for OrderBookDepth10 {
95    type Error = ();
96
97    fn try_from(value: Data) -> Result<Self, Self::Error> {
98        match value {
99            Data::Depth10(x) => Ok(*x),
100            _ => Err(()),
101        }
102    }
103}
104
105impl_try_from_data!(Quote, QuoteTick);
106impl_try_from_data!(Delta, OrderBookDelta);
107impl_try_from_data!(Deltas, OrderBookDeltas_API);
108impl_try_from_data!(Trade, TradeTick);
109impl_try_from_data!(Bar, Bar);
110
111pub fn to_variant<T: TryFrom<Data>>(data: Vec<Data>) -> Vec<T> {
112    data.into_iter()
113        .filter_map(|d| T::try_from(d).ok())
114        .collect()
115}
116
117impl Data {
118    /// Returns the instrument ID for the data.
119    pub fn instrument_id(&self) -> InstrumentId {
120        match self {
121            Self::Delta(delta) => delta.instrument_id,
122            Self::Deltas(deltas) => deltas.instrument_id,
123            Self::Depth10(depth) => depth.instrument_id,
124            Self::Quote(quote) => quote.instrument_id,
125            Self::Trade(trade) => trade.instrument_id,
126            Self::Bar(bar) => bar.bar_type.instrument_id(),
127        }
128    }
129
130    /// Returns whether the data is a type of order book data.
131    pub fn is_order_book_data(&self) -> bool {
132        matches!(self, Self::Delta(_) | Self::Deltas(_) | Self::Depth10(_))
133    }
134}
135
136pub trait GetTsInit {
137    fn ts_init(&self) -> UnixNanos;
138}
139
140impl GetTsInit for Data {
141    fn ts_init(&self) -> UnixNanos {
142        match self {
143            Self::Delta(d) => d.ts_init,
144            Self::Deltas(d) => d.ts_init,
145            Self::Depth10(d) => d.ts_init,
146            Self::Quote(q) => q.ts_init,
147            Self::Trade(t) => t.ts_init,
148            Self::Bar(b) => b.ts_init,
149        }
150    }
151}
152
153pub fn is_monotonically_increasing_by_init<T: GetTsInit>(data: &[T]) -> bool {
154    data.windows(2)
155        .all(|window| window[0].ts_init() <= window[1].ts_init())
156}
157
158impl From<OrderBookDelta> for Data {
159    fn from(value: OrderBookDelta) -> Self {
160        Self::Delta(value)
161    }
162}
163
164impl From<OrderBookDeltas_API> for Data {
165    fn from(value: OrderBookDeltas_API) -> Self {
166        Self::Deltas(value)
167    }
168}
169
170impl From<OrderBookDepth10> for Data {
171    fn from(value: OrderBookDepth10) -> Self {
172        Self::Depth10(Box::new(value))
173    }
174}
175
176impl From<QuoteTick> for Data {
177    fn from(value: QuoteTick) -> Self {
178        Self::Quote(value)
179    }
180}
181
182impl From<TradeTick> for Data {
183    fn from(value: TradeTick) -> Self {
184        Self::Trade(value)
185    }
186}
187
188impl From<Bar> for Data {
189    fn from(value: Bar) -> Self {
190        Self::Bar(value)
191    }
192}
193
194// TODO: https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
195// i128 and u128 is now FFI compatible. However, since the clippy lint
196// hasn't been removed yet. We'll suppress with #[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
197#[unsafe(no_mangle)]
198#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
199pub extern "C" fn data_clone(data: &Data) -> Data {
200    // Dummy function for cbindgen to export types
201    data.clone()
202}
203
204/// Represents a data type including metadata.
205#[derive(Clone, Serialize, Deserialize)]
206#[cfg_attr(
207    feature = "python",
208    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
209)]
210pub struct DataType {
211    type_name: String,
212    metadata: Option<IndexMap<String, String>>,
213    topic: String,
214    hash: u64,
215}
216
217impl DataType {
218    /// Creates a new [`DataType`] instance.
219    pub fn new(type_name: &str, metadata: Option<IndexMap<String, String>>) -> Self {
220        // Precompute topic
221        let topic = if let Some(ref meta) = metadata {
222            let meta_str = meta
223                .iter()
224                .map(|(k, v)| format!("{}={}", k, v))
225                .collect::<Vec<_>>()
226                .join(".");
227            format!("{}.{}", type_name, meta_str)
228        } else {
229            type_name.to_string()
230        };
231
232        // Precompute hash
233        let mut hasher = std::collections::hash_map::DefaultHasher::new();
234        topic.hash(&mut hasher);
235
236        Self {
237            type_name: type_name.to_owned(),
238            metadata,
239            topic,
240            hash: hasher.finish(),
241        }
242    }
243
244    /// Returns the type name for the data type.
245    pub fn type_name(&self) -> &str {
246        self.type_name.as_str()
247    }
248
249    /// Returns the metadata for the data type.
250    pub fn metadata(&self) -> Option<&IndexMap<String, String>> {
251        self.metadata.as_ref()
252    }
253
254    /// Returns a string representation of the metadata.
255    pub fn metadata_str(&self) -> String {
256        self.metadata
257            .as_ref()
258            .map(|metadata| to_string(metadata).unwrap_or_default())
259            .unwrap_or_else(|| "null".to_string())
260    }
261
262    /// Returns the messaging topic for the data type.
263    pub fn topic(&self) -> &str {
264        self.topic.as_str()
265    }
266
267    /// Returns an [`Option<InstrumentId>`] from the metadata.
268    ///
269    /// # Panics
270    ///
271    /// This function panics:
272    /// - If there is no metadata.
273    /// - If the `instrument_id` value contained in the metadata is invalid.
274    pub fn instrument_id(&self) -> Option<InstrumentId> {
275        let metadata = self.metadata.as_ref().expect("metadata was `None`");
276        let instrument_id = metadata.get("instrument_id")?;
277        Some(
278            InstrumentId::from_str(instrument_id)
279                .expect("Invalid `InstrumentId` for 'instrument_id'"),
280        )
281    }
282
283    /// Returns an [`Option<Venue>`] from the metadata.
284    ///
285    /// # Panics
286    ///
287    /// This function panics:
288    /// - If there is no metadata.
289    /// - If the `venue` value contained in the metadata is invalid.
290    pub fn venue(&self) -> Option<Venue> {
291        let metadata = self.metadata.as_ref().expect("metadata was `None`");
292        let venue_str = metadata.get("venue")?;
293        Some(Venue::from(venue_str.as_str()))
294    }
295
296    /// Returns a [`BarType`] from the metadata.
297    ///
298    /// # Panics
299    ///
300    /// This function panics:
301    /// - If there is no metadata.
302    /// - If the metadata does not contain a `bar_type`.
303    /// - If the `bar_type` value contained in the metadata is invalid.
304    pub fn bar_type(&self) -> BarType {
305        let metadata = self.metadata.as_ref().expect("metadata was `None`");
306        let bar_type_str = metadata
307            .get("bar_type")
308            .expect("No 'bar_type' found in metadata");
309        BarType::from_str(bar_type_str).expect("Invalid `BarType` for 'bar_type'")
310    }
311
312    /// Returns an [`Option<UnixNanos>`] start from the metadata.
313    ///
314    /// # Panics
315    ///
316    /// This function panics:
317    /// - If there is no metadata.
318    /// - If the `start` value contained in the metadata is invalid.
319    pub fn start(&self) -> Option<UnixNanos> {
320        let metadata = self.metadata.as_ref()?;
321        let start_str = metadata.get("start")?;
322        Some(UnixNanos::from_str(start_str).expect("Invalid `UnixNanos` for 'start'"))
323    }
324
325    /// Returns an [`Option<UnixNanos>`] end from the metadata.
326    ///
327    /// # Panics
328    ///
329    /// This function panics:
330    /// - If there is no metadata.
331    /// - If the `end` value contained in the metadata is invalid.
332    pub fn end(&self) -> Option<UnixNanos> {
333        let metadata = self.metadata.as_ref()?;
334        let end_str = metadata.get("end")?;
335        Some(UnixNanos::from_str(end_str).expect("Invalid `UnixNanos` for 'end'"))
336    }
337
338    /// Returns an [`Option<usize>`] limit from the metadata.
339    ///
340    /// # Panics
341    ///
342    /// This function panics:
343    /// - If there is no metadata.
344    /// - If the `limit` value contained in the metadata is invalid.
345    pub fn limit(&self) -> Option<usize> {
346        let metadata = self.metadata.as_ref()?;
347        let depth_str = metadata.get("limit")?;
348        Some(
349            depth_str
350                .parse::<usize>()
351                .expect("Invalid `usize` for 'limit'"),
352        )
353    }
354
355    /// Returns a [`BookType`] from the metadata.
356    ///
357    /// # Panics
358    ///
359    /// This function panics:
360    /// - If there is no metadata.
361    /// - If the `book_type` value contained in the metadata is invalid.
362    pub fn book_type(&self) -> BookType {
363        let metadata = self.metadata.as_ref().expect("metadata was `None`");
364        let book_type_str = metadata
365            .get("book_type")
366            .expect("'book_type' not found in metadata");
367        BookType::from_str(book_type_str).expect("Invalid `BookType` for 'book_type'")
368    }
369
370    /// Returns an [`Option<usize>`] depth from the metadata.
371    ///
372    /// # Panics
373    ///
374    /// This function panics:
375    /// - If there is no metadata.
376    /// - If the `depth` value contained in the metadata is invalid.
377    pub fn depth(&self) -> Option<usize> {
378        let metadata = self.metadata.as_ref()?;
379        let depth_str = metadata.get("depth")?;
380        Some(
381            depth_str
382                .parse::<usize>()
383                .expect("Invalid `usize` for 'depth'"),
384        )
385    }
386
387    /// Returns a [`NonZeroU64`] interval (milliseconds) from the metadata.
388    ///
389    /// # Panics
390    ///
391    /// This function panics:
392    /// - If there is no metadata.
393    /// - If the `interval_ms` value contained in the metadata is invalid.
394    pub fn interval_ms(&self) -> NonZeroU64 {
395        let metadata = self.metadata.as_ref().expect("metadata was `None`");
396        let interval_ms_str = metadata
397            .get("interval_ms")
398            .expect("No 'interval_ms' in metadata");
399
400        interval_ms_str
401            .parse::<NonZeroU64>()
402            .expect("Invalid `NonZeroU64` for 'interval_ms'")
403    }
404
405    /// Returns a [`bool`] from the metadata indicating whether the book should be managed.
406    ///
407    /// # Panics
408    ///
409    /// This function panics:
410    /// - If there is no metadata.
411    /// - If the `managed` value contained in the metadata is invalid.
412    pub fn managed(&self) -> bool {
413        let metadata = self.metadata.as_ref().expect("metadata was `None`");
414        let managed_str = metadata.get("managed").expect("No 'managed' in metadata");
415        managed_str
416            .parse::<bool>()
417            .expect("Invalid `bool` for 'managed'")
418    }
419}
420
421impl PartialEq for DataType {
422    fn eq(&self, other: &Self) -> bool {
423        self.topic == other.topic
424    }
425}
426
427impl Eq for DataType {}
428
429impl PartialOrd for DataType {
430    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
431        Some(self.cmp(other))
432    }
433}
434
435impl Ord for DataType {
436    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
437        self.topic.cmp(&other.topic)
438    }
439}
440
441impl Hash for DataType {
442    fn hash<H: Hasher>(&self, state: &mut H) {
443        self.hash.hash(state);
444    }
445}
446
447impl Display for DataType {
448    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449        write!(f, "{}", self.topic)
450    }
451}
452
453impl Debug for DataType {
454    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
455        write!(
456            f,
457            "DataType(type_name={}, metadata={:?})",
458            self.type_name, self.metadata
459        )
460    }
461}
462
463////////////////////////////////////////////////////////////////////////////////
464// Tests
465////////////////////////////////////////////////////////////////////////////////
466#[cfg(test)]
467mod tests {
468    use std::hash::DefaultHasher;
469
470    use rstest::*;
471
472    use super::*;
473
474    #[rstest]
475    fn test_data_type_creation_with_metadata() {
476        let metadata = Some(
477            [
478                ("key1".to_string(), "value1".to_string()),
479                ("key2".to_string(), "value2".to_string()),
480            ]
481            .iter()
482            .cloned()
483            .collect(),
484        );
485        let data_type = DataType::new("ExampleType", metadata.clone());
486
487        assert_eq!(data_type.type_name(), "ExampleType");
488        assert_eq!(data_type.topic(), "ExampleType.key1=value1.key2=value2");
489        assert_eq!(data_type.metadata(), metadata.as_ref());
490    }
491
492    #[rstest]
493    fn test_data_type_creation_without_metadata() {
494        let data_type = DataType::new("ExampleType", None);
495
496        assert_eq!(data_type.type_name(), "ExampleType");
497        assert_eq!(data_type.topic(), "ExampleType");
498        assert_eq!(data_type.metadata(), None);
499    }
500
501    #[rstest]
502    fn test_data_type_equality() {
503        let metadata1 = Some(
504            [("key1".to_string(), "value1".to_string())]
505                .iter()
506                .cloned()
507                .collect(),
508        );
509        let metadata2 = Some(
510            [("key1".to_string(), "value1".to_string())]
511                .iter()
512                .cloned()
513                .collect(),
514        );
515
516        let data_type1 = DataType::new("ExampleType", metadata1);
517        let data_type2 = DataType::new("ExampleType", metadata2);
518
519        assert_eq!(data_type1, data_type2);
520    }
521
522    #[rstest]
523    fn test_data_type_inequality() {
524        let metadata1 = Some(
525            [("key1".to_string(), "value1".to_string())]
526                .iter()
527                .cloned()
528                .collect(),
529        );
530        let metadata2 = Some(
531            [("key2".to_string(), "value2".to_string())]
532                .iter()
533                .cloned()
534                .collect(),
535        );
536
537        let data_type1 = DataType::new("ExampleType", metadata1);
538        let data_type2 = DataType::new("ExampleType", metadata2);
539
540        assert_ne!(data_type1, data_type2);
541    }
542
543    #[rstest]
544    fn test_data_type_ordering() {
545        let metadata1 = Some(
546            [("key1".to_string(), "value1".to_string())]
547                .iter()
548                .cloned()
549                .collect(),
550        );
551        let metadata2 = Some(
552            [("key2".to_string(), "value2".to_string())]
553                .iter()
554                .cloned()
555                .collect(),
556        );
557
558        let data_type1 = DataType::new("ExampleTypeA", metadata1);
559        let data_type2 = DataType::new("ExampleTypeB", metadata2);
560
561        assert!(data_type1 < data_type2);
562    }
563
564    #[rstest]
565    fn test_data_type_hash() {
566        let metadata = Some(
567            [("key1".to_string(), "value1".to_string())]
568                .iter()
569                .cloned()
570                .collect(),
571        );
572
573        let data_type1 = DataType::new("ExampleType", metadata.clone());
574        let data_type2 = DataType::new("ExampleType", metadata.clone());
575
576        let mut hasher1 = DefaultHasher::new();
577        data_type1.hash(&mut hasher1);
578        let hash1 = hasher1.finish();
579
580        let mut hasher2 = DefaultHasher::new();
581        data_type2.hash(&mut hasher2);
582        let hash2 = hasher2.finish();
583
584        assert_eq!(hash1, hash2);
585    }
586
587    #[test]
588    fn test_data_type_display() {
589        let metadata = Some(
590            [("key1".to_string(), "value1".to_string())]
591                .iter()
592                .cloned()
593                .collect(),
594        );
595        let data_type = DataType::new("ExampleType", metadata);
596
597        assert_eq!(format!("{}", data_type), "ExampleType.key1=value1");
598    }
599
600    #[test]
601    fn test_data_type_debug() {
602        let metadata = Some(
603            [("key1".to_string(), "value1".to_string())]
604                .iter()
605                .cloned()
606                .collect(),
607        );
608        let data_type = DataType::new("ExampleType", metadata.clone());
609
610        assert_eq!(
611            format!("{data_type:?}"),
612            format!("DataType(type_name=ExampleType, metadata={metadata:?})")
613        );
614    }
615
616    #[rstest]
617    fn test_parse_instrument_id_from_metadata() {
618        let instrument_id_str = "MSFT.XNAS";
619        let metadata = Some(
620            [("instrument_id".to_string(), instrument_id_str.to_string())]
621                .iter()
622                .cloned()
623                .collect(),
624        );
625        let data_type = DataType::new("InstrumentAny", metadata);
626
627        assert_eq!(
628            data_type.instrument_id().unwrap(),
629            InstrumentId::from_str(instrument_id_str).unwrap()
630        );
631    }
632
633    #[rstest]
634    fn test_parse_venue_from_metadata() {
635        let venue_str = "BINANCE";
636        let metadata = Some(
637            [("venue".to_string(), venue_str.to_string())]
638                .iter()
639                .cloned()
640                .collect(),
641        );
642        let data_type = DataType::new(stringify!(InstrumentAny), metadata);
643
644        assert_eq!(data_type.venue().unwrap(), Venue::new(venue_str));
645    }
646
647    #[rstest]
648    fn test_parse_bar_type_from_metadata() {
649        let bar_type_str = "MSFT.XNAS-1000-TICK-LAST-INTERNAL";
650        let metadata = Some(
651            [("bar_type".to_string(), bar_type_str.to_string())]
652                .iter()
653                .cloned()
654                .collect(),
655        );
656        let data_type = DataType::new(stringify!(BarType), metadata);
657
658        assert_eq!(
659            data_type.bar_type(),
660            BarType::from_str(bar_type_str).unwrap()
661        );
662    }
663
664    #[rstest]
665    fn test_parse_start_from_metadata() {
666        let start_ns = 1600054595844758000;
667        let metadata = Some(
668            [("start".to_string(), start_ns.to_string())]
669                .iter()
670                .cloned()
671                .collect(),
672        );
673        let data_type = DataType::new(stringify!(TradeTick), metadata);
674
675        assert_eq!(data_type.start().unwrap(), UnixNanos::from(start_ns),);
676    }
677
678    #[rstest]
679    fn test_parse_end_from_metadata() {
680        let end_ns = 1720954595844758000;
681        let metadata = Some(
682            [("end".to_string(), end_ns.to_string())]
683                .iter()
684                .cloned()
685                .collect(),
686        );
687        let data_type = DataType::new(stringify!(TradeTick), metadata);
688
689        assert_eq!(data_type.end().unwrap(), UnixNanos::from(end_ns),);
690    }
691
692    #[rstest]
693    fn test_parse_limit_from_metadata() {
694        let limit = 1000;
695        let metadata = Some(
696            [("limit".to_string(), limit.to_string())]
697                .iter()
698                .cloned()
699                .collect(),
700        );
701        let data_type = DataType::new(stringify!(TradeTick), metadata);
702
703        assert_eq!(data_type.limit().unwrap(), limit);
704    }
705
706    #[rstest]
707    fn test_parse_book_type_from_metadata() {
708        let book_type_str = "L3_MBO";
709        let metadata = Some(
710            [("book_type".to_string(), book_type_str.to_string())]
711                .iter()
712                .cloned()
713                .collect(),
714        );
715        let data_type = DataType::new(stringify!(OrderBookDelta), metadata);
716
717        assert_eq!(
718            data_type.book_type(),
719            BookType::from_str(book_type_str).unwrap()
720        );
721    }
722
723    #[rstest]
724    fn test_parse_depth_from_metadata() {
725        let depth = 25;
726        let metadata = Some(
727            [("depth".to_string(), depth.to_string())]
728                .iter()
729                .cloned()
730                .collect(),
731        );
732        let data_type = DataType::new(stringify!(OrderBookDeltas), metadata);
733
734        assert_eq!(data_type.depth().unwrap(), depth);
735    }
736}