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)]
245pub struct DataType {
246    type_name: String,
247    metadata: Option<IndexMap<String, String>>,
248    topic: String,
249    hash: u64,
250}
251
252impl DataType {
253    /// Creates a new [`DataType`] instance.
254    pub fn new(type_name: &str, metadata: Option<IndexMap<String, String>>) -> Self {
255        // Precompute topic
256        let topic = if let Some(ref meta) = metadata {
257            let meta_str = meta
258                .iter()
259                .map(|(k, v)| format!("{k}={v}"))
260                .collect::<Vec<_>>()
261                .join(".");
262            format!("{type_name}.{meta_str}")
263        } else {
264            type_name.to_string()
265        };
266
267        // Precompute hash
268        let mut hasher = std::collections::hash_map::DefaultHasher::new();
269        topic.hash(&mut hasher);
270
271        Self {
272            type_name: type_name.to_owned(),
273            metadata,
274            topic,
275            hash: hasher.finish(),
276        }
277    }
278
279    /// Returns the type name for the data type.
280    pub fn type_name(&self) -> &str {
281        self.type_name.as_str()
282    }
283
284    /// Returns the metadata for the data type.
285    pub fn metadata(&self) -> Option<&IndexMap<String, String>> {
286        self.metadata.as_ref()
287    }
288
289    /// Returns a string representation of the metadata.
290    pub fn metadata_str(&self) -> String {
291        self.metadata
292            .as_ref()
293            .map(|metadata| to_string(metadata).unwrap_or_default())
294            .unwrap_or_else(|| "null".to_string())
295    }
296
297    /// Returns the messaging topic for the data type.
298    pub fn topic(&self) -> &str {
299        self.topic.as_str()
300    }
301
302    /// Returns an [`Option<InstrumentId>`] parsed from the metadata.
303    ///
304    /// # Panics
305    ///
306    /// This function panics if:
307    /// - There is no metadata.
308    /// - The `instrument_id` value contained in the metadata is invalid.
309    pub fn instrument_id(&self) -> Option<InstrumentId> {
310        let metadata = self.metadata.as_ref().expect("metadata was `None`");
311        let instrument_id = metadata.get("instrument_id")?;
312        Some(
313            InstrumentId::from_str(instrument_id)
314                .expect("Invalid `InstrumentId` for 'instrument_id'"),
315        )
316    }
317
318    /// Returns an [`Option<Venue>`] parsed from the metadata.
319    ///
320    /// # Panics
321    ///
322    /// This function panics if:
323    /// - There is no metadata.
324    /// - The `venue` value contained in the metadata is invalid.
325    pub fn venue(&self) -> Option<Venue> {
326        let metadata = self.metadata.as_ref().expect("metadata was `None`");
327        let venue_str = metadata.get("venue")?;
328        Some(Venue::from(venue_str.as_str()))
329    }
330
331    /// Returns an [`Option<UnixNanos>`] parsed from the metadata `start` field.
332    ///
333    /// # Panics
334    ///
335    /// This function panics if:
336    /// - There is no metadata.
337    /// - The `start` value contained in the metadata is invalid.
338    pub fn start(&self) -> Option<UnixNanos> {
339        let metadata = self.metadata.as_ref()?;
340        let start_str = metadata.get("start")?;
341        Some(UnixNanos::from_str(start_str).expect("Invalid `UnixNanos` for 'start'"))
342    }
343
344    /// Returns an [`Option<UnixNanos>`] parsed from the metadata `end` field.
345    ///
346    /// # Panics
347    ///
348    /// This function panics if:
349    /// - There is no metadata.
350    /// - The `end` value contained in the metadata is invalid.
351    pub fn end(&self) -> Option<UnixNanos> {
352        let metadata = self.metadata.as_ref()?;
353        let end_str = metadata.get("end")?;
354        Some(UnixNanos::from_str(end_str).expect("Invalid `UnixNanos` for 'end'"))
355    }
356
357    /// Returns an [`Option<usize>`] parsed from the metadata `limit` field.
358    ///
359    /// # Panics
360    ///
361    /// This function panics if:
362    /// - There is no metadata.
363    /// - The `limit` value contained in the metadata is invalid.
364    pub fn limit(&self) -> Option<usize> {
365        let metadata = self.metadata.as_ref()?;
366        let depth_str = metadata.get("limit")?;
367        Some(
368            depth_str
369                .parse::<usize>()
370                .expect("Invalid `usize` for 'limit'"),
371        )
372    }
373}
374
375impl PartialEq for DataType {
376    fn eq(&self, other: &Self) -> bool {
377        self.topic == other.topic
378    }
379}
380
381impl Eq for DataType {}
382
383impl PartialOrd for DataType {
384    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
385        Some(self.cmp(other))
386    }
387}
388
389impl Ord for DataType {
390    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
391        self.topic.cmp(&other.topic)
392    }
393}
394
395impl Hash for DataType {
396    fn hash<H: Hasher>(&self, state: &mut H) {
397        self.hash.hash(state);
398    }
399}
400
401impl Display for DataType {
402    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403        write!(f, "{}", self.topic)
404    }
405}
406
407impl Debug for DataType {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        write!(
410            f,
411            "DataType(type_name={}, metadata={:?})",
412            self.type_name, self.metadata
413        )
414    }
415}
416
417////////////////////////////////////////////////////////////////////////////////
418// Tests
419////////////////////////////////////////////////////////////////////////////////
420#[cfg(test)]
421mod tests {
422    use std::hash::DefaultHasher;
423
424    use rstest::*;
425
426    use super::*;
427
428    #[rstest]
429    fn test_data_type_creation_with_metadata() {
430        let metadata = Some(
431            [
432                ("key1".to_string(), "value1".to_string()),
433                ("key2".to_string(), "value2".to_string()),
434            ]
435            .iter()
436            .cloned()
437            .collect(),
438        );
439        let data_type = DataType::new("ExampleType", metadata.clone());
440
441        assert_eq!(data_type.type_name(), "ExampleType");
442        assert_eq!(data_type.topic(), "ExampleType.key1=value1.key2=value2");
443        assert_eq!(data_type.metadata(), metadata.as_ref());
444    }
445
446    #[rstest]
447    fn test_data_type_creation_without_metadata() {
448        let data_type = DataType::new("ExampleType", None);
449
450        assert_eq!(data_type.type_name(), "ExampleType");
451        assert_eq!(data_type.topic(), "ExampleType");
452        assert_eq!(data_type.metadata(), None);
453    }
454
455    #[rstest]
456    fn test_data_type_equality() {
457        let metadata1 = Some(
458            [("key1".to_string(), "value1".to_string())]
459                .iter()
460                .cloned()
461                .collect(),
462        );
463        let metadata2 = Some(
464            [("key1".to_string(), "value1".to_string())]
465                .iter()
466                .cloned()
467                .collect(),
468        );
469
470        let data_type1 = DataType::new("ExampleType", metadata1);
471        let data_type2 = DataType::new("ExampleType", metadata2);
472
473        assert_eq!(data_type1, data_type2);
474    }
475
476    #[rstest]
477    fn test_data_type_inequality() {
478        let metadata1 = Some(
479            [("key1".to_string(), "value1".to_string())]
480                .iter()
481                .cloned()
482                .collect(),
483        );
484        let metadata2 = Some(
485            [("key2".to_string(), "value2".to_string())]
486                .iter()
487                .cloned()
488                .collect(),
489        );
490
491        let data_type1 = DataType::new("ExampleType", metadata1);
492        let data_type2 = DataType::new("ExampleType", metadata2);
493
494        assert_ne!(data_type1, data_type2);
495    }
496
497    #[rstest]
498    fn test_data_type_ordering() {
499        let metadata1 = Some(
500            [("key1".to_string(), "value1".to_string())]
501                .iter()
502                .cloned()
503                .collect(),
504        );
505        let metadata2 = Some(
506            [("key2".to_string(), "value2".to_string())]
507                .iter()
508                .cloned()
509                .collect(),
510        );
511
512        let data_type1 = DataType::new("ExampleTypeA", metadata1);
513        let data_type2 = DataType::new("ExampleTypeB", metadata2);
514
515        assert!(data_type1 < data_type2);
516    }
517
518    #[rstest]
519    fn test_data_type_hash() {
520        let metadata = Some(
521            [("key1".to_string(), "value1".to_string())]
522                .iter()
523                .cloned()
524                .collect(),
525        );
526
527        let data_type1 = DataType::new("ExampleType", metadata.clone());
528        let data_type2 = DataType::new("ExampleType", metadata.clone());
529
530        let mut hasher1 = DefaultHasher::new();
531        data_type1.hash(&mut hasher1);
532        let hash1 = hasher1.finish();
533
534        let mut hasher2 = DefaultHasher::new();
535        data_type2.hash(&mut hasher2);
536        let hash2 = hasher2.finish();
537
538        assert_eq!(hash1, hash2);
539    }
540
541    #[rstest]
542    fn test_data_type_display() {
543        let metadata = Some(
544            [("key1".to_string(), "value1".to_string())]
545                .iter()
546                .cloned()
547                .collect(),
548        );
549        let data_type = DataType::new("ExampleType", metadata);
550
551        assert_eq!(format!("{data_type}"), "ExampleType.key1=value1");
552    }
553
554    #[rstest]
555    fn test_data_type_debug() {
556        let metadata = Some(
557            [("key1".to_string(), "value1".to_string())]
558                .iter()
559                .cloned()
560                .collect(),
561        );
562        let data_type = DataType::new("ExampleType", metadata.clone());
563
564        assert_eq!(
565            format!("{data_type:?}"),
566            format!("DataType(type_name=ExampleType, metadata={metadata:?})")
567        );
568    }
569
570    #[rstest]
571    fn test_parse_instrument_id_from_metadata() {
572        let instrument_id_str = "MSFT.XNAS";
573        let metadata = Some(
574            [("instrument_id".to_string(), instrument_id_str.to_string())]
575                .iter()
576                .cloned()
577                .collect(),
578        );
579        let data_type = DataType::new("InstrumentAny", metadata);
580
581        assert_eq!(
582            data_type.instrument_id().unwrap(),
583            InstrumentId::from_str(instrument_id_str).unwrap()
584        );
585    }
586
587    #[rstest]
588    fn test_parse_venue_from_metadata() {
589        let venue_str = "BINANCE";
590        let metadata = Some(
591            [("venue".to_string(), venue_str.to_string())]
592                .iter()
593                .cloned()
594                .collect(),
595        );
596        let data_type = DataType::new(stringify!(InstrumentAny), metadata);
597
598        assert_eq!(data_type.venue().unwrap(), Venue::new(venue_str));
599    }
600
601    #[rstest]
602    fn test_parse_start_from_metadata() {
603        let start_ns = 1600054595844758000;
604        let metadata = Some(
605            [("start".to_string(), start_ns.to_string())]
606                .iter()
607                .cloned()
608                .collect(),
609        );
610        let data_type = DataType::new(stringify!(TradeTick), metadata);
611
612        assert_eq!(data_type.start().unwrap(), UnixNanos::from(start_ns),);
613    }
614
615    #[rstest]
616    fn test_parse_end_from_metadata() {
617        let end_ns = 1720954595844758000;
618        let metadata = Some(
619            [("end".to_string(), end_ns.to_string())]
620                .iter()
621                .cloned()
622                .collect(),
623        );
624        let data_type = DataType::new(stringify!(TradeTick), metadata);
625
626        assert_eq!(data_type.end().unwrap(), UnixNanos::from(end_ns),);
627    }
628
629    #[rstest]
630    fn test_parse_limit_from_metadata() {
631        let limit = 1000;
632        let metadata = Some(
633            [("limit".to_string(), limit.to_string())]
634                .iter()
635                .cloned()
636                .collect(),
637        );
638        let data_type = DataType::new(stringify!(TradeTick), metadata);
639
640        assert_eq!(data_type.limit().unwrap(), limit);
641    }
642}