nautilus_model/data/
mod.rs

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