nautilus_model/data/
mod.rs

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