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