dbn/
enums.rs

1#![allow(deprecated)] // TODO: remove with SType::Smart
2
3//! Enums used in Databento APIs.
4
5use std::fmt::{self, Display, Formatter};
6
7// Dummy derive macro to get around `cfg_attr` incompatibility of several
8// of pyo3's attribute macros. See https://github.com/PyO3/pyo3/issues/780
9#[cfg(not(feature = "python"))]
10use dbn_macros::MockPyo3;
11use num_enum::{IntoPrimitive, TryFromPrimitive};
12
13/// A [side](https://databento.com/docs/standards-and-conventions/common-fields-enums-types)
14/// of the market. The side of the market for resting orders, or the side of the
15/// aggressor for trades.
16#[derive(
17    Debug,
18    Clone,
19    Copy,
20    PartialEq,
21    Eq,
22    PartialOrd,
23    Ord,
24    Hash,
25    Default,
26    TryFromPrimitive,
27    IntoPrimitive,
28)]
29#[cfg_attr(
30    feature = "python",
31    derive(strum::EnumIter, strum::AsRefStr),
32    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
33)]
34#[repr(u8)]
35pub enum Side {
36    /// A sell order or sell aggressor in a trade.
37    Ask = b'A',
38    /// A buy order or a buy aggressor in a trade.
39    Bid = b'B',
40    /// No side specified by the original source.
41    #[default]
42    None = b'N',
43}
44
45impl From<Side> for char {
46    fn from(side: Side) -> Self {
47        u8::from(side) as char
48    }
49}
50
51/// An [order event or order book operation](https://databento.com/docs/api-reference-historical/basics/schemas-and-conventions).
52///
53/// For example usage see:
54/// - [Order actions](https://databento.com/docs/examples/order-book/order-actions)
55/// - [Order tracking](https://databento.com/docs/examples/order-book/order-tracking)
56#[derive(
57    Debug,
58    Clone,
59    Copy,
60    PartialEq,
61    Eq,
62    PartialOrd,
63    Ord,
64    Hash,
65    Default,
66    TryFromPrimitive,
67    IntoPrimitive,
68)]
69#[cfg_attr(
70    feature = "python",
71    derive(strum::EnumIter, strum::AsRefStr),
72    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
73)]
74#[repr(u8)]
75pub enum Action {
76    /// An existing order was modified: price and/or size.
77    Modify = b'M',
78    /// An aggressing order traded. Does not affect the book.
79    Trade = b'T',
80    /// An existing order was filled. Does not affect the book.
81    Fill = b'F',
82    /// An order was fully or partially cancelled.
83    Cancel = b'C',
84    /// A new order was added to the book.
85    Add = b'A',
86    /// Reset the book; clear all orders for an instrument.
87    Clear = b'R',
88    /// Has no effect on the book, but may carry `flags` or other information.
89    #[default]
90    None = b'N',
91}
92
93impl From<Action> for char {
94    fn from(action: Action) -> Self {
95        u8::from(action) as char
96    }
97}
98
99/// The class of instrument.
100///
101/// For example usage see
102/// [Getting options with their underlying](https://databento.com/docs/examples/options/options-and-futures).
103#[derive(
104    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, TryFromPrimitive, IntoPrimitive,
105)]
106#[cfg_attr(
107    feature = "python",
108    derive(strum::EnumIter),
109    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
110)]
111#[non_exhaustive]
112#[repr(u8)]
113pub enum InstrumentClass {
114    /// A bond.
115    Bond = b'B',
116    /// A call option.
117    Call = b'C',
118    /// A future.
119    Future = b'F',
120    /// A stock.
121    Stock = b'K',
122    /// A spread composed of multiple instrument classes.
123    MixedSpread = b'M',
124    /// A put option.
125    Put = b'P',
126    /// A spread composed of futures.
127    FutureSpread = b'S',
128    /// A spread composed of options.
129    OptionSpread = b'T',
130    /// A foreign exchange spot.
131    FxSpot = b'X',
132    /// A commodity being traded for immediate delivery.
133    CommoditySpot = b'Y',
134}
135
136impl From<InstrumentClass> for char {
137    fn from(class: InstrumentClass) -> Self {
138        u8::from(class) as char
139    }
140}
141
142impl InstrumentClass {
143    /// Returns `true` if the instrument class is a type of option.
144    ///
145    /// Note: excludes [`Self::MixedSpread`], which *may* include options.
146    pub fn is_option(&self) -> bool {
147        matches!(self, Self::Call | Self::Put | Self::OptionSpread)
148    }
149
150    /// Returns `true` if the instrument class is a type of future.
151    ///
152    /// Note: excludes [`Self::MixedSpread`], which *may* include futures.
153    pub fn is_future(&self) -> bool {
154        matches!(self, Self::Future | Self::FutureSpread)
155    }
156
157    /// Returns `true` if the instrument class is a type of spread, i.e. composed of two
158    /// or more instrument legs.
159    pub fn is_spread(&self) -> bool {
160        matches!(
161            self,
162            Self::FutureSpread | Self::OptionSpread | Self::MixedSpread
163        )
164    }
165}
166
167/// The type of matching algorithm used for the instrument at the exchange.
168#[derive(
169    Debug,
170    Clone,
171    Copy,
172    Default,
173    PartialEq,
174    Eq,
175    PartialOrd,
176    Ord,
177    Hash,
178    TryFromPrimitive,
179    IntoPrimitive,
180)]
181#[cfg_attr(
182    feature = "python",
183    derive(strum::EnumIter),
184    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
185)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
187#[repr(u8)]
188pub enum MatchAlgorithm {
189    /// No matching algorithm was specified.
190    #[default]
191    Undefined = b' ',
192    /// First-in-first-out matching.
193    Fifo = b'F',
194    /// A configurable match algorithm.
195    Configurable = b'K',
196    /// Trade quantity is allocated to resting orders based on a pro-rata percentage:
197    /// resting order quantity divided by total quantity.
198    ProRata = b'C',
199    /// Like [`Self::Fifo`] but with LMM allocations prior to FIFO allocations.
200    FifoLmm = b'T',
201    /// Like [`Self::ProRata`] but includes a configurable allocation to the first order that
202    /// improves the market.
203    ThresholdProRata = b'O',
204    /// Like [`Self::FifoLmm`] but includes a configurable allocation to the first order that
205    /// improves the market.
206    FifoTopLmm = b'S',
207    /// Like [`Self::ThresholdProRata`] but includes a special priority to LMMs.
208    ThresholdProRataLmm = b'Q',
209    /// Special variant used only for Eurodollar futures on CME.
210    EurodollarFutures = b'Y',
211    /// Trade quantity is shared between all orders at the best price. Orders with the
212    /// highest time priority receive a higher matched quantity.
213    TimeProRata = b'P',
214    /// A two-pass FIFO algorithm. The first pass fills the Institutional Group the aggressing
215    /// order is associated with. The second pass matches orders without an Institutional Group
216    /// association. See [CME documentation](https://cmegroupclientsite.atlassian.net/wiki/spaces/EPICSANDBOX/pages/457217267#InstitutionalPrioritizationMatchAlgorithm).
217    InstitutionalPrioritization = b'V',
218}
219
220impl From<MatchAlgorithm> for char {
221    fn from(algo: MatchAlgorithm) -> Self {
222        u8::from(algo) as char
223    }
224}
225
226/// Whether the instrument is user-defined.
227///
228/// For example usage see
229/// [Getting options with their underlying](https://databento.com/docs/examples/options/options-and-futures).
230#[derive(
231    Debug,
232    Clone,
233    Copy,
234    PartialEq,
235    Eq,
236    PartialOrd,
237    Ord,
238    Hash,
239    TryFromPrimitive,
240    IntoPrimitive,
241    Default,
242)]
243#[cfg_attr(
244    feature = "python",
245    derive(strum::EnumIter, strum::AsRefStr),
246    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
247)]
248#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
249#[repr(u8)]
250pub enum UserDefinedInstrument {
251    /// The instrument is not user-defined.
252    #[default]
253    No = b'N',
254    /// The instrument is user-defined.
255    Yes = b'Y',
256}
257
258impl From<UserDefinedInstrument> for char {
259    fn from(user_defined_instrument: UserDefinedInstrument) -> Self {
260        u8::from(user_defined_instrument) as char
261    }
262}
263
264/// A symbology type. Refer to the
265/// [symbology documentation](https://databento.com/docs/api-reference-historical/basics/symbology)
266/// for more information.
267#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, TryFromPrimitive)]
268#[cfg_attr(
269    feature = "python",
270    derive(strum::EnumIter),
271    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
272)]
273#[repr(u8)]
274pub enum SType {
275    /// Symbology using a unique numeric ID.
276    InstrumentId = 0,
277    /// Symbology using the original symbols provided by the publisher.
278    RawSymbol = 1,
279    /// A set of Databento-specific symbologies for referring to groups of symbols.
280    #[deprecated(since = "0.5.0", note = "Smart was split into Continuous and Parent.")]
281    Smart = 2,
282    /// A Databento-specific symbology where one symbol may point to different
283    /// instruments at different points of time, e.g. to always refer to the front month
284    /// future.
285    Continuous = 3,
286    /// A Databento-specific symbology for referring to a group of symbols by one
287    /// "parent" symbol, e.g. ES.FUT to refer to all ES futures.
288    Parent = 4,
289    /// Symbology for US equities using NASDAQ Integrated suffix conventions.
290    NasdaqSymbol = 5,
291    /// Symbology for US equities using CMS suffix conventions.
292    CmsSymbol = 6,
293    /// Symbology using International Security Identification Numbers (ISIN) - ISO 6166.
294    Isin = 7,
295    /// Symbology using US domestic Committee on Uniform Securities Identification Procedure (CUSIP) codes.
296    UsCode = 8,
297    /// Symbology using Bloomberg composite global IDs.
298    BbgCompId = 9,
299    /// Symbology using Bloomberg composite tickers.
300    BbgCompTicker = 10,
301    /// Symbology using Bloomberg FIGI exchange level IDs.
302    Figi = 11,
303    /// Symbology using Bloomberg exchange level tickers.
304    FigiTicker = 12,
305}
306
307impl std::str::FromStr for SType {
308    type Err = crate::Error;
309
310    fn from_str(s: &str) -> Result<Self, Self::Err> {
311        match s {
312            "instrument_id" | "product_id" => Ok(SType::InstrumentId),
313            "raw_symbol" | "native" => Ok(SType::RawSymbol),
314            "smart" => Ok(SType::Smart),
315            "continuous" => Ok(SType::Continuous),
316            "parent" => Ok(SType::Parent),
317            "nasdaq_symbol" | "nasdaq" => Ok(SType::NasdaqSymbol),
318            "cms_symbol" | "cms" => Ok(SType::CmsSymbol),
319            "isin" => Ok(SType::Isin),
320            "us_code" => Ok(SType::UsCode),
321            "bbg_comp_id" => Ok(SType::BbgCompId),
322            "bbg_comp_ticker" => Ok(SType::BbgCompTicker),
323            "figi" => Ok(SType::Figi),
324            "figi_ticker" => Ok(SType::FigiTicker),
325            _ => Err(crate::Error::conversion::<Self>(s.to_owned())),
326        }
327    }
328}
329
330impl AsRef<str> for SType {
331    fn as_ref(&self) -> &str {
332        self.as_str()
333    }
334}
335
336impl SType {
337    /// Convert the symbology type to its `str` representation.
338    pub const fn as_str(&self) -> &'static str {
339        match self {
340            SType::InstrumentId => "instrument_id",
341            SType::RawSymbol => "raw_symbol",
342            #[allow(deprecated)]
343            SType::Smart => "smart",
344            SType::Continuous => "continuous",
345            SType::Parent => "parent",
346            SType::NasdaqSymbol => "nasdaq_symbol",
347            SType::CmsSymbol => "cms_symbol",
348            SType::Isin => "isin",
349            SType::UsCode => "us_code",
350            SType::BbgCompId => "bbg_comp_id",
351            SType::BbgCompTicker => "bbg_comp_ticker",
352            SType::Figi => "figi",
353            SType::FigiTicker => "figi_ticker",
354        }
355    }
356}
357
358impl Display for SType {
359    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
360        f.write_str(self.as_str())
361    }
362}
363
364pub use rtype::RType;
365
366/// Record types, possible values for [`RecordHeader::rtype`][crate::RecordHeader::rtype]
367#[allow(deprecated)]
368pub mod rtype {
369    #[cfg(not(feature = "python"))]
370    use dbn_macros::MockPyo3;
371    use num_enum::TryFromPrimitive;
372
373    use super::Schema;
374
375    /// A [record type](https://databento.com/docs/standards-and-conventions/common-fields-enums-types),
376    /// i.e. a sentinel for different types implementing [`HasRType`](crate::record::HasRType).
377    ///
378    /// Use in [`RecordHeader`](crate::RecordHeader) to indicate the type of record,
379    /// which is useful when working with DBN streams containing multiple record types
380    /// or an unknown record type.
381    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, TryFromPrimitive)]
382    #[cfg_attr(
383        feature = "python",
384        derive(strum::EnumIter),
385        pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
386    )]
387    #[cfg_attr(not(feature = "python"), derive(MockPyo3))]
388    #[repr(u8)]
389    pub enum RType {
390        /// Denotes a market-by-price record with a book depth of 0 (used for the
391        /// [`Trades`](super::Schema::Trades) schema).
392        #[pyo3(name = "MBP_0")]
393        Mbp0 = 0,
394        /// Denotes a market-by-price record with a book depth of 1 (also used for the
395        /// [`Tbbo`](super::Schema::Tbbo) schema).
396        #[pyo3(name = "MBP_1")]
397        Mbp1 = 0x01,
398        /// Denotes a market-by-price record with a book depth of 10.
399        #[pyo3(name = "MBP_10")]
400        Mbp10 = 0x0A,
401        /// Denotes an open, high, low, close, and volume record at an unspecified cadence.
402        #[deprecated(
403            since = "0.3.3",
404            note = "Separated into separate rtypes for each OHLCV schema."
405        )]
406        OhlcvDeprecated = 0x11,
407        /// Denotes an open, high, low, close, and volume record at a 1-second cadence.
408        #[pyo3(name = "OHLCV_1S")]
409        Ohlcv1S = 0x20,
410        /// Denotes an open, high, low, close, and volume record at a 1-minute cadence.
411        #[pyo3(name = "OHLCV_1M")]
412        Ohlcv1M = 0x21,
413        /// Denotes an open, high, low, close, and volume record at an hourly cadence.
414        #[pyo3(name = "OHLCV_1H")]
415        Ohlcv1H = 0x22,
416        /// Denotes an open, high, low, close, and volume record at a daily cadence
417        /// based on the UTC date.
418        #[pyo3(name = "OHLCV_1D")]
419        Ohlcv1D = 0x23,
420        /// Denotes an open, high, low, close, and volume record at a daily cadence
421        /// based on the end of the trading session.
422        OhlcvEod = 0x24,
423        /// Denotes an exchange status record.
424        Status = 0x12,
425        /// Denotes an instrument definition record.
426        InstrumentDef = 0x13,
427        /// Denotes an order imbalance record.
428        Imbalance = 0x14,
429        /// Denotes an error from gateway.
430        Error = 0x15,
431        /// Denotes a symbol mapping record.
432        SymbolMapping = 0x16,
433        /// Denotes a non-error message from the gateway. Also used for heartbeats.
434        System = 0x17,
435        /// Denotes a statistics record from the publisher (not calculated by Databento).
436        Statistics = 0x18,
437        /// Denotes a market by order record.
438        Mbo = 0xA0,
439        /// Denotes a consolidated best bid and offer record.
440        #[pyo3(name = "CMBP_1")]
441        Cmbp1 = 0xB1,
442        /// Denotes a consolidated best bid and offer record subsampled on a one-second
443        /// interval.
444        #[pyo3(name = "CBBO_1S")]
445        Cbbo1S = 0xC0,
446        /// Denotes a consolidated best bid and offer record subsampled on a one-minute
447        /// interval.
448        #[pyo3(name = "CBBO_1M")]
449        Cbbo1M = 0xC1,
450        /// Denotes a consolidated best bid and offer trade record containing the
451        /// consolidated BBO before the trade.
452        Tcbbo = 0xC2,
453        /// Denotes a best bid and offer record subsampled on a one-second interval.
454        #[pyo3(name = "BBO_1S")]
455        Bbo1S = 0xC3,
456        /// Denotes a best bid and offer record subsampled on a one-minute interval.
457        #[pyo3(name = "BBO_1M")]
458        Bbo1M = 0xC4,
459    }
460
461    /// Denotes a market-by-price record with a book depth of 0 (used for the
462    /// [`Trades`](super::Schema::Trades) schema).
463    pub const MBP_0: u8 = RType::Mbp0 as u8;
464    /// Denotes a market-by-price record with a book depth of 1 (also used for the
465    /// [`Tbbo`](super::Schema::Tbbo) schema).
466    pub const MBP_1: u8 = RType::Mbp1 as u8;
467    /// Denotes a market-by-price record with a book depth of 10.
468    pub const MBP_10: u8 = RType::Mbp10 as u8;
469    /// Denotes an open, high, low, close, and volume record at an unspecified cadence.
470    #[deprecated(
471        since = "0.3.3",
472        note = "Separated into separate rtypes for each OHLCV schema."
473    )]
474    pub const OHLCV_DEPRECATED: u8 = RType::OhlcvDeprecated as u8;
475    /// Denotes an open, high, low, close, and volume record at a 1-second cadence.
476    pub const OHLCV_1S: u8 = RType::Ohlcv1S as u8;
477    /// Denotes an open, high, low, close, and volume record at a 1-minute cadence.
478    pub const OHLCV_1M: u8 = RType::Ohlcv1M as u8;
479    /// Denotes an open, high, low, close, and volume record at an hourly cadence.
480    pub const OHLCV_1H: u8 = RType::Ohlcv1H as u8;
481    /// Denotes an open, high, low, close, and volume record at a daily cadence based
482    /// on the UTC date.
483    pub const OHLCV_1D: u8 = RType::Ohlcv1D as u8;
484    /// Denotes an open, high, low, close, and volume record at a daily cadence
485    /// based on the end of the trading session.
486    pub const OHLCV_EOD: u8 = RType::OhlcvEod as u8;
487    /// Denotes an exchange status record.
488    pub const STATUS: u8 = RType::Status as u8;
489    /// Denotes an instrument definition record.
490    pub const INSTRUMENT_DEF: u8 = RType::InstrumentDef as u8;
491    /// Denotes an order imbalance record.
492    pub const IMBALANCE: u8 = RType::Imbalance as u8;
493    /// Denotes an error from gateway.
494    pub const ERROR: u8 = RType::Error as u8;
495    /// Denotes a symbol mapping record.
496    pub const SYMBOL_MAPPING: u8 = RType::SymbolMapping as u8;
497    /// Denotes a non-error message from the gateway. Also used for heartbeats.
498    pub const SYSTEM: u8 = RType::System as u8;
499    /// Denotes a statistics record from the publisher (not calculated by Databento).
500    pub const STATISTICS: u8 = RType::Statistics as u8;
501    /// Denotes a market-by-order record.
502    pub const MBO: u8 = RType::Mbo as u8;
503    /// Denotes a consolidated best bid and offer record.
504    pub const CMBP_1: u8 = RType::Cmbp1 as u8;
505    /// Denotes a consolidated best bid and offer record subsampled on a one-second interval.
506    pub const CBBO_1S: u8 = RType::Cbbo1S as u8;
507    /// Denotes a consolidated best bid and offer record subsampled on a one-minute interval.
508    pub const CBBO_1M: u8 = RType::Cbbo1M as u8;
509    /// Denotes a consolidated best bid and offer trade record containing the consolidated BBO before the trade.
510    pub const TCBBO: u8 = RType::Tcbbo as u8;
511    /// Denotes a best bid and offer record subsampled on a one-second interval.
512    pub const BBO_1S: u8 = RType::Bbo1S as u8;
513    /// Denotes a best bid and offer record subsampled on a one-minute interval.
514    pub const BBO_1M: u8 = RType::Bbo1M as u8;
515
516    /// Get the corresponding `rtype` for the given `schema`.
517    impl From<Schema> for RType {
518        fn from(schema: Schema) -> Self {
519            match schema {
520                Schema::Mbo => RType::Mbo,
521                Schema::Mbp1 | Schema::Tbbo => RType::Mbp1,
522                Schema::Mbp10 => RType::Mbp10,
523                Schema::Trades => RType::Mbp0,
524                Schema::Ohlcv1S => RType::Ohlcv1S,
525                Schema::Ohlcv1M => RType::Ohlcv1M,
526                Schema::Ohlcv1H => RType::Ohlcv1H,
527                Schema::Ohlcv1D => RType::Ohlcv1D,
528                Schema::OhlcvEod => RType::OhlcvEod,
529                Schema::Definition => RType::InstrumentDef,
530                Schema::Statistics => RType::Statistics,
531                Schema::Status => RType::Status,
532                Schema::Imbalance => RType::Imbalance,
533                Schema::Cmbp1 => RType::Cmbp1,
534                Schema::Cbbo1S => RType::Cbbo1S,
535                Schema::Cbbo1M => RType::Cbbo1M,
536                Schema::Tcbbo => RType::Tcbbo,
537                Schema::Bbo1S => RType::Bbo1S,
538                Schema::Bbo1M => RType::Bbo1M,
539            }
540        }
541    }
542
543    /// Tries to convert the given rtype to a [`Schema`].
544    ///
545    /// Returns `None` if there's no corresponding `Schema` for the given rtype or
546    /// in the case of [`OHLCV_DEPRECATED`], it doesn't map to a single `Schema`.
547    pub fn try_into_schema(rtype: u8) -> Option<Schema> {
548        match rtype {
549            MBP_0 => Some(Schema::Trades),
550            MBP_1 => Some(Schema::Mbp1),
551            MBP_10 => Some(Schema::Mbp10),
552            OHLCV_1S => Some(Schema::Ohlcv1S),
553            OHLCV_1M => Some(Schema::Ohlcv1M),
554            OHLCV_1H => Some(Schema::Ohlcv1H),
555            OHLCV_1D => Some(Schema::Ohlcv1D),
556            OHLCV_EOD => Some(Schema::OhlcvEod),
557            STATUS => Some(Schema::Status),
558            INSTRUMENT_DEF => Some(Schema::Definition),
559            IMBALANCE => Some(Schema::Imbalance),
560            STATISTICS => Some(Schema::Statistics),
561            MBO => Some(Schema::Mbo),
562            CMBP_1 => Some(Schema::Cmbp1),
563            CBBO_1S => Some(Schema::Cbbo1S),
564            CBBO_1M => Some(Schema::Cbbo1M),
565            TCBBO => Some(Schema::Tcbbo),
566            BBO_1S => Some(Schema::Bbo1S),
567            BBO_1M => Some(Schema::Bbo1M),
568            _ => None,
569        }
570    }
571
572    impl std::str::FromStr for RType {
573        type Err = crate::Error;
574
575        fn from_str(s: &str) -> Result<Self, Self::Err> {
576            match s {
577                "mbp-0" => Ok(RType::Mbp0),
578                "mbp-1" => Ok(RType::Mbp1),
579                "mbp-10" => Ok(RType::Mbp10),
580                "ohlcv-deprecated" => Ok(RType::OhlcvDeprecated),
581                "ohlcv-1s" => Ok(RType::Ohlcv1S),
582                "ohlcv-1m" => Ok(RType::Ohlcv1M),
583                "ohlcv-1h" => Ok(RType::Ohlcv1H),
584                "ohlcv-1d" => Ok(RType::Ohlcv1D),
585                "ohlcv-eod" => Ok(RType::OhlcvEod),
586                "status" => Ok(RType::Status),
587                "instrument-def" => Ok(RType::InstrumentDef),
588                "imbalance" => Ok(RType::Imbalance),
589                "error" => Ok(RType::Error),
590                "symbol-mapping" => Ok(RType::SymbolMapping),
591                "system" => Ok(RType::System),
592                "statistics" => Ok(RType::Statistics),
593                "mbo" => Ok(RType::Mbo),
594                "cmbp-1" => Ok(RType::Cmbp1),
595                "cbbo-1s" => Ok(RType::Cbbo1S),
596                "cbbo-1m" => Ok(RType::Cbbo1M),
597                "tcbbo" => Ok(RType::Tcbbo),
598                "bbo-1s" => Ok(RType::Bbo1S),
599                "bbo-1m" => Ok(RType::Bbo1M),
600                _ => Err(crate::Error::conversion::<Self>(s.to_owned())),
601            }
602        }
603    }
604
605    impl RType {
606        /// Convert the RType type to its `str` representation.
607        pub const fn as_str(&self) -> &'static str {
608            match self {
609                RType::Mbp0 => "mbp-0",
610                RType::Mbp1 => "mbp-1",
611                RType::Mbp10 => "mbp-10",
612                RType::OhlcvDeprecated => "ohlcv-deprecated",
613                RType::Ohlcv1S => "ohlcv-1s",
614                RType::Ohlcv1M => "ohlcv-1m",
615                RType::Ohlcv1H => "ohlcv-1h",
616                RType::Ohlcv1D => "ohlcv-1d",
617                RType::OhlcvEod => "ohlcv-eod",
618                RType::Status => "status",
619                RType::InstrumentDef => "instrument-def",
620                RType::Imbalance => "imbalance",
621                RType::Error => "error",
622                RType::SymbolMapping => "symbol-mapping",
623                RType::System => "system",
624                RType::Statistics => "statistics",
625                RType::Mbo => "mbo",
626                RType::Cmbp1 => "cmbp-1",
627                RType::Cbbo1S => "cbbo-1s",
628                RType::Cbbo1M => "cbbo-1m",
629                RType::Tcbbo => "tcbbo",
630                RType::Bbo1S => "bbo-1s",
631                RType::Bbo1M => "bbo-1m",
632            }
633        }
634    }
635}
636
637/// A data record schema.
638///
639/// Each schema has a particular [record](crate::record) type associated with it.
640///
641/// See [List of supported market data schemas](https://databento.com/docs/schemas-and-data-formats/whats-a-schema)
642/// for an overview of the differences and use cases of each schema.
643#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, TryFromPrimitive)]
644#[cfg_attr(
645    feature = "python",
646    derive(strum::EnumIter),
647    pyo3::pyclass(module = "databento_dbn")
648)]
649#[cfg_attr(not(feature = "python"), derive(MockPyo3))]
650#[cfg_attr(test, derive(strum::EnumCount))]
651#[repr(u16)]
652pub enum Schema {
653    /// Market by order.
654    #[pyo3(name = "MBO")]
655    Mbo = 0,
656    /// Market by price with a book depth of 1.
657    #[pyo3(name = "MBP_1")]
658    Mbp1 = 1,
659    /// Market by price with a book depth of 10.
660    #[pyo3(name = "MBP_10")]
661    Mbp10 = 2,
662    /// All trade events with the best bid and offer (BBO) immediately **before** the
663    /// effect of the trade.
664    #[pyo3(name = "TBBO")]
665    Tbbo = 3,
666    /// All trade events.
667    #[pyo3(name = "TRADES")]
668    Trades = 4,
669    /// Open, high, low, close, and volume at a one-second interval.
670    #[pyo3(name = "OHLCV_1S")]
671    Ohlcv1S = 5,
672    /// Open, high, low, close, and volume at a one-minute interval.
673    #[pyo3(name = "OHLCV_1M")]
674    Ohlcv1M = 6,
675    /// Open, high, low, close, and volume at an hourly interval.
676    #[pyo3(name = "OHLCV_1H")]
677    Ohlcv1H = 7,
678    /// Open, high, low, close, and volume at a daily interval based on the UTC date.
679    #[pyo3(name = "OHLCV_1D")]
680    Ohlcv1D = 8,
681    /// Instrument definitions.
682    #[pyo3(name = "DEFINITION")]
683    Definition = 9,
684    /// Additional data disseminated by publishers.
685    #[pyo3(name = "STATISTICS")]
686    Statistics = 10,
687    /// Trading status events.
688    #[pyo3(name = "STATUS")]
689    Status = 11,
690    /// Auction imbalance events.
691    #[pyo3(name = "IMBALANCE")]
692    Imbalance = 12,
693    /// Open, high, low, close, and volume at a daily cadence based on the end of the
694    /// trading session.
695    #[pyo3(name = "OHLCV_EOD")]
696    OhlcvEod = 13,
697    /// Consolidated best bid and offer.
698    #[pyo3(name = "CMBP_1")]
699    Cmbp1 = 14,
700    /// Consolidated best bid and offer subsampled at one-second intervals, in addition
701    /// to trades.
702    #[pyo3(name = "CBBO_1S")]
703    Cbbo1S = 15,
704    /// Consolidated best bid and offer subsampled at one-minute intervals, in addition
705    /// to trades.
706    #[pyo3(name = "CBBO_1M")]
707    Cbbo1M = 16,
708    /// All trade events with the consolidated best bid and offer (CBBO) immediately
709    /// **before** the effect of the trade.
710    #[pyo3(name = "TCBBO")]
711    Tcbbo = 17,
712    /// Best bid and offer subsampled at one-second intervals, in addition to trades.
713    #[pyo3(name = "BBO_1S")]
714    Bbo1S = 18,
715    /// Best bid and offer subsampled at one-minute intervals, in addition to trades.
716    #[pyo3(name = "BBO_1M")]
717    Bbo1M = 19,
718}
719
720/// The number of [`Schema`]s.
721pub const SCHEMA_COUNT: usize = 20;
722
723impl std::str::FromStr for Schema {
724    type Err = crate::Error;
725
726    fn from_str(s: &str) -> Result<Self, Self::Err> {
727        match s {
728            "mbo" => Ok(Schema::Mbo),
729            "mbp-1" => Ok(Schema::Mbp1),
730            "mbp-10" => Ok(Schema::Mbp10),
731            "tbbo" => Ok(Schema::Tbbo),
732            "trades" => Ok(Schema::Trades),
733            "ohlcv-1s" => Ok(Schema::Ohlcv1S),
734            "ohlcv-1m" => Ok(Schema::Ohlcv1M),
735            "ohlcv-1h" => Ok(Schema::Ohlcv1H),
736            "ohlcv-1d" => Ok(Schema::Ohlcv1D),
737            "ohlcv-eod" => Ok(Schema::OhlcvEod),
738            "definition" => Ok(Schema::Definition),
739            "statistics" => Ok(Schema::Statistics),
740            "status" => Ok(Schema::Status),
741            "imbalance" => Ok(Schema::Imbalance),
742            "cmbp-1" => Ok(Schema::Cmbp1),
743            "cbbo-1s" => Ok(Schema::Cbbo1S),
744            "cbbo-1m" => Ok(Schema::Cbbo1M),
745            "tcbbo" => Ok(Schema::Tcbbo),
746            "bbo-1s" => Ok(Schema::Bbo1S),
747            "bbo-1m" => Ok(Schema::Bbo1M),
748            _ => Err(crate::Error::conversion::<Self>(s.to_owned())),
749        }
750    }
751}
752
753impl AsRef<str> for Schema {
754    fn as_ref(&self) -> &str {
755        self.as_str()
756    }
757}
758
759impl Schema {
760    /// Converts the given schema to a `&'static str`.
761    pub const fn as_str(&self) -> &'static str {
762        match self {
763            Schema::Mbo => "mbo",
764            Schema::Mbp1 => "mbp-1",
765            Schema::Mbp10 => "mbp-10",
766            Schema::Tbbo => "tbbo",
767            Schema::Trades => "trades",
768            Schema::Ohlcv1S => "ohlcv-1s",
769            Schema::Ohlcv1M => "ohlcv-1m",
770            Schema::Ohlcv1H => "ohlcv-1h",
771            Schema::Ohlcv1D => "ohlcv-1d",
772            Schema::OhlcvEod => "ohlcv-eod",
773            Schema::Definition => "definition",
774            Schema::Statistics => "statistics",
775            Schema::Status => "status",
776            Schema::Imbalance => "imbalance",
777            Schema::Cmbp1 => "cmbp-1",
778            Schema::Cbbo1S => "cbbo-1s",
779            Schema::Cbbo1M => "cbbo-1m",
780            Schema::Tcbbo => "tcbbo",
781            Schema::Bbo1S => "bbo-1s",
782            Schema::Bbo1M => "bbo-1m",
783        }
784    }
785}
786
787impl Display for Schema {
788    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
789        f.write_str(self.as_str())
790    }
791}
792
793/// A data encoding format.
794#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, TryFromPrimitive)]
795#[cfg_attr(
796    feature = "python",
797    derive(strum::EnumIter),
798    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
799)]
800#[repr(u8)]
801pub enum Encoding {
802    /// Databento Binary Encoding.
803    Dbn = 0,
804    /// Comma-separated values.
805    Csv = 1,
806    /// JavaScript object notation.
807    Json = 2,
808}
809
810impl std::str::FromStr for Encoding {
811    type Err = crate::Error;
812
813    fn from_str(s: &str) -> Result<Self, Self::Err> {
814        match s {
815            "dbn" | "dbz" => Ok(Encoding::Dbn),
816            "csv" => Ok(Encoding::Csv),
817            "json" => Ok(Encoding::Json),
818            _ => Err(crate::Error::conversion::<Self>(s.to_owned())),
819        }
820    }
821}
822
823impl AsRef<str> for Encoding {
824    fn as_ref(&self) -> &str {
825        self.as_str()
826    }
827}
828
829impl Encoding {
830    /// Converts the given encoding to a `&'static str`.
831    pub const fn as_str(&self) -> &'static str {
832        match self {
833            Encoding::Dbn => "dbn",
834            Encoding::Csv => "csv",
835            Encoding::Json => "json",
836        }
837    }
838}
839
840impl Display for Encoding {
841    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
842        f.write_str(self.as_str())
843    }
844}
845
846/// A compression format or none if uncompressed.
847#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, TryFromPrimitive)]
848#[cfg_attr(
849    feature = "python",
850    derive(strum::EnumIter),
851    pyo3::pyclass(module = "databento_dbn")
852)]
853#[cfg_attr(not(feature = "python"), derive(MockPyo3))]
854#[repr(u8)]
855pub enum Compression {
856    /// Uncompressed.
857    #[pyo3(name = "NONE")]
858    None = 0,
859    /// Zstandard compressed.
860    #[pyo3(name = "ZSTD")]
861    ZStd = 1,
862}
863
864impl std::str::FromStr for Compression {
865    type Err = crate::Error;
866
867    fn from_str(s: &str) -> Result<Self, Self::Err> {
868        match s {
869            "none" => Ok(Compression::None),
870            "zstd" => Ok(Compression::ZStd),
871            _ => Err(crate::Error::conversion::<Self>(s.to_owned())),
872        }
873    }
874}
875
876impl AsRef<str> for Compression {
877    fn as_ref(&self) -> &str {
878        self.as_str()
879    }
880}
881
882impl Compression {
883    /// Converts the given compression to a `&'static str`.
884    pub const fn as_str(&self) -> &'static str {
885        match self {
886            Compression::None => "none",
887            Compression::ZStd => "zstd",
888        }
889    }
890}
891
892impl Display for Compression {
893    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
894        f.write_str(self.as_str())
895    }
896}
897
898/// Constants for the bit flag record fields.
899pub mod flags {
900    /// Indicates it's the last message in the packet from the venue for a given
901    /// `instrument_id`.
902    pub const LAST: u8 = 1 << 7;
903    /// Indicates a top-of-book message, not an individual order.
904    pub const TOB: u8 = 1 << 6;
905    /// Indicates the message was sourced from a replay, such as a snapshot server.
906    pub const SNAPSHOT: u8 = 1 << 5;
907    /// Indicates an aggregated price level message, not an individual order.
908    pub const MBP: u8 = 1 << 4;
909    /// Indicates the `ts_recv` value is inaccurate due to clock issues or packet
910    /// reordering.
911    pub const BAD_TS_RECV: u8 = 1 << 3;
912    /// Indicates an unrecoverable gap was detected in the channel.
913    pub const MAYBE_BAD_BOOK: u8 = 1 << 2;
914}
915
916/// The type of [`InstrumentDefMsg`](crate::record::InstrumentDefMsg) update.
917#[allow(clippy::manual_non_exhaustive)] // false positive
918#[derive(
919    Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, TryFromPrimitive,
920)]
921#[cfg_attr(
922    feature = "python",
923    derive(strum::EnumIter, strum::AsRefStr),
924    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
925)]
926#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
927#[repr(u8)]
928pub enum SecurityUpdateAction {
929    /// A new instrument definition.
930    Add = b'A',
931    /// A modified instrument definition of an existing one.
932    Modify = b'M',
933    /// Removal of an instrument definition.
934    Delete = b'D',
935    #[doc(hidden)]
936    #[deprecated = "Still present in legacy files."]
937    Invalid = b'~',
938}
939
940/// The type of statistic contained in a [`StatMsg`](crate::record::StatMsg).
941#[derive(
942    Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, TryFromPrimitive,
943)]
944#[cfg_attr(
945    feature = "python",
946    derive(strum::EnumIter),
947    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
948)]
949#[non_exhaustive]
950#[repr(u16)]
951pub enum StatType {
952    /// The price of the first trade of an instrument. `price` will be set.
953    /// `quantity` will be set when provided by the venue.
954    OpeningPrice = 1,
955    /// The probable price of the first trade of an instrument published during pre-
956    /// open. Both `price` and `quantity` will be set.
957    IndicativeOpeningPrice = 2,
958    /// The settlement price of an instrument. `price` will be set and `flags` indicate
959    /// whether the price is final or preliminary and actual or theoretical. `ts_ref`
960    /// will indicate the trading date of the settlement price.
961    SettlementPrice = 3,
962    /// The lowest trade price of an instrument during the trading session. `price` will
963    /// be set.
964    TradingSessionLowPrice = 4,
965    /// The highest trade price of an instrument during the trading session. `price` will
966    /// be set.
967    TradingSessionHighPrice = 5,
968    /// The number of contracts cleared for an instrument on the previous trading date.
969    /// `quantity` will be set. `ts_ref` will indicate the trading date of the volume.
970    ClearedVolume = 6,
971    /// The lowest offer price for an instrument during the trading session. `price`
972    /// will be set.
973    LowestOffer = 7,
974    /// The highest bid price for an instrument during the trading session. `price`
975    /// will be set.
976    HighestBid = 8,
977    /// The current number of outstanding contracts of an instrument. `quantity` will
978    /// be set. `ts_ref` will indicate the trading date for which the open interest was
979    /// calculated.
980    OpenInterest = 9,
981    /// The volume-weighted average price (VWAP) for a fixing period. `price` will be
982    /// set.
983    FixingPrice = 10,
984    /// The last trade price during a trading session. `price` will be set.
985    /// `quantity` will be set when provided by the venue.
986    ClosePrice = 11,
987    /// The change in price from the close price of the previous trading session to the
988    /// most recent trading session. `price` will be set.
989    NetChange = 12,
990    /// The volume-weighted average price (VWAP) during the trading session.
991    /// `price` will be set to the VWAP while `quantity` will be the traded
992    /// volume.
993    Vwap = 13,
994    /// The implied volatility associated with the settlement price. `price` will be set
995    /// with the standard precision.
996    Volatility = 14,
997    /// The option delta associated with the settlement price. `price` will be set with
998    /// the standard precision.
999    Delta = 15,
1000    /// The auction uncrossing price. This is used for auctions that are neither the
1001    /// official opening auction nor the official closing auction. `price` will be set.
1002    /// `quantity` will be set when provided by the venue.
1003    UncrossingPrice = 16,
1004}
1005
1006/// The type of [`StatMsg`](crate::record::StatMsg) update.
1007#[derive(
1008    Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, TryFromPrimitive,
1009)]
1010#[cfg_attr(
1011    feature = "python",
1012    derive(strum::EnumIter),
1013    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
1014)]
1015#[repr(u8)]
1016pub enum StatUpdateAction {
1017    /// A new statistic.
1018    New = 1,
1019    /// A removal of a statistic.
1020    Delete = 2,
1021}
1022
1023/// The primary enum for the type of [`StatusMsg`](crate::record::StatusMsg) update.
1024#[derive(
1025    Clone,
1026    Copy,
1027    Debug,
1028    PartialEq,
1029    Eq,
1030    PartialOrd,
1031    Ord,
1032    Hash,
1033    IntoPrimitive,
1034    TryFromPrimitive,
1035    Default,
1036)]
1037#[cfg_attr(
1038    feature = "python",
1039    derive(strum::EnumIter),
1040    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
1041)]
1042#[non_exhaustive]
1043#[repr(u16)]
1044pub enum StatusAction {
1045    /// No change.
1046    #[default]
1047    None = 0,
1048    /// The instrument is in a pre-open period.
1049    PreOpen = 1,
1050    /// The instrument is in a pre-cross period.
1051    PreCross = 2,
1052    /// The instrument is quoting but not trading.
1053    Quoting = 3,
1054    /// The instrument is in a cross/auction.
1055    Cross = 4,
1056    /// The instrument is being opened through a trading rotation.
1057    Rotation = 5,
1058    /// A new price indication is available for the instrument.
1059    NewPriceIndication = 6,
1060    /// The instrument is trading.
1061    Trading = 7,
1062    /// Trading in the instrument has been halted.
1063    Halt = 8,
1064    /// Trading in the instrument has been paused.
1065    Pause = 9,
1066    /// Trading in the instrument has been suspended.
1067    Suspend = 10,
1068    /// The instrument is in a pre-close period.
1069    PreClose = 11,
1070    /// Trading in the instrument has closed.
1071    Close = 12,
1072    /// The instrument is in a post-close period.
1073    PostClose = 13,
1074    /// A change in short-selling restrictions.
1075    SsrChange = 14,
1076    /// The instrument is not available for trading, either trading has closed or been
1077    /// halted.
1078    NotAvailableForTrading = 15,
1079}
1080
1081/// The secondary enum for a [`StatusMsg`](crate::record::StatusMsg) update, explains
1082/// the cause of a halt or other change in `action`.
1083#[derive(
1084    Clone,
1085    Copy,
1086    Debug,
1087    PartialEq,
1088    Eq,
1089    PartialOrd,
1090    Ord,
1091    Hash,
1092    IntoPrimitive,
1093    TryFromPrimitive,
1094    Default,
1095)]
1096#[cfg_attr(
1097    feature = "python",
1098    derive(strum::EnumIter),
1099    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
1100)]
1101#[non_exhaustive]
1102#[repr(u16)]
1103pub enum StatusReason {
1104    /// No reason is given.
1105    #[default]
1106    None = 0,
1107    /// The change in status occurred as scheduled.
1108    Scheduled = 1,
1109    /// The instrument stopped due to a market surveillance intervention.
1110    SurveillanceIntervention = 2,
1111    /// The status changed due to activity in the market.
1112    MarketEvent = 3,
1113    /// The derivative instrument began trading.
1114    InstrumentActivation = 4,
1115    /// The derivative instrument expired.
1116    InstrumentExpiration = 5,
1117    /// Recovery in progress.
1118    RecoveryInProcess = 6,
1119    /// The status change was caused by a regulatory action.
1120    Regulatory = 10,
1121    /// The status change was caused by an administrative action.
1122    Administrative = 11,
1123    /// The status change was caused by the issuer not being compliance with regulatory
1124    /// requirements.
1125    NonCompliance = 12,
1126    /// Trading halted because the issuer's filings are not current.
1127    FilingsNotCurrent = 13,
1128    /// Trading halted due to an SEC trading suspension.
1129    SecTradingSuspension = 14,
1130    /// The status changed because a new issue is available.
1131    NewIssue = 15,
1132    /// The status changed because an issue is available.
1133    IssueAvailable = 16,
1134    /// The status changed because the issue(s) were reviewed.
1135    IssuesReviewed = 17,
1136    /// The status changed because the filing requirements were satisfied.
1137    FilingReqsSatisfied = 18,
1138    /// Relevant news is pending.
1139    NewsPending = 30,
1140    /// Relevant news was released.
1141    NewsReleased = 31,
1142    /// The news has been fully disseminated and times are available for the resumption
1143    /// in quoting and trading.
1144    NewsAndResumptionTimes = 32,
1145    /// The relevant news was not forthcoming.
1146    NewsNotForthcoming = 33,
1147    /// Halted for order imbalance.
1148    OrderImbalance = 40,
1149    /// The instrument hit limit up or limit down.
1150    LuldPause = 50,
1151    /// An operational issue occurred with the venue.
1152    Operational = 60,
1153    /// The status changed until the exchange receives additional information.
1154    AdditionalInformationRequested = 70,
1155    /// Trading halted due to merger becoming effective.
1156    MergerEffective = 80,
1157    /// Trading is halted in an ETF due to conditions with the component securities.
1158    Etf = 90,
1159    /// Trading is halted for a corporate action.
1160    CorporateAction = 100,
1161    /// Trading is halted because the instrument is a new offering.
1162    NewSecurityOffering = 110,
1163    /// Halted due to the market-wide circuit breaker level 1.
1164    MarketWideHaltLevel1 = 120,
1165    /// Halted due to the market-wide circuit breaker level 2.
1166    MarketWideHaltLevel2 = 121,
1167    /// Halted due to the market-wide circuit breaker level 3.
1168    MarketWideHaltLevel3 = 122,
1169    /// Halted due to the carryover of a market-wide circuit breaker from the previous
1170    /// trading day.
1171    MarketWideHaltCarryover = 123,
1172    /// Resumption due to the end of a market-wide circuit breaker halt.
1173    MarketWideHaltResumption = 124,
1174    /// Halted because quotation is not available.
1175    QuotationNotAvailable = 130,
1176}
1177
1178/// Further information about a status update.
1179#[derive(
1180    Clone,
1181    Copy,
1182    Debug,
1183    PartialEq,
1184    Eq,
1185    PartialOrd,
1186    Ord,
1187    Hash,
1188    IntoPrimitive,
1189    TryFromPrimitive,
1190    Default,
1191)]
1192#[cfg_attr(
1193    feature = "python",
1194    derive(strum::EnumIter),
1195    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
1196)]
1197#[non_exhaustive]
1198#[repr(u16)]
1199pub enum TradingEvent {
1200    /// No additional information given.
1201    #[default]
1202    None = 0,
1203    /// Order entry and modification are not allowed.
1204    NoCancel = 1,
1205    /// A change of trading session occurred. Daily statistics are reset.
1206    ChangeTradingSession = 2,
1207    /// Implied matching is available.
1208    ImpliedMatchingOn = 3,
1209    /// Implied matching is not available.
1210    ImpliedMatchingOff = 4,
1211}
1212
1213/// An enum for representing unknown, true, or false values. Equivalent to
1214/// `Option<bool>` but with a human-readable repr.
1215#[derive(
1216    Clone,
1217    Copy,
1218    Debug,
1219    PartialEq,
1220    Eq,
1221    PartialOrd,
1222    Ord,
1223    Hash,
1224    IntoPrimitive,
1225    TryFromPrimitive,
1226    Default,
1227)]
1228#[cfg_attr(
1229    feature = "python",
1230    derive(strum::EnumIter),
1231    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
1232)]
1233#[repr(u8)]
1234pub enum TriState {
1235    /// The value is not applicable or not known.
1236    #[default]
1237    NotAvailable = b'~',
1238    /// False
1239    No = b'N',
1240    /// True
1241    Yes = b'Y',
1242}
1243
1244impl From<TriState> for Option<bool> {
1245    fn from(value: TriState) -> Self {
1246        match value {
1247            TriState::NotAvailable => None,
1248            TriState::No => Some(false),
1249            TriState::Yes => Some(true),
1250        }
1251    }
1252}
1253
1254impl From<Option<bool>> for TriState {
1255    fn from(value: Option<bool>) -> Self {
1256        match value {
1257            Some(true) => Self::Yes,
1258            Some(false) => Self::No,
1259            None => Self::NotAvailable,
1260        }
1261    }
1262}
1263
1264/// How to handle decoding DBN data from other versions.
1265#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
1266#[cfg_attr(
1267    feature = "python",
1268    derive(strum::EnumIter),
1269    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
1270)]
1271#[non_exhaustive]
1272pub enum VersionUpgradePolicy {
1273    /// Decode data from all supported versions (less than or equal to
1274    /// [`DBN_VERSION`](crate::DBN_VERSION)) as-is.
1275    AsIs,
1276    /// Decode and convert data from DBN versions prior to version 2 to that version.
1277    /// Attempting to decode data from newer versions will fail.
1278    UpgradeToV2,
1279    /// Decode and convert data from DBN versions prior to version 3 to that version.
1280    /// Attempting to decode data from newer versions (when they're introduced) will
1281    /// fail.
1282    #[default]
1283    UpgradeToV3,
1284}
1285
1286impl VersionUpgradePolicy {
1287    /// Validates a given DBN `version` is compatible with the upgrade policy.
1288    ///
1289    /// # Errors
1290    /// This function returns an error if the version and upgrade policy are
1291    /// incompatible.
1292    pub fn validate_compatibility(self, version: u8) -> crate::Result<()> {
1293        if version > 2 && self == VersionUpgradePolicy::UpgradeToV2 {
1294            Err(crate::Error::decode("Invalid combination of `VersionUpgradePolicy::UpgradeToV2` and input version 3. Choose either `AsIs` and `UpgradeToV3` as an upgrade policy"))
1295        } else {
1296            Ok(())
1297        }
1298    }
1299
1300    pub(crate) fn is_upgrade_situation(self, version: u8) -> bool {
1301        match (self, version) {
1302            (VersionUpgradePolicy::AsIs, _) => false,
1303            (VersionUpgradePolicy::UpgradeToV2, v) if v < 2 => true,
1304            (VersionUpgradePolicy::UpgradeToV2, _) => false,
1305            (VersionUpgradePolicy::UpgradeToV3, v) if v < 3 => true,
1306            (VersionUpgradePolicy::UpgradeToV3, _) => false,
1307        }
1308    }
1309
1310    /// Returns the output DBN version given the input version and upgrade policy.
1311    pub fn output_version(self, input_version: u8) -> u8 {
1312        match self {
1313            VersionUpgradePolicy::AsIs => input_version,
1314            VersionUpgradePolicy::UpgradeToV2 => 2,
1315            VersionUpgradePolicy::UpgradeToV3 => 3,
1316        }
1317    }
1318}
1319
1320/// An error code from the live subscription gateway.
1321#[derive(
1322    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, TryFromPrimitive,
1323)]
1324#[cfg_attr(
1325    feature = "python",
1326    derive(strum::EnumIter),
1327    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
1328)]
1329#[non_exhaustive]
1330#[repr(u8)]
1331pub enum ErrorCode {
1332    /// The authentication step failed.
1333    AuthFailed = 1,
1334    /// The user account or API key were deactivated.
1335    ApiKeyDeactivated = 2,
1336    /// The user has exceeded their open connection limit
1337    ConnectionLimitExceeded = 3,
1338    /// One or more symbols failed to resolve.
1339    SymbolResolutionFailed = 4,
1340    /// There was an issue with a subscription request (other than symbol resolution).
1341    InvalidSubscription = 5,
1342    /// An error occurred in the gateway.
1343    InternalError = 6,
1344}
1345
1346impl AsRef<str> for ErrorCode {
1347    fn as_ref(&self) -> &str {
1348        self.as_str()
1349    }
1350}
1351
1352impl ErrorCode {
1353    /// Converts the given error code to a `&'static str`.
1354    pub const fn as_str(&self) -> &'static str {
1355        match self {
1356            ErrorCode::AuthFailed => "auth_failed",
1357            ErrorCode::ApiKeyDeactivated => "api_key_deactivated",
1358            ErrorCode::ConnectionLimitExceeded => "connection_limit_exceeded",
1359            ErrorCode::SymbolResolutionFailed => "symbol_resolution_failed",
1360            ErrorCode::InvalidSubscription => "invalid_subscription",
1361            ErrorCode::InternalError => "internal_error",
1362        }
1363    }
1364}
1365
1366impl Display for ErrorCode {
1367    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1368        f.write_str(self.as_str())
1369    }
1370}
1371
1372/// A [`SystemMsg`](crate::SystemMsg) code indicating the type of message from the live
1373/// subscription gateway.
1374#[derive(
1375    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, TryFromPrimitive,
1376)]
1377#[cfg_attr(
1378    feature = "python",
1379    derive(strum::EnumIter),
1380    pyo3::pyclass(module = "databento_dbn", rename_all = "SCREAMING_SNAKE_CASE")
1381)]
1382#[non_exhaustive]
1383#[repr(u8)]
1384pub enum SystemCode {
1385    /// A message sent in the absence of other records to indicate the connection
1386    /// remains open.
1387    Heartbeat = 0,
1388    /// An acknowledgement of a subscription request.
1389    SubscriptionAck = 1,
1390    /// The gateway has detected this session is falling behind real-time.
1391    SlowReaderWarning = 2,
1392    /// Indicates a replay subscription has caught up with real-time data.
1393    ReplayCompleted = 3,
1394}
1395
1396impl AsRef<str> for SystemCode {
1397    fn as_ref(&self) -> &str {
1398        self.as_str()
1399    }
1400}
1401
1402impl SystemCode {
1403    /// Converts the given system code to a `&'static str`.
1404    pub const fn as_str(&self) -> &'static str {
1405        match self {
1406            SystemCode::Heartbeat => "heartbeat",
1407            SystemCode::SubscriptionAck => "subscription_ack",
1408            SystemCode::SlowReaderWarning => "slow_reader_warning",
1409            SystemCode::ReplayCompleted => "replay_completed",
1410        }
1411    }
1412}
1413
1414impl Display for SystemCode {
1415    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1416        f.write_str(self.as_str())
1417    }
1418}
1419
1420#[cfg(feature = "serde")]
1421mod deserialize {
1422    use std::str::FromStr;
1423
1424    use serde::{de, Deserialize, Deserializer, Serialize};
1425
1426    use super::*;
1427
1428    impl<'de> Deserialize<'de> for Compression {
1429        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1430            let str = String::deserialize(deserializer)?;
1431            FromStr::from_str(&str).map_err(de::Error::custom)
1432        }
1433    }
1434
1435    impl Serialize for Compression {
1436        fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1437        where
1438            S: serde::Serializer,
1439        {
1440            self.as_str().serialize(serializer)
1441        }
1442    }
1443
1444    impl<'de> Deserialize<'de> for SType {
1445        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1446            let str = String::deserialize(deserializer)?;
1447            FromStr::from_str(&str).map_err(de::Error::custom)
1448        }
1449    }
1450
1451    impl Serialize for SType {
1452        fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1453        where
1454            S: serde::Serializer,
1455        {
1456            self.as_str().serialize(serializer)
1457        }
1458    }
1459
1460    impl<'de> Deserialize<'de> for Schema {
1461        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1462            let str = String::deserialize(deserializer)?;
1463            FromStr::from_str(&str).map_err(de::Error::custom)
1464        }
1465    }
1466
1467    impl Serialize for Schema {
1468        fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1469        where
1470            S: serde::Serializer,
1471        {
1472            self.as_str().serialize(serializer)
1473        }
1474    }
1475
1476    impl<'de> Deserialize<'de> for Encoding {
1477        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1478            let str = String::deserialize(deserializer)?;
1479            FromStr::from_str(&str).map_err(de::Error::custom)
1480        }
1481    }
1482
1483    impl Serialize for Encoding {
1484        fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1485        where
1486            S: serde::Serializer,
1487        {
1488            self.as_str().serialize(serializer)
1489        }
1490    }
1491}
1492
1493#[cfg(test)]
1494mod tests {
1495    use super::*;
1496
1497    #[test]
1498    fn validate_schema_count() {
1499        use strum::EnumCount;
1500
1501        assert_eq!(Schema::COUNT, SCHEMA_COUNT);
1502    }
1503}