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