dbn/
record.rs

1//! Market data types for encoding different Databento [`Schema`](crate::enums::Schema)s
2//! in the most recent DBN version, as well as conversion functions.
3
4pub(crate) mod conv;
5mod impl_default;
6mod methods;
7
8use std::{ffi::CStr, mem, os::raw::c_char, ptr::NonNull, slice};
9
10// Dummy derive macro to get around `cfg_attr` incompatibility of several
11// of pyo3's attribute macros. See https://github.com/PyO3/pyo3/issues/780
12#[cfg(not(feature = "python"))]
13use dbn_macros::MockPyo3;
14
15use crate::{
16    enums::rtype,
17    macros::{dbn_record, CsvSerialize, JsonSerialize, RecordDebug},
18    Action, Error, FlagSet, InstrumentClass, MatchAlgorithm, Publisher, RType, Result,
19    SecurityUpdateAction, Side, StatType, StatUpdateAction, UserDefinedInstrument, SYMBOL_CSTR_LEN,
20};
21pub(crate) use conv::as_u8_slice;
22#[cfg(feature = "serde")]
23pub(crate) use conv::cstr_serde;
24pub use conv::{
25    c_chars_to_str, str_to_c_chars, transmute_header_bytes, transmute_record,
26    transmute_record_bytes, transmute_record_mut, ts_to_dt,
27};
28
29/// Common data for all Databento records. Always found at the beginning of a record
30/// struct.
31#[repr(C)]
32#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
33#[cfg_attr(feature = "trivial_copy", derive(Copy))]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35#[cfg_attr(
36    feature = "python",
37    pyo3::pyclass(eq, get_all, dict, module = "databento_dbn"),
38    derive(crate::macros::PyFieldDesc)
39)]
40#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
41#[cfg_attr(test, derive(type_layout::TypeLayout))]
42pub struct RecordHeader {
43    /// The length of the record in 32-bit words.
44    #[dbn(skip)]
45    pub(crate) length: u8,
46    /// The record type; with `0xe0..0x0F` specifying MBP levels size. Record types
47    /// implement the trait [`HasRType`], and the [`has_rtype`][HasRType::has_rtype]
48    /// function can be used to check if that type can be used to decode a message with
49    /// a given rtype. The set of possible values is defined in [`rtype`].
50    pub rtype: u8,
51    /// The publisher ID assigned by Databento, which denotes the dataset and venue.
52    #[pyo3(set)]
53    pub publisher_id: u16,
54    /// The numeric ID assigned to the instrument.
55    #[pyo3(set)]
56    pub instrument_id: u32,
57    /// The matching-engine-received timestamp expressed as the number of nanoseconds
58    /// since the UNIX epoch.
59    #[dbn(encode_order(0), unix_nanos)]
60    #[pyo3(set)]
61    pub ts_event: u64,
62}
63
64/// A market-by-order (MBO) tick message. The record of the
65/// [`Mbo`](crate::enums::Schema::Mbo) schema.
66#[repr(C)]
67#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
68#[cfg_attr(feature = "trivial_copy", derive(Copy))]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70#[cfg_attr(
71    feature = "python",
72    pyo3::pyclass(dict, eq, module = "databento_dbn", name = "MBOMsg"),
73    derive(crate::macros::PyFieldDesc)
74)]
75#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
76#[cfg_attr(test, derive(type_layout::TypeLayout))]
77#[dbn_record(rtype::MBO)]
78pub struct MboMsg {
79    /// The common header.
80    #[pyo3(get)]
81    pub hd: RecordHeader,
82    /// The order ID assigned at the venue.
83    #[pyo3(get, set)]
84    pub order_id: u64,
85    /// The order price expressed as a signed integer where every 1 unit
86    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
87    #[dbn(encode_order(4), fixed_price)]
88    #[pyo3(get, set)]
89    pub price: i64,
90    /// The order quantity.
91    #[dbn(encode_order(5))]
92    #[pyo3(get, set)]
93    pub size: u32,
94    /// A bit field indicating event end, message characteristics, and data quality. See
95    /// [`enums::flags`](crate::enums::flags) for possible values.
96    #[pyo3(get, set)]
97    pub flags: FlagSet,
98    /// The channel ID assigned by Databento as an incrementing integer starting at
99    /// zero.
100    #[dbn(encode_order(6))]
101    #[pyo3(get, set)]
102    pub channel_id: u8,
103    /// The event action. Can be **A**dd, **C**ancel, **M**odify, clea**R**,
104    /// **T**rade, **F**ill, or **N**one.
105    #[dbn(c_char, encode_order(2))]
106    pub action: c_char,
107    /// The side that initiates the event. Can be **A**sk for a sell order (or sell
108    /// aggressor in a trade), **B**id for a buy order (or buy aggressor in a trade), or
109    /// **N**one where no side is specified by the original source.
110    #[dbn(c_char, encode_order(3))]
111    pub side: c_char,
112    /// The capture-server-received timestamp expressed as the number of nanoseconds
113    /// since the UNIX epoch.
114    #[dbn(encode_order(0), index_ts, unix_nanos)]
115    #[pyo3(get, set)]
116    pub ts_recv: u64,
117    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
118    #[pyo3(get, set)]
119    pub ts_in_delta: i32,
120    /// The message sequence number assigned at the venue.
121    #[pyo3(get, set)]
122    pub sequence: u32,
123}
124
125/// A level.
126#[repr(C)]
127#[derive(Clone, JsonSerialize, RecordDebug, PartialEq, Eq, Hash)]
128#[cfg_attr(feature = "trivial_copy", derive(Copy))]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130#[cfg_attr(
131    feature = "python",
132    pyo3::pyclass(eq, get_all, set_all, dict, module = "databento_dbn"),
133    derive(crate::macros::PyFieldDesc)
134)]
135#[cfg_attr(test, derive(type_layout::TypeLayout))]
136pub struct BidAskPair {
137    /// The bid price.
138    #[dbn(fixed_price)]
139    pub bid_px: i64,
140    /// The ask price.
141    #[dbn(fixed_price)]
142    pub ask_px: i64,
143    /// The bid size.
144    pub bid_sz: u32,
145    /// The ask size.
146    pub ask_sz: u32,
147    /// The bid order count.
148    pub bid_ct: u32,
149    /// The ask order count.
150    pub ask_ct: u32,
151}
152
153/// A price level consolidated from multiple venues.
154#[repr(C)]
155#[derive(Clone, JsonSerialize, RecordDebug, PartialEq, Eq, Hash)]
156#[cfg_attr(feature = "trivial_copy", derive(Copy))]
157#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
158#[cfg_attr(
159    feature = "python",
160    pyo3::pyclass(eq, get_all, set_all, dict, module = "databento_dbn"),
161    derive(crate::macros::PyFieldDesc)
162)]
163#[cfg_attr(test, derive(type_layout::TypeLayout))]
164pub struct ConsolidatedBidAskPair {
165    /// The bid price.
166    #[dbn(fixed_price)]
167    pub bid_px: i64,
168    /// The ask price.
169    #[dbn(fixed_price)]
170    pub ask_px: i64,
171    /// The bid size.
172    pub bid_sz: u32,
173    /// The ask size.
174    pub ask_sz: u32,
175    /// The bid publisher ID assigned by Databento, which denotes the dataset and venue.
176    #[dbn(fmt_method)]
177    pub bid_pb: u16,
178    // Reserved for later usage.
179    #[doc(hidden)]
180    #[cfg_attr(feature = "serde", serde(skip))]
181    pub _reserved1: [c_char; 2],
182    /// The ask publisher ID assigned by Databento, which denotes the dataset and venue.
183    #[dbn(fmt_method)]
184    pub ask_pb: u16,
185    // Reserved for later usage.
186    #[doc(hidden)]
187    #[cfg_attr(feature = "serde", serde(skip))]
188    pub _reserved2: [c_char; 2],
189}
190
191/// Market by price implementation with a book depth of 0. Equivalent to
192/// MBP-0. The record of the [`Trades`](crate::enums::Schema::Trades) schema.
193#[repr(C)]
194#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
195#[cfg_attr(feature = "trivial_copy", derive(Copy))]
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197#[cfg_attr(
198    feature = "python",
199    pyo3::pyclass(dict, eq, module = "databento_dbn"),
200    derive(crate::macros::PyFieldDesc)
201)]
202#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
203#[cfg_attr(test, derive(type_layout::TypeLayout))]
204#[dbn_record(rtype::MBP_0)]
205pub struct TradeMsg {
206    /// The common header.
207    #[pyo3(get)]
208    pub hd: RecordHeader,
209    /// The order price expressed as a signed integer where every 1 unit
210    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
211    #[dbn(fixed_price)]
212    #[pyo3(get, set)]
213    pub price: i64,
214    /// The order quantity.
215    #[pyo3(get, set)]
216    pub size: u32,
217    /// The event action. Always **T**rade in the trades schema.
218    #[dbn(c_char, encode_order(2))]
219    pub action: c_char,
220    /// The side that initiates the trade. Can be **A**sk for a sell aggressor in a
221    /// trade, **B**id for a buy aggressor in a trade, or **N**one where no side is
222    /// specified by the original source.
223    #[dbn(c_char, encode_order(3))]
224    pub side: c_char,
225    /// A bit field indicating event end, message characteristics, and data quality. See
226    /// [`enums::flags`](crate::enums::flags) for possible values.
227    #[pyo3(get, set)]
228    pub flags: FlagSet,
229    /// The depth of actual book change.
230    #[dbn(encode_order(4))]
231    #[pyo3(get, set)]
232    pub depth: u8,
233    /// The capture-server-received timestamp expressed as the number of nanoseconds
234    /// since the UNIX epoch.
235    #[dbn(encode_order(0), index_ts, unix_nanos)]
236    #[pyo3(get, set)]
237    pub ts_recv: u64,
238    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
239    #[pyo3(get, set)]
240    pub ts_in_delta: i32,
241    /// The message sequence number assigned at the venue.
242    #[pyo3(get, set)]
243    pub sequence: u32,
244}
245
246/// Market by price implementation with a known book depth of 1. The record of the
247/// [`Mbp1`](crate::enums::Schema::Mbp1) schema.
248#[repr(C)]
249#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
250#[cfg_attr(feature = "trivial_copy", derive(Copy))]
251#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
252#[cfg_attr(
253    feature = "python",
254    pyo3::pyclass(dict, eq, module = "databento_dbn", name = "MBP1Msg"),
255    derive(crate::macros::PyFieldDesc)
256)]
257#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
258#[cfg_attr(test, derive(type_layout::TypeLayout))]
259#[dbn_record(rtype::MBP_1)]
260pub struct Mbp1Msg {
261    /// The common header.
262    #[pyo3(get)]
263    pub hd: RecordHeader,
264    /// The order price expressed as a signed integer where every 1 unit
265    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
266    #[dbn(fixed_price)]
267    #[pyo3(get, set)]
268    pub price: i64,
269    /// The order quantity.
270    #[pyo3(get, set)]
271    pub size: u32,
272    /// The event action. Can be **A**dd, **C**ancel, **M**odify, clea**R**, or
273    /// **T**rade.
274    #[dbn(c_char, encode_order(2))]
275    pub action: c_char,
276    /// The side that initiates the event. Can be **A**sk for a sell order (or sell
277    /// aggressor in a trade), **B**id for a buy order (or buy aggressor in a trade), or
278    /// **N**one where no side is specified by the original source.
279    #[dbn(c_char, encode_order(3))]
280    pub side: c_char,
281    /// A bit field indicating event end, message characteristics, and data quality. See
282    /// [`enums::flags`](crate::enums::flags) for possible values.
283    #[pyo3(get, set)]
284    pub flags: FlagSet,
285    /// The depth of actual book change.
286    #[dbn(encode_order(4))]
287    #[pyo3(get, set)]
288    pub depth: u8,
289    /// The capture-server-received timestamp expressed as the number of nanoseconds
290    /// since the UNIX epoch.
291    #[dbn(encode_order(0), index_ts, unix_nanos)]
292    #[pyo3(get, set)]
293    pub ts_recv: u64,
294    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
295    #[pyo3(get, set)]
296    pub ts_in_delta: i32,
297    /// The message sequence number assigned at the venue.
298    #[pyo3(get, set)]
299    pub sequence: u32,
300    /// The top of the order book.
301    #[pyo3(get, set)]
302    pub levels: [BidAskPair; 1],
303}
304
305/// Market by price implementation with a known book depth of 10. The record of the
306/// [`Mbp10`](crate::enums::Schema::Mbp10) schema.
307#[repr(C)]
308#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
309#[cfg_attr(feature = "trivial_copy", derive(Copy))]
310#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
311#[cfg_attr(
312    feature = "python",
313    pyo3::pyclass(dict, eq, module = "databento_dbn", name = "MBP10Msg"),
314    derive(crate::macros::PyFieldDesc)
315)]
316#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
317#[cfg_attr(test, derive(type_layout::TypeLayout))]
318#[dbn_record(rtype::MBP_10)]
319pub struct Mbp10Msg {
320    /// The common header.
321    #[pyo3(get)]
322    pub hd: RecordHeader,
323    /// The order price expressed as a signed integer where every 1 unit
324    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
325    #[dbn(fixed_price)]
326    #[pyo3(get, set)]
327    pub price: i64,
328    /// The order quantity.
329    #[pyo3(get, set)]
330    pub size: u32,
331    /// The event action. Can be **A**dd, **C**ancel, **M**odify, clea**R**, or
332    /// **T**rade.
333    #[dbn(c_char, encode_order(2))]
334    pub action: c_char,
335    /// The side that initiates the event. Can be **A**sk for a sell order (or sell
336    /// aggressor in a trade), **B**id for a buy order (or buy aggressor in a trade), or
337    /// **N**one where no side is specified by the original source.
338    #[dbn(c_char, encode_order(3))]
339    pub side: c_char,
340    /// A bit field indicating event end, message characteristics, and data quality. See
341    /// [`enums::flags`](crate::enums::flags) for possible values.
342    #[pyo3(get, set)]
343    pub flags: FlagSet,
344    /// The depth of actual book change.
345    #[dbn(encode_order(4))]
346    #[pyo3(get, set)]
347    pub depth: u8,
348    /// The capture-server-received timestamp expressed as the number of nanoseconds
349    /// since the UNIX epoch.
350    #[dbn(encode_order(0), index_ts, unix_nanos)]
351    #[pyo3(get, set)]
352    pub ts_recv: u64,
353    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
354    #[pyo3(get, set)]
355    pub ts_in_delta: i32,
356    /// The message sequence number assigned at the venue.
357    #[pyo3(get, set)]
358    pub sequence: u32,
359    /// The top 10 levels of the order book.
360    #[pyo3(get, set)]
361    pub levels: [BidAskPair; 10],
362}
363
364/// Subsampled market by price with a known book depth of 1. The record of the
365/// [`Bbo1S`](crate::Schema::Bbo1S) and [`Bbo1M`](crate::Schema::Bbo1M) schemas.
366#[repr(C)]
367#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
368#[cfg_attr(feature = "trivial_copy", derive(Copy))]
369#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
370#[cfg_attr(
371    feature = "python",
372    pyo3::pyclass(dict, eq, module = "databento_dbn", name = "BBOMsg"),
373    derive(crate::macros::PyFieldDesc)
374)]
375#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
376#[cfg_attr(test, derive(type_layout::TypeLayout))]
377#[dbn_record(rtype::BBO_1S, rtype::BBO_1M)]
378pub struct BboMsg {
379    /// The common header.
380    #[pyo3(get)]
381    pub hd: RecordHeader,
382    /// The price of the last trade expressed as a signed integer where every 1 unit
383    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001. Will be [`UNDEF_PRICE`](crate::UNDEF_PRICE)
384    /// if there was no last trade in the session.
385    #[dbn(fixed_price)]
386    #[pyo3(get, set)]
387    pub price: i64,
388    /// The last trade quantity.
389    #[pyo3(get, set)]
390    pub size: u32,
391    // Reserved for later usage.
392    #[doc(hidden)]
393    #[cfg_attr(feature = "serde", serde(skip))]
394    pub _reserved1: u8,
395    /// The side that initiated the last trade. Can be **A**sk for a sell order (or sell
396    /// aggressor in a trade), **B**id for a buy order (or buy aggressor in a trade), or
397    /// **N**one where no side is specified by the original source or if there was no last
398    /// trade.
399    #[dbn(c_char, encode_order(2))]
400    pub side: c_char,
401    /// A bit field indicating event end, message characteristics, and data quality. See
402    /// [`enums::flags`](crate::enums::flags) for possible values.
403    #[pyo3(get, set)]
404    pub flags: FlagSet,
405    // Reserved for later usage.
406    #[doc(hidden)]
407    #[cfg_attr(feature = "serde", serde(skip))]
408    pub _reserved2: u8,
409    /// The end timestamp of the interval as the number of nanoseconds since the UNIX
410    /// epoch. Clamped to the 1-second or 1-minute boundary.
411    #[dbn(encode_order(0), index_ts, unix_nanos)]
412    #[pyo3(get, set)]
413    pub ts_recv: u64,
414    // Reserved for later usage.
415    #[doc(hidden)]
416    #[cfg_attr(feature = "serde", serde(skip))]
417    pub _reserved3: [u8; 4],
418    /// The sequence number assigned at the venue of the last update.
419    #[pyo3(get, set)]
420    pub sequence: u32,
421    /// The top of the order book.
422    #[pyo3(get, set)]
423    pub levels: [BidAskPair; 1],
424}
425
426/// Consolidated market by price implementation with a known book depth of 1. The record of the
427/// [`Cmbp1`](crate::Schema::Cmbp1) schema.
428#[repr(C)]
429#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
430#[cfg_attr(feature = "trivial_copy", derive(Copy))]
431#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
432#[cfg_attr(
433    feature = "python",
434    pyo3::pyclass(dict, eq, module = "databento_dbn", name = "CMBP1Msg"),
435    derive(crate::macros::PyFieldDesc)
436)]
437#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
438#[cfg_attr(test, derive(type_layout::TypeLayout))]
439#[dbn_record(rtype::CMBP1, rtype::TCBBO)]
440pub struct Cmbp1Msg {
441    /// The common header.
442    #[pyo3(get)]
443    pub hd: RecordHeader,
444    /// The order price expressed as a signed integer where every 1 unit
445    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
446    #[dbn(fixed_price)]
447    #[pyo3(get, set)]
448    pub price: i64,
449    /// The order quantity.
450    #[pyo3(get, set)]
451    pub size: u32,
452    /// The event action. Can be **A**dd, **C**ancel, **M**odify, clea**R**, or
453    /// **T**rade.
454    #[dbn(c_char, encode_order(2))]
455    pub action: c_char,
456    /// The side that initiates the event. Can be **A**sk for a sell order (or sell
457    /// aggressor in a trade), **B**id for a buy order (or buy aggressor in a trade), or
458    /// **N**one where no side is specified by the original source.
459    #[dbn(c_char, encode_order(3))]
460    pub side: c_char,
461    /// A bit field indicating event end, message characteristics, and data quality. See
462    /// [`enums::flags`](crate::enums::flags) for possible values.
463    #[pyo3(get, set)]
464    pub flags: FlagSet,
465    // Reserved for future usage.
466    #[doc(hidden)]
467    #[cfg_attr(feature = "serde", serde(skip))]
468    pub _reserved1: [c_char; 1],
469    /// The capture-server-received timestamp expressed as the number of nanoseconds
470    /// since the UNIX epoch.
471    #[dbn(encode_order(0), index_ts, unix_nanos)]
472    #[pyo3(get, set)]
473    pub ts_recv: u64,
474    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
475    #[pyo3(get, set)]
476    pub ts_in_delta: i32,
477    #[doc(hidden)]
478    #[cfg_attr(feature = "serde", serde(skip))]
479    pub _reserved2: [c_char; 4],
480    /// The top of the order book.
481    #[pyo3(get, set)]
482    pub levels: [ConsolidatedBidAskPair; 1],
483}
484
485/// Subsampled market by price with a known book depth of 1. The record of the
486/// [`Bbo1S`](crate::Schema::Bbo1S) and [`Bbo1M`](crate::Schema::Bbo1M) schemas.
487#[repr(C)]
488#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
489#[cfg_attr(feature = "trivial_copy", derive(Copy))]
490#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
491#[cfg_attr(
492    feature = "python",
493    pyo3::pyclass(dict, eq, module = "databento_dbn", name = "CBBOMsg"),
494    derive(crate::macros::PyFieldDesc)
495)]
496#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
497#[cfg_attr(test, derive(type_layout::TypeLayout))]
498#[dbn_record(rtype::CBBO_1S, rtype::CBBO_1M)]
499pub struct CbboMsg {
500    /// The common header.
501    #[pyo3(get)]
502    pub hd: RecordHeader,
503    /// The price of the last trade expressed as a signed integer where every 1 unit
504    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
505    #[dbn(fixed_price)]
506    #[pyo3(get, set)]
507    pub price: i64,
508    /// The quantity of the last trade.
509    #[pyo3(get, set)]
510    pub size: u32,
511    // Reserved for later usage.
512    #[doc(hidden)]
513    #[cfg_attr(feature = "serde", serde(skip))]
514    pub _reserved1: u8,
515    /// The side that initiated the last trade. Can be **A**sk for a sell order (or sell
516    /// aggressor in a trade), **B**id for a buy order (or buy aggressor in a trade), or
517    /// **N**one where no side is specified by the original source.
518    #[dbn(c_char, encode_order(2))]
519    pub side: c_char,
520    /// A bit field indicating event end, message characteristics, and data quality. See
521    /// [`enums::flags`](crate::enums::flags) for possible values.
522    #[pyo3(get, set)]
523    pub flags: FlagSet,
524    // Reserved for later usage.
525    #[doc(hidden)]
526    #[cfg_attr(feature = "serde", serde(skip))]
527    pub _reserved2: u8,
528    /// The interval timestamp expressed as the number of nanoseconds since the UNIX
529    /// epoch.
530    #[dbn(encode_order(0), index_ts, unix_nanos)]
531    #[pyo3(get, set)]
532    pub ts_recv: u64,
533    // Reserved for later usage.
534    #[doc(hidden)]
535    #[cfg_attr(feature = "serde", serde(skip))]
536    pub _reserved3: [u8; 8],
537    /// The top of the order book.
538    #[pyo3(get, set)]
539    pub levels: [ConsolidatedBidAskPair; 1],
540}
541
542/// The record of the [`Tbbo`](crate::enums::Schema::Tbbo) schema.
543pub type TbboMsg = Mbp1Msg;
544/// The record of the [`Bbo1S`](crate::enums::Schema::Bbo1S) schema.
545pub type Bbo1SMsg = BboMsg;
546/// The record of the [`Bbo1M`](crate::enums::Schema::Bbo1M) schema.
547pub type Bbo1MMsg = BboMsg;
548
549/// The record of the [`Tcbbo`](crate::enums::Schema::Tcbbo) schema.
550pub type TcbboMsg = Cmbp1Msg;
551/// The record of the [`Cbbo1S`](crate::enums::Schema::Cbbo1S) schema.
552pub type Cbbo1SMsg = CbboMsg;
553/// The record of the [`Cbbo1M`](crate::enums::Schema::Cbbo1M) schema.
554pub type Cbbo1MMsg = CbboMsg;
555
556/// Open, high, low, close, and volume. The record of the following schemas:
557/// - [`Ohlcv1S`](crate::enums::Schema::Ohlcv1S)
558/// - [`Ohlcv1M`](crate::enums::Schema::Ohlcv1M)
559/// - [`Ohlcv1H`](crate::enums::Schema::Ohlcv1H)
560/// - [`Ohlcv1D`](crate::enums::Schema::Ohlcv1D)
561/// - [`OhlcvEod`](crate::enums::Schema::OhlcvEod)
562#[repr(C)]
563#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
564#[cfg_attr(feature = "trivial_copy", derive(Copy))]
565#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
566#[cfg_attr(
567    feature = "python",
568    pyo3::pyclass(dict, eq, get_all, module = "databento_dbn", name = "OHLCVMsg"),
569    derive(crate::macros::PyFieldDesc)
570)]
571#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
572#[cfg_attr(test, derive(type_layout::TypeLayout))]
573#[dbn_record(
574    rtype::OHLCV_1S,
575    rtype::OHLCV_1M,
576    rtype::OHLCV_1H,
577    rtype::OHLCV_1D,
578    rtype::OHLCV_EOD,
579    rtype::OHLCV_DEPRECATED
580)]
581pub struct OhlcvMsg {
582    /// The common header.
583    pub hd: RecordHeader,
584    /// The open price for the bar.
585    #[dbn(fixed_price)]
586    #[pyo3(set)]
587    pub open: i64,
588    /// The high price for the bar.
589    #[dbn(fixed_price)]
590    #[pyo3(set)]
591    pub high: i64,
592    /// The low price for the bar.
593    #[dbn(fixed_price)]
594    #[pyo3(set)]
595    pub low: i64,
596    /// The close price for the bar.
597    #[dbn(fixed_price)]
598    #[pyo3(set)]
599    pub close: i64,
600    /// The total volume traded during the aggregation period.
601    #[pyo3(set)]
602    pub volume: u64,
603}
604
605/// A trading status update message. The record of the
606/// [`Status`](crate::enums::Schema::Status) schema.
607#[repr(C)]
608#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
609#[cfg_attr(feature = "trivial_copy", derive(Copy))]
610#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
611#[cfg_attr(
612    feature = "python",
613    pyo3::pyclass(dict, eq, module = "databento_dbn"),
614    derive(crate::macros::PyFieldDesc)
615)]
616#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
617#[cfg_attr(test, derive(type_layout::TypeLayout))]
618#[dbn_record(rtype::STATUS)]
619pub struct StatusMsg {
620    /// The common header.
621    #[pyo3(get)]
622    pub hd: RecordHeader,
623    /// The capture-server-received timestamp expressed as the number of nanoseconds
624    /// since the UNIX epoch.
625    #[dbn(encode_order(0), index_ts, unix_nanos)]
626    #[pyo3(get, set)]
627    pub ts_recv: u64,
628    /// The type of status change.
629    #[dbn(fmt_method)]
630    #[pyo3(get, set)]
631    pub action: u16,
632    /// Additional details about the cause of the status change.
633    #[dbn(fmt_method)]
634    #[pyo3(get, set)]
635    pub reason: u16,
636    /// Further information about the status change and its effect on trading.
637    #[dbn(fmt_method)]
638    #[pyo3(get, set)]
639    pub trading_event: u16,
640    /// The state of trading in the instrument.
641    #[dbn(c_char)]
642    #[pyo3(get, set)]
643    pub is_trading: c_char,
644    /// The state of quoting in the instrument.
645    #[dbn(c_char)]
646    #[pyo3(get, set)]
647    pub is_quoting: c_char,
648    /// The state of short sell restrictions for the instrument.
649    #[dbn(c_char)]
650    #[pyo3(get, set)]
651    pub is_short_sell_restricted: c_char,
652    // Filler for alignment.
653    #[doc(hidden)]
654    #[cfg_attr(feature = "serde", serde(skip))]
655    pub _reserved: [u8; 7],
656}
657
658/// Definition of an instrument. The record of the
659/// [`Definition`](crate::enums::Schema::Definition) schema.
660#[repr(C)]
661#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
662#[cfg_attr(feature = "trivial_copy", derive(Copy))]
663#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
664#[cfg_attr(
665    feature = "python",
666    pyo3::pyclass(dict, eq, module = "databento_dbn"),
667    derive(crate::macros::PyFieldDesc)
668)]
669#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
670#[cfg_attr(test, derive(type_layout::TypeLayout))]
671#[dbn_record(rtype::INSTRUMENT_DEF)]
672pub struct InstrumentDefMsg {
673    /// The common header.
674    #[pyo3(get, set)]
675    pub hd: RecordHeader,
676    /// The capture-server-received timestamp expressed as the number of nanoseconds
677    /// since the UNIX epoch.
678    #[dbn(encode_order(0), index_ts, unix_nanos)]
679    #[pyo3(get, set)]
680    pub ts_recv: u64,
681    /// The minimum constant tick for the instrument in units of 1e-9, i.e.
682    /// 1/1,000,000,000 or 0.000000001.
683    #[dbn(fixed_price)]
684    #[pyo3(get, set)]
685    pub min_price_increment: i64,
686    /// The multiplier to convert the venue’s display price to the conventional price,
687    /// in units of 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
688    #[dbn(fixed_price)]
689    #[pyo3(get, set)]
690    pub display_factor: i64,
691    /// The last eligible trade time expressed as a number of nanoseconds since the
692    /// UNIX epoch.
693    ///
694    /// Will be [`crate::UNDEF_TIMESTAMP`] when null, such as for equities. Some publishers
695    /// only provide date-level granularity.
696    #[dbn(unix_nanos)]
697    #[pyo3(get, set)]
698    pub expiration: u64,
699    /// The time of instrument activation expressed as a number of nanoseconds since the
700    /// UNIX epoch.
701    ///
702    /// Will be [`crate::UNDEF_TIMESTAMP`] when null, such as for equities. Some publishers
703    /// only provide date-level granularity.
704    #[dbn(unix_nanos)]
705    #[pyo3(get, set)]
706    pub activation: u64,
707    /// The allowable high limit price for the trading day in units of 1e-9, i.e.
708    /// 1/1,000,000,000 or 0.000000001.
709    #[dbn(fixed_price)]
710    #[pyo3(get, set)]
711    pub high_limit_price: i64,
712    /// The allowable low limit price for the trading day in units of 1e-9, i.e.
713    /// 1/1,000,000,000 or 0.000000001.
714    #[dbn(fixed_price)]
715    #[pyo3(get, set)]
716    pub low_limit_price: i64,
717    /// The differential value for price banding in units of 1e-9, i.e. 1/1,000,000,000
718    /// or 0.000000001.
719    #[dbn(fixed_price)]
720    #[pyo3(get, set)]
721    pub max_price_variation: i64,
722    /// The trading session settlement price on `trading_reference_date`.
723    #[dbn(fixed_price)]
724    #[pyo3(get, set)]
725    pub trading_reference_price: i64,
726    /// The contract size for each instrument, in combination with `unit_of_measure`, in units
727    /// of 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
728    #[dbn(fixed_price)]
729    #[pyo3(get, set)]
730    pub unit_of_measure_qty: i64,
731    /// The value currently under development by the venue. Converted to units of 1e-9, i.e.
732    /// 1/1,000,000,000 or 0.000000001.
733    #[dbn(fixed_price)]
734    #[pyo3(get, set)]
735    pub min_price_increment_amount: i64,
736    /// The value used for price calculation in spread and leg pricing in units of 1e-9,
737    /// i.e. 1/1,000,000,000 or 0.000000001.
738    #[dbn(fixed_price)]
739    #[pyo3(get, set)]
740    pub price_ratio: i64,
741    /// The strike price of the option. Converted to units of 1e-9, i.e. 1/1,000,000,000
742    /// or 0.000000001.
743    #[dbn(fixed_price, encode_order(46))]
744    #[pyo3(get, set)]
745    pub strike_price: i64,
746    /// A bitmap of instrument eligibility attributes.
747    #[dbn(fmt_binary)]
748    #[pyo3(get, set)]
749    pub inst_attrib_value: i32,
750    /// The `instrument_id` of the first underlying instrument.
751    #[pyo3(get, set)]
752    pub underlying_id: u32,
753    /// The instrument ID assigned by the publisher. May be the same as `instrument_id`.
754    #[pyo3(get, set)]
755    pub raw_instrument_id: u32,
756    /// The implied book depth on the price level data feed.
757    #[pyo3(get, set)]
758    pub market_depth_implied: i32,
759    /// The (outright) book depth on the price level data feed.
760    #[pyo3(get, set)]
761    pub market_depth: i32,
762    /// The market segment of the instrument.
763    #[pyo3(get, set)]
764    pub market_segment_id: u32,
765    /// The maximum trading volume for the instrument.
766    #[pyo3(get, set)]
767    pub max_trade_vol: u32,
768    /// The minimum order entry quantity for the instrument.
769    #[pyo3(get, set)]
770    pub min_lot_size: i32,
771    /// The minimum quantity required for a block trade of the instrument.
772    #[pyo3(get, set)]
773    pub min_lot_size_block: i32,
774    /// The minimum quantity required for a round lot of the instrument. Multiples of
775    /// this quantity are also round lots.
776    #[pyo3(get, set)]
777    pub min_lot_size_round_lot: i32,
778    /// The minimum trading volume for the instrument.
779    #[pyo3(get, set)]
780    pub min_trade_vol: u32,
781    /// The number of deliverables per instrument, i.e. peak days.
782    #[pyo3(get, set)]
783    pub contract_multiplier: i32,
784    /// The quantity that a contract will decay daily, after `decay_start_date` has
785    /// been reached.
786    #[pyo3(get, set)]
787    pub decay_quantity: i32,
788    /// The fixed contract value assigned to each instrument.
789    #[pyo3(get, set)]
790    pub original_contract_size: i32,
791    /// The trading session date corresponding to the settlement price in
792    /// `trading_reference_price`, in number of days since the UNIX epoch.
793    #[pyo3(get, set)]
794    pub trading_reference_date: u16,
795    /// The channel ID assigned at the venue.
796    #[pyo3(get, set)]
797    pub appl_id: i16,
798    /// The calendar year reflected in the instrument symbol.
799    #[pyo3(get, set)]
800    pub maturity_year: u16,
801    /// The date at which a contract will begin to decay.
802    #[pyo3(get, set)]
803    pub decay_start_date: u16,
804    /// The channel ID assigned by Databento as an incrementing integer starting at
805    /// zero.
806    #[pyo3(get, set)]
807    pub channel_id: u16,
808    /// The currency used for price fields.
809    #[dbn(fmt_method)]
810    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
811    pub currency: [c_char; 4],
812    /// The currency used for settlement, if different from `currency`.
813    #[dbn(fmt_method)]
814    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
815    pub settl_currency: [c_char; 4],
816    /// The strategy type of the spread.
817    #[dbn(fmt_method)]
818    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
819    pub secsubtype: [c_char; 6],
820    /// The instrument raw symbol assigned by the publisher.
821    #[dbn(encode_order(2), fmt_method)]
822    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
823    pub raw_symbol: [c_char; SYMBOL_CSTR_LEN],
824    /// The security group code of the instrument.
825    #[dbn(fmt_method)]
826    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
827    pub group: [c_char; 21],
828    /// The exchange used to identify the instrument.
829    #[dbn(fmt_method)]
830    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
831    pub exchange: [c_char; 5],
832    /// The underlying asset code (product code) of the instrument.
833    #[dbn(fmt_method)]
834    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
835    pub asset: [c_char; 7],
836    /// The ISO standard instrument categorization code.
837    #[dbn(fmt_method)]
838    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
839    pub cfi: [c_char; 7],
840    /// The type of the instrument, e.g. FUT for future or future spread.
841    #[dbn(fmt_method)]
842    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
843    pub security_type: [c_char; 7],
844    /// The unit of measure for the instrument’s original contract size, e.g. USD or LBS.
845    #[dbn(fmt_method)]
846    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
847    pub unit_of_measure: [c_char; 31],
848    /// The symbol of the first underlying instrument.
849    #[dbn(fmt_method)]
850    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
851    pub underlying: [c_char; 21],
852    /// The currency of [`strike_price`](Self::strike_price).
853    #[dbn(fmt_method)]
854    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
855    pub strike_price_currency: [c_char; 4],
856    /// The classification of the instrument.
857    #[dbn(c_char, encode_order(4))]
858    #[pyo3(set)]
859    pub instrument_class: c_char,
860    /// The matching algorithm used for the instrument, typically **F**IFO.
861    #[dbn(c_char)]
862    #[pyo3(set)]
863    pub match_algorithm: c_char,
864    /// The current trading state of the instrument.
865    #[pyo3(get, set)]
866    pub md_security_trading_status: u8,
867    /// The price denominator of the main fraction.
868    #[pyo3(get, set)]
869    pub main_fraction: u8,
870    ///  The number of digits to the right of the tick mark, to display fractional prices.
871    #[pyo3(get, set)]
872    pub price_display_format: u8,
873    /// The type indicators for the settlement price, as a bitmap.
874    #[pyo3(get, set)]
875    pub settl_price_type: u8,
876    /// The price denominator of the sub fraction.
877    #[pyo3(get, set)]
878    pub sub_fraction: u8,
879    /// The product complex of the instrument.
880    #[pyo3(get, set)]
881    pub underlying_product: u8,
882    /// Indicates if the instrument definition has been added, modified, or deleted.
883    #[dbn(c_char, encode_order(3))]
884    #[pyo3(set)]
885    pub security_update_action: c_char,
886    /// The calendar month reflected in the instrument symbol.
887    #[pyo3(get, set)]
888    pub maturity_month: u8,
889    /// The calendar day reflected in the instrument symbol, or 0.
890    #[pyo3(get, set)]
891    pub maturity_day: u8,
892    /// The calendar week reflected in the instrument symbol, or 0.
893    #[pyo3(get, set)]
894    pub maturity_week: u8,
895    /// Indicates if the instrument is user defined: **Y**es or **N**o.
896    #[pyo3(set)]
897    pub user_defined_instrument: UserDefinedInstrument,
898    /// The type of `contract_multiplier`. Either `1` for hours, or `2` for days.
899    #[pyo3(get, set)]
900    pub contract_multiplier_unit: i8,
901    /// The schedule for delivering electricity.
902    #[pyo3(get, set)]
903    pub flow_schedule_type: i8,
904    /// The tick rule of the spread.
905    #[pyo3(get, set)]
906    pub tick_rule: u8,
907    // Filler for alignment.
908    #[doc(hidden)]
909    #[cfg_attr(feature = "serde", serde(skip))]
910    pub _reserved: [u8; 10],
911}
912
913/// An auction imbalance message.
914#[repr(C)]
915#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
916#[cfg_attr(feature = "trivial_copy", derive(Copy))]
917#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
918#[cfg_attr(
919    feature = "python",
920    pyo3::pyclass(dict, eq, module = "databento_dbn"),
921    derive(crate::macros::PyFieldDesc)
922)]
923#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
924#[cfg_attr(test, derive(type_layout::TypeLayout))]
925#[dbn_record(rtype::IMBALANCE)]
926pub struct ImbalanceMsg {
927    /// The common header.
928    #[pyo3(get)]
929    pub hd: RecordHeader,
930    /// The capture-server-received timestamp expressed as the number of nanoseconds
931    /// since the UNIX epoch.
932    #[dbn(encode_order(0), index_ts, unix_nanos)]
933    #[pyo3(get, set)]
934    pub ts_recv: u64,
935    /// The price at which the imbalance shares are calculated, where every 1 unit corresponds to
936    /// 1e-9, i.e. 1/1,000,000,000 or 0.000000001.
937    #[dbn(fixed_price)]
938    #[pyo3(get, set)]
939    pub ref_price: i64,
940    /// Reserved for future use.
941    #[pyo3(get, set)]
942    pub auction_time: u64,
943    /// The hypothetical auction-clearing price for both cross and continuous orders.
944    #[dbn(fixed_price)]
945    #[pyo3(get, set)]
946    pub cont_book_clr_price: i64,
947    /// The hypothetical auction-clearing price for cross orders only.
948    #[dbn(fixed_price)]
949    #[pyo3(get, set)]
950    pub auct_interest_clr_price: i64,
951    /// Reserved for future use.
952    #[dbn(fixed_price)]
953    #[pyo3(get, set)]
954    pub ssr_filling_price: i64,
955    /// Reserved for future use.
956    #[dbn(fixed_price)]
957    #[pyo3(get, set)]
958    pub ind_match_price: i64,
959    /// Reserved for future use.
960    #[dbn(fixed_price)]
961    #[pyo3(get, set)]
962    pub upper_collar: i64,
963    /// Reserved for future use.
964    #[dbn(fixed_price)]
965    #[pyo3(get, set)]
966    pub lower_collar: i64,
967    /// The quantity of shares that are eligible to be matched at `ref_price`.
968    #[pyo3(get, set)]
969    pub paired_qty: u32,
970    /// The quantity of shares that are not paired at `ref_price`.
971    #[pyo3(get, set)]
972    pub total_imbalance_qty: u32,
973    /// Reserved for future use.
974    #[pyo3(get, set)]
975    pub market_imbalance_qty: u32,
976    /// Reserved for future use.
977    #[pyo3(get, set)]
978    pub unpaired_qty: u32,
979    /// Venue-specific character code indicating the auction type.
980    #[dbn(c_char)]
981    pub auction_type: c_char,
982    /// The market side of the `total_imbalance_qty`. Can be **A**sk, **B**id, or **N**one.
983    #[dbn(c_char)]
984    pub side: c_char,
985    /// Reserved for future use.
986    #[pyo3(get, set)]
987    pub auction_status: u8,
988    /// Reserved for future use.
989    #[pyo3(get, set)]
990    pub freeze_status: u8,
991    /// Reserved for future use.
992    #[pyo3(get, set)]
993    pub num_extensions: u8,
994    /// Reserved for future use.
995    #[dbn(c_char)]
996    pub unpaired_side: c_char,
997    /// Venue-specific character code. For Nasdaq, contains the raw Price Variation Indicator.
998    #[dbn(c_char)]
999    pub significant_imbalance: c_char,
1000    // Filler for alignment.
1001    #[doc(hidden)]
1002    #[cfg_attr(feature = "serde", serde(skip))]
1003    pub _reserved: [u8; 1],
1004}
1005
1006/// A statistics message. A catchall for various data disseminated by publishers.
1007/// The [`stat_type`](Self::stat_type) indicates the statistic contained in the message.
1008#[repr(C)]
1009#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
1010#[cfg_attr(feature = "trivial_copy", derive(Copy))]
1011#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1012#[cfg_attr(
1013    feature = "python",
1014    pyo3::pyclass(dict, eq, get_all, module = "databento_dbn"),
1015    derive(crate::macros::PyFieldDesc)
1016)]
1017#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
1018#[cfg_attr(test, derive(type_layout::TypeLayout))]
1019#[dbn_record(rtype::STATISTICS)]
1020pub struct StatMsg {
1021    /// The common header.
1022    pub hd: RecordHeader,
1023    /// The capture-server-received timestamp expressed as the number of nanoseconds
1024    /// since the UNIX epoch.
1025    #[dbn(encode_order(0), index_ts, unix_nanos)]
1026    #[pyo3(set)]
1027    pub ts_recv: u64,
1028    /// The reference timestamp of the statistic value expressed as the number of
1029    /// nanoseconds since the UNIX epoch. Will be [`crate::UNDEF_TIMESTAMP`] when
1030    /// unused.
1031    #[dbn(unix_nanos)]
1032    #[pyo3(set)]
1033    pub ts_ref: u64,
1034    /// The value for price statistics expressed as a signed integer where every 1 unit
1035    /// corresponds to 1e-9, i.e. 1/1,000,000,000 or 0.000000001. Will be
1036    /// [`UNDEF_PRICE`](crate::UNDEF_PRICE) when unused.
1037    #[dbn(fixed_price)]
1038    #[pyo3(set)]
1039    pub price: i64,
1040    /// The value for non-price statistics. Will be [`crate::UNDEF_STAT_QUANTITY`] when
1041    /// unused.
1042    #[pyo3(set)]
1043    pub quantity: i32,
1044    /// The message sequence number assigned at the venue.
1045    #[pyo3(set)]
1046    pub sequence: u32,
1047    /// The delta of `ts_recv - ts_exchange_send`, max 2 seconds.
1048    #[pyo3(set)]
1049    pub ts_in_delta: i32,
1050    /// The type of statistic value contained in the message. Refer to the
1051    /// [`StatType`](crate::enums::StatType) for variants.
1052    #[dbn(fmt_method)]
1053    #[pyo3(set)]
1054    pub stat_type: u16,
1055    /// The channel ID assigned by Databento as an incrementing integer starting at
1056    /// zero.
1057    #[pyo3(set)]
1058    pub channel_id: u16,
1059    /// Indicates if the statistic is newly added (1) or deleted (2). (Deleted is only used with
1060    /// some stat types)
1061    #[dbn(fmt_method)]
1062    #[pyo3(set)]
1063    pub update_action: u8,
1064    /// Additional flags associate with certain stat types.
1065    #[dbn(fmt_binary)]
1066    #[pyo3(set)]
1067    pub stat_flags: u8,
1068    // Filler for alignment
1069    #[doc(hidden)]
1070    #[cfg_attr(feature = "serde", serde(skip))]
1071    pub _reserved: [u8; 6],
1072}
1073
1074/// An error message from the Databento Live Subscription Gateway (LSG).
1075#[repr(C)]
1076#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
1077#[cfg_attr(feature = "trivial_copy", derive(Copy))]
1078#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1079#[cfg_attr(
1080    feature = "python",
1081    pyo3::pyclass(dict, eq, module = "databento_dbn"),
1082    derive(crate::macros::PyFieldDesc)
1083)]
1084#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
1085#[cfg_attr(test, derive(type_layout::TypeLayout))]
1086#[dbn_record(rtype::ERROR)]
1087pub struct ErrorMsg {
1088    /// The common header.
1089    #[pyo3(get)]
1090    pub hd: RecordHeader,
1091    /// The error message.
1092    #[dbn(fmt_method)]
1093    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
1094    pub err: [c_char; 302],
1095    /// The error code.
1096    #[pyo3(get, set)]
1097    pub code: u8,
1098    /// Sometimes multiple errors are sent together. This field will be non-zero for the
1099    /// last error.
1100    #[pyo3(get, set)]
1101    pub is_last: u8,
1102}
1103
1104/// A symbol mapping message which maps a symbol of one [`SType`](crate::enums::SType)
1105/// to another.
1106#[repr(C)]
1107#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
1108#[cfg_attr(feature = "trivial_copy", derive(Copy))]
1109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1110#[cfg_attr(
1111    feature = "python",
1112    pyo3::pyclass(dict, eq, module = "databento_dbn"),
1113    derive(crate::macros::PyFieldDesc)
1114)]
1115#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
1116#[cfg_attr(test, derive(type_layout::TypeLayout))]
1117#[dbn_record(rtype::SYMBOL_MAPPING)]
1118pub struct SymbolMappingMsg {
1119    /// The common header.
1120    #[pyo3(get, set)]
1121    pub hd: RecordHeader,
1122    // TODO(carter): special serialization to string?
1123    /// The input symbology type of `stype_in_symbol`.
1124    #[dbn(fmt_method)]
1125    #[pyo3(get, set)]
1126    pub stype_in: u8,
1127    /// The input symbol.
1128    #[dbn(fmt_method)]
1129    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
1130    pub stype_in_symbol: [c_char; SYMBOL_CSTR_LEN],
1131    /// The output symbology type of `stype_out_symbol`.
1132    #[dbn(fmt_method)]
1133    #[pyo3(get, set)]
1134    pub stype_out: u8,
1135    /// The output symbol.
1136    #[dbn(fmt_method)]
1137    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
1138    pub stype_out_symbol: [c_char; SYMBOL_CSTR_LEN],
1139    /// The start of the mapping interval expressed as the number of nanoseconds since
1140    /// the UNIX epoch.
1141    #[dbn(unix_nanos)]
1142    #[pyo3(get, set)]
1143    pub start_ts: u64,
1144    /// The end of the mapping interval expressed as the number of nanoseconds since
1145    /// the UNIX epoch.
1146    #[dbn(unix_nanos)]
1147    #[pyo3(get, set)]
1148    pub end_ts: u64,
1149}
1150
1151/// A non-error message from the Databento Live Subscription Gateway (LSG). Also used
1152/// for heartbeating.
1153#[repr(C)]
1154#[derive(Clone, CsvSerialize, JsonSerialize, PartialEq, Eq, Hash)]
1155#[cfg_attr(feature = "trivial_copy", derive(Copy))]
1156#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1157#[cfg_attr(
1158    feature = "python",
1159    pyo3::pyclass(dict, eq, module = "databento_dbn"),
1160    derive(crate::macros::PyFieldDesc)
1161)]
1162#[cfg_attr(not(feature = "python"), derive(MockPyo3))] // bring `pyo3` attribute into scope
1163#[cfg_attr(test, derive(type_layout::TypeLayout))]
1164#[dbn_record(rtype::SYSTEM)]
1165pub struct SystemMsg {
1166    /// The common header.
1167    #[pyo3(get, set)]
1168    pub hd: RecordHeader,
1169    /// The message from the Databento Live Subscription Gateway (LSG).
1170    #[dbn(fmt_method)]
1171    #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
1172    pub msg: [c_char; 303],
1173    /// Type of system message.
1174    #[pyo3(get, set)]
1175    pub code: u8,
1176}
1177
1178/// Used for polymorphism around types all beginning with a [`RecordHeader`] where
1179/// `rtype` is the discriminant used for indicating the type of record.
1180pub trait Record {
1181    /// Returns a reference to the `RecordHeader` that comes at the beginning of all
1182    /// record types.
1183    fn header(&self) -> &RecordHeader;
1184
1185    /// Returns the size of the record in bytes.
1186    fn record_size(&self) -> usize {
1187        self.header().record_size()
1188    }
1189
1190    /// Tries to convert the raw record type into an enum which is useful for exhaustive
1191    /// pattern matching.
1192    ///
1193    /// # Errors
1194    /// This function returns an error if the `rtype` field does not
1195    /// contain a valid, known [`RType`].
1196    fn rtype(&self) -> crate::Result<RType> {
1197        self.header().rtype()
1198    }
1199
1200    /// Tries to convert the raw `publisher_id` into an enum which is useful for
1201    /// exhaustive pattern matching.
1202    ///
1203    /// # Errors
1204    /// This function returns an error if the `publisher_id` does not correspond with
1205    /// any known [`Publisher`].
1206    fn publisher(&self) -> crate::Result<Publisher> {
1207        self.header().publisher()
1208    }
1209
1210    /// Returns the raw primary timestamp for the record.
1211    ///
1212    /// This timestamp should be used for sorting records as well as indexing into any
1213    /// symbology data structure.
1214    fn raw_index_ts(&self) -> u64 {
1215        self.header().ts_event
1216    }
1217
1218    /// Returns the primary timestamp for the record. Returns `None` if the primary
1219    /// timestamp contains the sentinel value for a null timestamp.
1220    ///
1221    /// This timestamp should be used for sorting records as well as indexing into any
1222    /// symbology data structure.
1223    fn index_ts(&self) -> Option<time::OffsetDateTime> {
1224        ts_to_dt(self.raw_index_ts())
1225    }
1226
1227    /// Returns the primary date for the record; the date component of the primary
1228    /// timestamp (`index_ts()`). Returns `None` if the primary timestamp contains the
1229    /// sentinel value for a null timestamp.
1230    fn index_date(&self) -> Option<time::Date> {
1231        self.index_ts().map(|dt| dt.date())
1232    }
1233}
1234
1235/// Used for polymorphism around mutable types beginning with a [`RecordHeader`].
1236pub trait RecordMut {
1237    /// Returns a mutable reference to the `RecordHeader` that comes at the beginning of
1238    /// all record types.
1239    fn header_mut(&mut self) -> &mut RecordHeader;
1240}
1241
1242/// An extension of the [`Record`] trait for types with a static [`RType`]. Used for
1243/// determining if a rtype matches a type.
1244pub trait HasRType: Record + RecordMut {
1245    /// Returns `true` if `rtype` matches the value associated with the implementing type.
1246    fn has_rtype(rtype: u8) -> bool;
1247}
1248
1249/// Wrapper object for records that include the live gateway send timestamp (`ts_out`).
1250#[repr(C)]
1251#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1252#[cfg_attr(feature = "trivial_copy", derive(Copy))]
1253#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1254pub struct WithTsOut<T: HasRType> {
1255    /// The inner record.
1256    pub rec: T,
1257    /// The live gateway send timestamp expressed as the number of nanoseconds since the
1258    /// UNIX epoch.
1259    pub ts_out: u64,
1260}
1261
1262#[cfg(test)]
1263mod tests {
1264    use mem::offset_of;
1265    use rstest::rstest;
1266    use type_layout::{Field, TypeLayout};
1267
1268    use crate::Schema;
1269    use crate::UNDEF_TIMESTAMP;
1270
1271    use super::*;
1272
1273    const OHLCV_MSG: OhlcvMsg = OhlcvMsg {
1274        hd: RecordHeader {
1275            length: 56,
1276            rtype: rtype::OHLCV_1S,
1277            publisher_id: 1,
1278            instrument_id: 5482,
1279            ts_event: 1609160400000000000,
1280        },
1281        open: 372025000000000,
1282        high: 372050000000000,
1283        low: 372025000000000,
1284        close: 372050000000000,
1285        volume: 57,
1286    };
1287
1288    #[test]
1289    fn test_transmute_record_bytes() {
1290        unsafe {
1291            let ohlcv_bytes = std::slice::from_raw_parts(
1292                &OHLCV_MSG as *const OhlcvMsg as *const u8,
1293                mem::size_of::<OhlcvMsg>(),
1294            )
1295            .to_vec();
1296            let ohlcv = transmute_record_bytes::<OhlcvMsg>(ohlcv_bytes.as_slice()).unwrap();
1297            assert_eq!(*ohlcv, OHLCV_MSG);
1298        };
1299    }
1300
1301    #[test]
1302    #[should_panic]
1303    fn test_transmute_record_bytes_small_buffer() {
1304        let source = OHLCV_MSG;
1305        unsafe {
1306            let slice = std::slice::from_raw_parts(
1307                &source as *const OhlcvMsg as *const u8,
1308                mem::size_of::<OhlcvMsg>() - 5,
1309            );
1310            transmute_record_bytes::<OhlcvMsg>(slice);
1311        };
1312    }
1313
1314    #[test]
1315    fn test_transmute_record() {
1316        let source = Box::new(OHLCV_MSG);
1317        let ohlcv_ref: &OhlcvMsg = unsafe { transmute_record(&source.hd) }.unwrap();
1318        assert_eq!(*ohlcv_ref, OHLCV_MSG);
1319    }
1320
1321    #[test]
1322    fn test_transmute_record_mut() {
1323        let mut source = Box::new(OHLCV_MSG);
1324        let ohlcv_ref: &OhlcvMsg = unsafe { transmute_record_mut(&mut source.hd) }.unwrap();
1325        assert_eq!(*ohlcv_ref, OHLCV_MSG);
1326    }
1327
1328    #[rstest]
1329    #[case::header(RecordHeader::default::<MboMsg>(rtype::MBO), 16)]
1330    #[case::mbo(MboMsg::default(), 56)]
1331    #[case::ba_pair(BidAskPair::default(), 32)]
1332    #[case::cba_pair(ConsolidatedBidAskPair::default(), mem::size_of::<BidAskPair>())]
1333    #[case::trade(TradeMsg::default(), 48)]
1334    #[case::mbp1(Mbp1Msg::default(), mem::size_of::<TradeMsg>() + mem::size_of::<BidAskPair>())]
1335    #[case::mbp10(Mbp10Msg::default(), mem::size_of::<TradeMsg>() + mem::size_of::<BidAskPair>() * 10)]
1336    #[case::bbo(BboMsg::default_for_schema(Schema::Bbo1S), mem::size_of::<Mbp1Msg>())]
1337    #[case::cmbp1(Cmbp1Msg::default_for_schema(Schema::Cmbp1), mem::size_of::<Mbp1Msg>())]
1338    #[case::cbbo(CbboMsg::default_for_schema(Schema::Cbbo1S), mem::size_of::<Mbp1Msg>())]
1339    #[case::ohlcv(OhlcvMsg::default_for_schema(Schema::Ohlcv1S), 56)]
1340    #[case::status(StatusMsg::default(), 40)]
1341    #[case::definition(InstrumentDefMsg::default(), 400)]
1342    #[case::imbalance(ImbalanceMsg::default(), 112)]
1343    #[case::stat(StatMsg::default(), 64)]
1344    #[case::error(ErrorMsg::default(), 320)]
1345    #[case::symbol_mapping(SymbolMappingMsg::default(), 176)]
1346    #[case::system(SystemMsg::default(), 320)]
1347    #[case::with_ts_out(WithTsOut::new(SystemMsg::default(), 0), mem::size_of::<SystemMsg>() + 8)]
1348    fn test_sizes<R: Sized>(#[case] _rec: R, #[case] exp: usize) {
1349        assert_eq!(mem::size_of::<R>(), exp);
1350        assert!(mem::size_of::<R>() <= crate::MAX_RECORD_LEN);
1351    }
1352
1353    #[rstest]
1354    #[case::header(RecordHeader::default::<MboMsg>(rtype::MBO))]
1355    #[case::mbo(MboMsg::default())]
1356    #[case::ba_pair(BidAskPair::default())]
1357    #[case::cba_pair(ConsolidatedBidAskPair::default())]
1358    #[case::trade(TradeMsg::default())]
1359    #[case::mbp1(Mbp1Msg::default())]
1360    #[case::mbp10(Mbp10Msg::default())]
1361    #[case::bbo(BboMsg::default_for_schema(crate::Schema::Bbo1S))]
1362    #[case::cmbp1(Cmbp1Msg::default_for_schema(crate::Schema::Cmbp1))]
1363    #[case::cbbo(CbboMsg::default_for_schema(crate::Schema::Cbbo1S))]
1364    #[case::ohlcv(OhlcvMsg::default_for_schema(Schema::Ohlcv1S))]
1365    #[case::status(StatusMsg::default())]
1366    #[case::definition(InstrumentDefMsg::default())]
1367    #[case::imbalance(ImbalanceMsg::default())]
1368    #[case::stat(StatMsg::default())]
1369    #[case::error(ErrorMsg::default())]
1370    #[case::symbol_mapping(SymbolMappingMsg::default())]
1371    #[case::system(SystemMsg::default())]
1372    fn test_alignment_and_no_padding<R: TypeLayout>(#[case] _rec: R) {
1373        let layout = R::type_layout();
1374        assert_eq!(layout.alignment, 8, "Unexpected alignment: {layout}");
1375        for field in layout.fields.iter() {
1376            assert!(
1377                matches!(field, Field::Field { .. }),
1378                "Detected padding: {layout}"
1379            );
1380        }
1381    }
1382
1383    #[test]
1384    fn test_bbo_alignment_matches_mbp1() {
1385        assert_eq!(offset_of!(BboMsg, hd), offset_of!(Mbp1Msg, hd));
1386        assert_eq!(offset_of!(BboMsg, price), offset_of!(Mbp1Msg, price));
1387        assert_eq!(offset_of!(BboMsg, size), offset_of!(Mbp1Msg, size));
1388        assert_eq!(offset_of!(BboMsg, side), offset_of!(Mbp1Msg, side));
1389        assert_eq!(offset_of!(BboMsg, flags), offset_of!(Mbp1Msg, flags));
1390        assert_eq!(offset_of!(BboMsg, ts_recv), offset_of!(Mbp1Msg, ts_recv));
1391        assert_eq!(offset_of!(BboMsg, sequence), offset_of!(Mbp1Msg, sequence));
1392        assert_eq!(offset_of!(BboMsg, levels), offset_of!(Mbp1Msg, levels));
1393    }
1394
1395    #[test]
1396    fn test_mbo_index_ts() {
1397        let rec = MboMsg {
1398            ts_recv: 1,
1399            ..Default::default()
1400        };
1401        assert_eq!(rec.raw_index_ts(), 1);
1402    }
1403
1404    #[test]
1405    fn test_def_index_ts() {
1406        let rec = InstrumentDefMsg {
1407            ts_recv: 1,
1408            ..Default::default()
1409        };
1410        assert_eq!(rec.raw_index_ts(), 1);
1411    }
1412
1413    #[test]
1414    fn test_db_ts_always_valid_time_offsetdatetime() {
1415        assert!(time::OffsetDateTime::from_unix_timestamp_nanos(0).is_ok());
1416        assert!(time::OffsetDateTime::from_unix_timestamp_nanos((u64::MAX - 1) as i128).is_ok());
1417        assert!(time::OffsetDateTime::from_unix_timestamp_nanos(UNDEF_TIMESTAMP as i128).is_ok());
1418    }
1419
1420    #[test]
1421    fn test_record_object_safe() {
1422        let _record: Box<dyn Record> = Box::new(ErrorMsg::new(1, None, "Boxed record", true));
1423    }
1424}