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(any(test, feature = "stubs"))]
32pub mod stubs;
33
34use std::{
35    fmt::{Debug, Display},
36    hash::{Hash, Hasher},
37    str::FromStr,
38};
39
40use close::InstrumentClose;
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::{
53    BlackScholesGreeksResult, GreeksData, PortfolioGreeks, YieldCurveData, black_scholes_greeks,
54    imply_vol_and_greeks,
55};
56pub use order::{BookOrder, NULL_ORDER};
57pub use prices::{IndexPriceUpdate, MarkPriceUpdate};
58pub use quote::QuoteTick;
59pub use status::InstrumentStatus;
60pub use trade::TradeTick;
61
62use crate::identifiers::{InstrumentId, Venue};
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    MarkPriceUpdate(MarkPriceUpdate), // TODO: Rename to MarkPrice once Cython gone
78    IndexPriceUpdate(IndexPriceUpdate), // TODO: Rename to IndexPrice once Cython gone
79    InstrumentClose(InstrumentClose),
80}
81
82macro_rules! impl_try_from_data {
83    ($variant:ident, $type:ty) => {
84        impl TryFrom<Data> for $type {
85            type Error = ();
86
87            fn try_from(value: Data) -> Result<Self, Self::Error> {
88                match value {
89                    Data::$variant(x) => Ok(x),
90                    _ => Err(()),
91                }
92            }
93        }
94    };
95}
96
97impl TryFrom<Data> for OrderBookDepth10 {
98    type Error = ();
99
100    fn try_from(value: Data) -> Result<Self, Self::Error> {
101        match value {
102            Data::Depth10(x) => Ok(*x),
103            _ => Err(()),
104        }
105    }
106}
107
108impl_try_from_data!(Quote, QuoteTick);
109impl_try_from_data!(Delta, OrderBookDelta);
110impl_try_from_data!(Deltas, OrderBookDeltas_API);
111impl_try_from_data!(Trade, TradeTick);
112impl_try_from_data!(Bar, Bar);
113impl_try_from_data!(MarkPriceUpdate, MarkPriceUpdate);
114impl_try_from_data!(IndexPriceUpdate, IndexPriceUpdate);
115impl_try_from_data!(InstrumentClose, InstrumentClose);
116
117/// Converts a vector of `Data` items to a specific variant type.
118///
119/// Filters and converts the data vector, keeping only items that can be
120/// successfully converted to the target type `T`.
121pub fn to_variant<T: TryFrom<Data>>(data: Vec<Data>) -> Vec<T> {
122    data.into_iter()
123        .filter_map(|d| T::try_from(d).ok())
124        .collect()
125}
126
127impl Data {
128    /// Returns the instrument ID for the data.
129    pub fn instrument_id(&self) -> InstrumentId {
130        match self {
131            Self::Delta(delta) => delta.instrument_id,
132            Self::Deltas(deltas) => deltas.instrument_id,
133            Self::Depth10(depth) => depth.instrument_id,
134            Self::Quote(quote) => quote.instrument_id,
135            Self::Trade(trade) => trade.instrument_id,
136            Self::Bar(bar) => bar.bar_type.instrument_id(),
137            Self::MarkPriceUpdate(mark_price) => mark_price.instrument_id,
138            Self::IndexPriceUpdate(index_price) => index_price.instrument_id,
139            Self::InstrumentClose(close) => close.instrument_id,
140        }
141    }
142
143    /// Returns whether the data is a type of order book data.
144    pub fn is_order_book_data(&self) -> bool {
145        matches!(self, Self::Delta(_) | Self::Deltas(_) | Self::Depth10(_))
146    }
147}
148
149/// Marker trait for types that carry a creation timestamp.
150///
151/// `ts_init` is the moment (UNIX nanoseconds) when this value was first generated or
152/// ingested by Nautilus. It can be used for sequencing, latency measurements,
153/// or monitoring data-pipeline delays.
154pub trait HasTsInit {
155    /// Returns the UNIX timestamp (nanoseconds) when the instance was created.
156    fn ts_init(&self) -> UnixNanos;
157}
158
159impl HasTsInit for Data {
160    fn ts_init(&self) -> UnixNanos {
161        match self {
162            Self::Delta(d) => d.ts_init,
163            Self::Deltas(d) => d.ts_init,
164            Self::Depth10(d) => d.ts_init,
165            Self::Quote(q) => q.ts_init,
166            Self::Trade(t) => t.ts_init,
167            Self::Bar(b) => b.ts_init,
168            Self::MarkPriceUpdate(p) => p.ts_init,
169            Self::IndexPriceUpdate(p) => p.ts_init,
170            Self::InstrumentClose(c) => c.ts_init,
171        }
172    }
173}
174
175/// Checks if the data slice is monotonically increasing by initialization timestamp.
176///
177/// Returns `true` if each element's `ts_init` is less than or equal to the next element's `ts_init`.
178pub fn is_monotonically_increasing_by_init<T: HasTsInit>(data: &[T]) -> bool {
179    data.windows(2)
180        .all(|window| window[0].ts_init() <= window[1].ts_init())
181}
182
183impl From<OrderBookDelta> for Data {
184    fn from(value: OrderBookDelta) -> Self {
185        Self::Delta(value)
186    }
187}
188
189impl From<OrderBookDeltas_API> for Data {
190    fn from(value: OrderBookDeltas_API) -> Self {
191        Self::Deltas(value)
192    }
193}
194
195impl From<OrderBookDepth10> for Data {
196    fn from(value: OrderBookDepth10) -> Self {
197        Self::Depth10(Box::new(value))
198    }
199}
200
201impl From<QuoteTick> for Data {
202    fn from(value: QuoteTick) -> Self {
203        Self::Quote(value)
204    }
205}
206
207impl From<TradeTick> for Data {
208    fn from(value: TradeTick) -> Self {
209        Self::Trade(value)
210    }
211}
212
213impl From<Bar> for Data {
214    fn from(value: Bar) -> Self {
215        Self::Bar(value)
216    }
217}
218
219impl From<MarkPriceUpdate> for Data {
220    fn from(value: MarkPriceUpdate) -> Self {
221        Self::MarkPriceUpdate(value)
222    }
223}
224
225impl From<IndexPriceUpdate> for Data {
226    fn from(value: IndexPriceUpdate) -> Self {
227        Self::IndexPriceUpdate(value)
228    }
229}
230
231impl From<InstrumentClose> for Data {
232    fn from(value: InstrumentClose) -> Self {
233        Self::InstrumentClose(value)
234    }
235}
236
237/// Represents a data type including metadata.
238#[derive(Clone, Serialize, Deserialize)]
239#[cfg_attr(
240    feature = "python",
241    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
242)]
243pub struct DataType {
244    type_name: String,
245    metadata: Option<IndexMap<String, String>>,
246    topic: String,
247    hash: u64,
248}
249
250impl DataType {
251    /// Creates a new [`DataType`] instance.
252    pub fn new(type_name: &str, metadata: Option<IndexMap<String, String>>) -> Self {
253        // Precompute topic
254        let topic = if let Some(ref meta) = metadata {
255            let meta_str = meta
256                .iter()
257                .map(|(k, v)| format!("{k}={v}"))
258                .collect::<Vec<_>>()
259                .join(".");
260            format!("{type_name}.{meta_str}")
261        } else {
262            type_name.to_string()
263        };
264
265        // Precompute hash
266        let mut hasher = std::collections::hash_map::DefaultHasher::new();
267        topic.hash(&mut hasher);
268
269        Self {
270            type_name: type_name.to_owned(),
271            metadata,
272            topic,
273            hash: hasher.finish(),
274        }
275    }
276
277    /// Returns the type name for the data type.
278    pub fn type_name(&self) -> &str {
279        self.type_name.as_str()
280    }
281
282    /// Returns the metadata for the data type.
283    pub fn metadata(&self) -> Option<&IndexMap<String, String>> {
284        self.metadata.as_ref()
285    }
286
287    /// Returns a string representation of the metadata.
288    pub fn metadata_str(&self) -> String {
289        self.metadata
290            .as_ref()
291            .map(|metadata| to_string(metadata).unwrap_or_default())
292            .unwrap_or_else(|| "null".to_string())
293    }
294
295    /// Returns the messaging topic for the data type.
296    pub fn topic(&self) -> &str {
297        self.topic.as_str()
298    }
299
300    /// Returns an [`Option<InstrumentId>`] parsed from the metadata.
301    ///
302    /// # Panics
303    ///
304    /// This function panics if:
305    /// - There is no metadata.
306    /// - The `instrument_id` value contained in the metadata is invalid.
307    pub fn instrument_id(&self) -> Option<InstrumentId> {
308        let metadata = self.metadata.as_ref().expect("metadata was `None`");
309        let instrument_id = metadata.get("instrument_id")?;
310        Some(
311            InstrumentId::from_str(instrument_id)
312                .expect("Invalid `InstrumentId` for 'instrument_id'"),
313        )
314    }
315
316    /// Returns an [`Option<Venue>`] parsed from the metadata.
317    ///
318    /// # Panics
319    ///
320    /// This function panics if:
321    /// - There is no metadata.
322    /// - The `venue` value contained in the metadata is invalid.
323    pub fn venue(&self) -> Option<Venue> {
324        let metadata = self.metadata.as_ref().expect("metadata was `None`");
325        let venue_str = metadata.get("venue")?;
326        Some(Venue::from(venue_str.as_str()))
327    }
328
329    /// Returns an [`Option<UnixNanos>`] parsed from the metadata `start` field.
330    ///
331    /// # Panics
332    ///
333    /// This function panics if:
334    /// - There is no metadata.
335    /// - The `start` value contained in the metadata is invalid.
336    pub fn start(&self) -> Option<UnixNanos> {
337        let metadata = self.metadata.as_ref()?;
338        let start_str = metadata.get("start")?;
339        Some(UnixNanos::from_str(start_str).expect("Invalid `UnixNanos` for 'start'"))
340    }
341
342    /// Returns an [`Option<UnixNanos>`] parsed from the metadata `end` field.
343    ///
344    /// # Panics
345    ///
346    /// This function panics if:
347    /// - There is no metadata.
348    /// - The `end` value contained in the metadata is invalid.
349    pub fn end(&self) -> Option<UnixNanos> {
350        let metadata = self.metadata.as_ref()?;
351        let end_str = metadata.get("end")?;
352        Some(UnixNanos::from_str(end_str).expect("Invalid `UnixNanos` for 'end'"))
353    }
354
355    /// Returns an [`Option<usize>`] parsed from the metadata `limit` field.
356    ///
357    /// # Panics
358    ///
359    /// This function panics if:
360    /// - There is no metadata.
361    /// - The `limit` value contained in the metadata is invalid.
362    pub fn limit(&self) -> Option<usize> {
363        let metadata = self.metadata.as_ref()?;
364        let depth_str = metadata.get("limit")?;
365        Some(
366            depth_str
367                .parse::<usize>()
368                .expect("Invalid `usize` for 'limit'"),
369        )
370    }
371}
372
373impl PartialEq for DataType {
374    fn eq(&self, other: &Self) -> bool {
375        self.topic == other.topic
376    }
377}
378
379impl Eq for DataType {}
380
381impl PartialOrd for DataType {
382    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
383        Some(self.cmp(other))
384    }
385}
386
387impl Ord for DataType {
388    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
389        self.topic.cmp(&other.topic)
390    }
391}
392
393impl Hash for DataType {
394    fn hash<H: Hasher>(&self, state: &mut H) {
395        self.hash.hash(state);
396    }
397}
398
399impl Display for DataType {
400    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401        write!(f, "{}", self.topic)
402    }
403}
404
405impl Debug for DataType {
406    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407        write!(
408            f,
409            "DataType(type_name={}, metadata={:?})",
410            self.type_name, self.metadata
411        )
412    }
413}
414
415////////////////////////////////////////////////////////////////////////////////
416// Tests
417////////////////////////////////////////////////////////////////////////////////
418#[cfg(test)]
419mod tests {
420    use std::hash::DefaultHasher;
421
422    use rstest::*;
423
424    use super::*;
425
426    #[rstest]
427    fn test_data_type_creation_with_metadata() {
428        let metadata = Some(
429            [
430                ("key1".to_string(), "value1".to_string()),
431                ("key2".to_string(), "value2".to_string()),
432            ]
433            .iter()
434            .cloned()
435            .collect(),
436        );
437        let data_type = DataType::new("ExampleType", metadata.clone());
438
439        assert_eq!(data_type.type_name(), "ExampleType");
440        assert_eq!(data_type.topic(), "ExampleType.key1=value1.key2=value2");
441        assert_eq!(data_type.metadata(), metadata.as_ref());
442    }
443
444    #[rstest]
445    fn test_data_type_creation_without_metadata() {
446        let data_type = DataType::new("ExampleType", None);
447
448        assert_eq!(data_type.type_name(), "ExampleType");
449        assert_eq!(data_type.topic(), "ExampleType");
450        assert_eq!(data_type.metadata(), None);
451    }
452
453    #[rstest]
454    fn test_data_type_equality() {
455        let metadata1 = Some(
456            [("key1".to_string(), "value1".to_string())]
457                .iter()
458                .cloned()
459                .collect(),
460        );
461        let metadata2 = Some(
462            [("key1".to_string(), "value1".to_string())]
463                .iter()
464                .cloned()
465                .collect(),
466        );
467
468        let data_type1 = DataType::new("ExampleType", metadata1);
469        let data_type2 = DataType::new("ExampleType", metadata2);
470
471        assert_eq!(data_type1, data_type2);
472    }
473
474    #[rstest]
475    fn test_data_type_inequality() {
476        let metadata1 = Some(
477            [("key1".to_string(), "value1".to_string())]
478                .iter()
479                .cloned()
480                .collect(),
481        );
482        let metadata2 = Some(
483            [("key2".to_string(), "value2".to_string())]
484                .iter()
485                .cloned()
486                .collect(),
487        );
488
489        let data_type1 = DataType::new("ExampleType", metadata1);
490        let data_type2 = DataType::new("ExampleType", metadata2);
491
492        assert_ne!(data_type1, data_type2);
493    }
494
495    #[rstest]
496    fn test_data_type_ordering() {
497        let metadata1 = Some(
498            [("key1".to_string(), "value1".to_string())]
499                .iter()
500                .cloned()
501                .collect(),
502        );
503        let metadata2 = Some(
504            [("key2".to_string(), "value2".to_string())]
505                .iter()
506                .cloned()
507                .collect(),
508        );
509
510        let data_type1 = DataType::new("ExampleTypeA", metadata1);
511        let data_type2 = DataType::new("ExampleTypeB", metadata2);
512
513        assert!(data_type1 < data_type2);
514    }
515
516    #[rstest]
517    fn test_data_type_hash() {
518        let metadata = Some(
519            [("key1".to_string(), "value1".to_string())]
520                .iter()
521                .cloned()
522                .collect(),
523        );
524
525        let data_type1 = DataType::new("ExampleType", metadata.clone());
526        let data_type2 = DataType::new("ExampleType", metadata.clone());
527
528        let mut hasher1 = DefaultHasher::new();
529        data_type1.hash(&mut hasher1);
530        let hash1 = hasher1.finish();
531
532        let mut hasher2 = DefaultHasher::new();
533        data_type2.hash(&mut hasher2);
534        let hash2 = hasher2.finish();
535
536        assert_eq!(hash1, hash2);
537    }
538
539    #[rstest]
540    fn test_data_type_display() {
541        let metadata = Some(
542            [("key1".to_string(), "value1".to_string())]
543                .iter()
544                .cloned()
545                .collect(),
546        );
547        let data_type = DataType::new("ExampleType", metadata);
548
549        assert_eq!(format!("{data_type}"), "ExampleType.key1=value1");
550    }
551
552    #[rstest]
553    fn test_data_type_debug() {
554        let metadata = Some(
555            [("key1".to_string(), "value1".to_string())]
556                .iter()
557                .cloned()
558                .collect(),
559        );
560        let data_type = DataType::new("ExampleType", metadata.clone());
561
562        assert_eq!(
563            format!("{data_type:?}"),
564            format!("DataType(type_name=ExampleType, metadata={metadata:?})")
565        );
566    }
567
568    #[rstest]
569    fn test_parse_instrument_id_from_metadata() {
570        let instrument_id_str = "MSFT.XNAS";
571        let metadata = Some(
572            [("instrument_id".to_string(), instrument_id_str.to_string())]
573                .iter()
574                .cloned()
575                .collect(),
576        );
577        let data_type = DataType::new("InstrumentAny", metadata);
578
579        assert_eq!(
580            data_type.instrument_id().unwrap(),
581            InstrumentId::from_str(instrument_id_str).unwrap()
582        );
583    }
584
585    #[rstest]
586    fn test_parse_venue_from_metadata() {
587        let venue_str = "BINANCE";
588        let metadata = Some(
589            [("venue".to_string(), venue_str.to_string())]
590                .iter()
591                .cloned()
592                .collect(),
593        );
594        let data_type = DataType::new(stringify!(InstrumentAny), metadata);
595
596        assert_eq!(data_type.venue().unwrap(), Venue::new(venue_str));
597    }
598
599    #[rstest]
600    fn test_parse_start_from_metadata() {
601        let start_ns = 1600054595844758000;
602        let metadata = Some(
603            [("start".to_string(), start_ns.to_string())]
604                .iter()
605                .cloned()
606                .collect(),
607        );
608        let data_type = DataType::new(stringify!(TradeTick), metadata);
609
610        assert_eq!(data_type.start().unwrap(), UnixNanos::from(start_ns),);
611    }
612
613    #[rstest]
614    fn test_parse_end_from_metadata() {
615        let end_ns = 1720954595844758000;
616        let metadata = Some(
617            [("end".to_string(), end_ns.to_string())]
618                .iter()
619                .cloned()
620                .collect(),
621        );
622        let data_type = DataType::new(stringify!(TradeTick), metadata);
623
624        assert_eq!(data_type.end().unwrap(), UnixNanos::from(end_ns),);
625    }
626
627    #[rstest]
628    fn test_parse_limit_from_metadata() {
629        let limit = 1000;
630        let metadata = Some(
631            [("limit".to_string(), limit.to_string())]
632                .iter()
633                .cloned()
634                .collect(),
635        );
636        let data_type = DataType::new(stringify!(TradeTick), metadata);
637
638        assert_eq!(data_type.limit().unwrap(), limit);
639    }
640}