Skip to main content

dbn/
flags.rs

1//! Bit set flags used in Databento market data.
2
3use std::fmt;
4
5#[cfg(feature = "python")]
6use pyo3::prelude::*;
7
8/// Indicates it's the last record in the event from the venue for a given
9/// `instrument_id`.
10pub const LAST: u8 = 1 << 7;
11/// Indicates a top-of-book record, not an individual order.
12pub const TOB: u8 = 1 << 6;
13/// Indicates the record was sourced from a replay, such as a snapshot server.
14pub const SNAPSHOT: u8 = 1 << 5;
15/// Indicates an aggregated price level record, not an individual order.
16pub const MBP: u8 = 1 << 4;
17/// Indicates the `ts_recv` value is inaccurate due to clock issues or packet
18/// reordering.
19pub const BAD_TS_RECV: u8 = 1 << 3;
20/// Indicates an unrecoverable gap was detected in the channel.
21pub const MAYBE_BAD_BOOK: u8 = 1 << 2;
22/// Used to indicate a publisher-specific event.
23pub const PUBLISHER_SPECIFIC: u8 = 1 << 1;
24
25/// A transparent wrapper around the bit field used in several DBN record types,
26/// namely [`MboMsg`](crate::MboMsg) and record types derived from it.
27///
28/// Most operations support constant evaluation, e.g.
29/// ```
30/// use dbn::FlagSet;
31///
32/// const FLAGS: FlagSet = FlagSet::empty().set_snapshot().set_last();
33/// ```
34#[repr(transparent)]
35#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
36#[cfg_attr(feature = "python", derive(FromPyObject), pyo3(transparent))]
37#[cfg_attr(
38    feature = "serde",
39    derive(serde::Serialize, serde::Deserialize),
40    serde(transparent)
41)]
42pub struct FlagSet {
43    raw: u8,
44}
45
46impl fmt::Debug for FlagSet {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        let mut has_written_flag = false;
49        for (flag, name) in [
50            (LAST, stringify!(LAST)),
51            (TOB, stringify!(TOB)),
52            (SNAPSHOT, stringify!(SNAPSHOT)),
53            (MBP, stringify!(MBP)),
54            (BAD_TS_RECV, stringify!(BAD_TS_RECV)),
55            (MAYBE_BAD_BOOK, stringify!(MAYBE_BAD_BOOK)),
56            (PUBLISHER_SPECIFIC, stringify!(PUBLISHER_SPECIFIC)),
57        ] {
58            if (self.raw() & flag) > 0 {
59                if has_written_flag {
60                    write!(f, " | {name}")?;
61                } else {
62                    write!(f, "{name}")?;
63                    has_written_flag = true;
64                }
65            }
66        }
67        if has_written_flag {
68            write!(f, " ({})", self.raw())
69        } else {
70            write!(f, "{}", self.raw())
71        }
72    }
73}
74
75impl From<u8> for FlagSet {
76    fn from(raw: u8) -> Self {
77        Self { raw }
78    }
79}
80
81impl FlagSet {
82    /// Returns an empty [`FlagSet`]: one with no flags set.
83    pub const fn empty() -> Self {
84        Self { raw: 0 }
85    }
86
87    /// Creates a new flag set from `raw`.
88    pub const fn new(raw: u8) -> Self {
89        Self { raw }
90    }
91
92    /// Turns all flags off, i.e. to `false`.
93    pub const fn clear(&mut self) -> &mut Self {
94        self.raw = 0;
95        self
96    }
97
98    /// Returns the raw value.
99    pub const fn raw(&self) -> u8 {
100        self.raw
101    }
102
103    /// Sets the flags directly with a raw `u8`.
104    pub const fn set_raw(&mut self, raw: u8) {
105        self.raw = raw;
106    }
107
108    /// Returns `true` if any of the flags are on or set to true.
109    pub const fn any(&self) -> bool {
110        self.raw > 0
111    }
112
113    /// Returns `true` if all flags are unset/false.
114    pub const fn is_empty(&self) -> bool {
115        self.raw == 0
116    }
117
118    /// Returns `true` if it's the last record in the event from the venue for a given
119    /// `instrument_id`.
120    pub const fn is_last(&self) -> bool {
121        (self.raw & LAST) > 0
122    }
123
124    /// Sets the `LAST` bit flag to `true` to indicate this is the last record in the
125    /// event for a given instrument.
126    pub const fn set_last(&mut self) -> Self {
127        self.raw |= LAST;
128        *self
129    }
130
131    /// Returns `true` if it's a top-of-book record, not an individual order.
132    pub const fn is_tob(&self) -> bool {
133        (self.raw & TOB) > 0
134    }
135
136    /// Sets the `TOB` bit flag to `true` to indicate this is a top-of-book record.
137    pub const fn set_tob(&mut self) -> Self {
138        self.raw |= TOB;
139        *self
140    }
141
142    /// Returns `true` if this record was sourced from a replay, such as a snapshot
143    /// server.
144    pub const fn is_snapshot(&self) -> bool {
145        (self.raw & SNAPSHOT) > 0
146    }
147
148    /// Sets the `SNAPSHOT` bit flag to `true` to indicate this record was sourced from
149    /// a replay.
150    pub const fn set_snapshot(&mut self) -> Self {
151        self.raw |= SNAPSHOT;
152        *self
153    }
154
155    /// Returns `true` if this record is an aggregated price level record, not an
156    /// individual order.
157    pub const fn is_mbp(&self) -> bool {
158        (self.raw & MBP) > 0
159    }
160
161    /// Sets the `MBP` bit flag to `true` to indicate this record is an aggregated price
162    /// level record.
163    pub const fn set_mbp(&mut self) -> Self {
164        self.raw |= MBP;
165        *self
166    }
167
168    /// Returns `true` if this record has an inaccurate `ts_recv` value due to clock
169    /// issues or packet reordering.
170    pub const fn is_bad_ts_recv(&self) -> bool {
171        (self.raw & BAD_TS_RECV) > 0
172    }
173
174    /// Sets the `BAD_TS_RECV` bit flag to `true` to indicate this record has an
175    /// inaccurate `ts_recv` value.
176    pub const fn set_bad_ts_recv(&mut self) -> Self {
177        self.raw |= BAD_TS_RECV;
178        *self
179    }
180
181    /// Returns `true` if this record is from a channel where an unrecoverable gap was
182    /// detected.
183    pub const fn is_maybe_bad_book(&self) -> bool {
184        (self.raw & MAYBE_BAD_BOOK) > 0
185    }
186
187    /// Sets the `MAYBE_BAD_BOOK` bit flag to `true` to indicate this record is from a
188    /// channel where an unrecoverable gap was detected.
189    pub const fn set_maybe_bad_book(&mut self) -> Self {
190        self.raw |= MAYBE_BAD_BOOK;
191        *self
192    }
193
194    /// Returns `true` if this record has the publisher-specific flag set.
195    pub const fn is_publisher_specific(&self) -> bool {
196        (self.raw & PUBLISHER_SPECIFIC) > 0
197    }
198
199    /// Sets the `PUBLISHER_SPECIFIC` bit flag to `true`.
200    pub const fn set_publisher_specific(&mut self) -> Self {
201        self.raw |= PUBLISHER_SPECIFIC;
202        *self
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    use rstest::*;
211
212    #[rstest]
213    #[case::empty(FlagSet::empty(), "0")]
214    #[case::one_set(FlagSet::empty().set_mbp(), "MBP (16)")]
215    #[case::three_set(FlagSet::empty().set_tob().set_snapshot().set_maybe_bad_book(), "TOB | SNAPSHOT | MAYBE_BAD_BOOK (100)")]
216    #[case::reserved_set(
217        FlagSet::new(255),
218        "LAST | TOB | SNAPSHOT | MBP | BAD_TS_RECV | MAYBE_BAD_BOOK | PUBLISHER_SPECIFIC (255)"
219    )]
220    fn dbg(#[case] target: FlagSet, #[case] exp: &str) {
221        assert_eq!(format!("{target:?}"), exp);
222    }
223}