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