1pub(crate) mod conv;
5mod impl_default;
6mod methods;
7
8use std::{ffi::CStr, mem, os::raw::c_char, ptr::NonNull, slice};
9
10#[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#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
42pub struct RecordHeader {
43 #[dbn(skip)]
45 pub(crate) length: u8,
46 pub rtype: u8,
51 #[pyo3(set)]
53 pub publisher_id: u16,
54 #[pyo3(set)]
56 pub instrument_id: u32,
57 #[dbn(encode_order(0), unix_nanos)]
60 #[pyo3(set)]
61 pub ts_event: u64,
62}
63
64#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
77#[dbn_record(rtype::MBO)]
78pub struct MboMsg {
79 #[pyo3(get)]
81 pub hd: RecordHeader,
82 #[pyo3(get, set)]
84 pub order_id: u64,
85 #[dbn(encode_order(4), fixed_price)]
88 #[pyo3(get, set)]
89 pub price: i64,
90 #[dbn(encode_order(5))]
92 #[pyo3(get, set)]
93 pub size: u32,
94 #[pyo3(get, set)]
97 pub flags: FlagSet,
98 #[dbn(encode_order(6))]
101 #[pyo3(get, set)]
102 pub channel_id: u8,
103 #[dbn(c_char, encode_order(2))]
106 pub action: c_char,
107 #[dbn(c_char, encode_order(3))]
111 pub side: c_char,
112 #[dbn(encode_order(0), index_ts, unix_nanos)]
115 #[pyo3(get, set)]
116 pub ts_recv: u64,
117 #[pyo3(get, set)]
119 pub ts_in_delta: i32,
120 #[pyo3(get, set)]
122 pub sequence: u32,
123}
124
125#[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 #[dbn(fixed_price)]
139 pub bid_px: i64,
140 #[dbn(fixed_price)]
142 pub ask_px: i64,
143 pub bid_sz: u32,
145 pub ask_sz: u32,
147 pub bid_ct: u32,
149 pub ask_ct: u32,
151}
152
153#[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 #[dbn(fixed_price)]
167 pub bid_px: i64,
168 #[dbn(fixed_price)]
170 pub ask_px: i64,
171 pub bid_sz: u32,
173 pub ask_sz: u32,
175 #[dbn(fmt_method)]
177 pub bid_pb: u16,
178 #[doc(hidden)]
180 #[cfg_attr(feature = "serde", serde(skip))]
181 pub _reserved1: [c_char; 2],
182 #[dbn(fmt_method)]
184 pub ask_pb: u16,
185 #[doc(hidden)]
187 #[cfg_attr(feature = "serde", serde(skip))]
188 pub _reserved2: [c_char; 2],
189}
190
191#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
204#[dbn_record(rtype::MBP_0)]
205pub struct TradeMsg {
206 #[pyo3(get)]
208 pub hd: RecordHeader,
209 #[dbn(fixed_price)]
212 #[pyo3(get, set)]
213 pub price: i64,
214 #[pyo3(get, set)]
216 pub size: u32,
217 #[dbn(c_char, encode_order(2))]
219 pub action: c_char,
220 #[dbn(c_char, encode_order(3))]
224 pub side: c_char,
225 #[pyo3(get, set)]
228 pub flags: FlagSet,
229 #[dbn(encode_order(4))]
231 #[pyo3(get, set)]
232 pub depth: u8,
233 #[dbn(encode_order(0), index_ts, unix_nanos)]
236 #[pyo3(get, set)]
237 pub ts_recv: u64,
238 #[pyo3(get, set)]
240 pub ts_in_delta: i32,
241 #[pyo3(get, set)]
243 pub sequence: u32,
244}
245
246#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
259#[dbn_record(rtype::MBP_1)]
260pub struct Mbp1Msg {
261 #[pyo3(get)]
263 pub hd: RecordHeader,
264 #[dbn(fixed_price)]
267 #[pyo3(get, set)]
268 pub price: i64,
269 #[pyo3(get, set)]
271 pub size: u32,
272 #[dbn(c_char, encode_order(2))]
275 pub action: c_char,
276 #[dbn(c_char, encode_order(3))]
280 pub side: c_char,
281 #[pyo3(get, set)]
284 pub flags: FlagSet,
285 #[dbn(encode_order(4))]
287 #[pyo3(get, set)]
288 pub depth: u8,
289 #[dbn(encode_order(0), index_ts, unix_nanos)]
292 #[pyo3(get, set)]
293 pub ts_recv: u64,
294 #[pyo3(get, set)]
296 pub ts_in_delta: i32,
297 #[pyo3(get, set)]
299 pub sequence: u32,
300 #[pyo3(get, set)]
302 pub levels: [BidAskPair; 1],
303}
304
305#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
318#[dbn_record(rtype::MBP_10)]
319pub struct Mbp10Msg {
320 #[pyo3(get)]
322 pub hd: RecordHeader,
323 #[dbn(fixed_price)]
326 #[pyo3(get, set)]
327 pub price: i64,
328 #[pyo3(get, set)]
330 pub size: u32,
331 #[dbn(c_char, encode_order(2))]
334 pub action: c_char,
335 #[dbn(c_char, encode_order(3))]
339 pub side: c_char,
340 #[pyo3(get, set)]
343 pub flags: FlagSet,
344 #[dbn(encode_order(4))]
346 #[pyo3(get, set)]
347 pub depth: u8,
348 #[dbn(encode_order(0), index_ts, unix_nanos)]
351 #[pyo3(get, set)]
352 pub ts_recv: u64,
353 #[pyo3(get, set)]
355 pub ts_in_delta: i32,
356 #[pyo3(get, set)]
358 pub sequence: u32,
359 #[pyo3(get, set)]
361 pub levels: [BidAskPair; 10],
362}
363
364#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
377#[dbn_record(rtype::BBO_1S, rtype::BBO_1M)]
378pub struct BboMsg {
379 #[pyo3(get)]
381 pub hd: RecordHeader,
382 #[dbn(fixed_price)]
386 #[pyo3(get, set)]
387 pub price: i64,
388 #[pyo3(get, set)]
390 pub size: u32,
391 #[doc(hidden)]
393 #[cfg_attr(feature = "serde", serde(skip))]
394 pub _reserved1: u8,
395 #[dbn(c_char, encode_order(2))]
400 pub side: c_char,
401 #[pyo3(get, set)]
404 pub flags: FlagSet,
405 #[doc(hidden)]
407 #[cfg_attr(feature = "serde", serde(skip))]
408 pub _reserved2: u8,
409 #[dbn(encode_order(0), index_ts, unix_nanos)]
412 #[pyo3(get, set)]
413 pub ts_recv: u64,
414 #[doc(hidden)]
416 #[cfg_attr(feature = "serde", serde(skip))]
417 pub _reserved3: [u8; 4],
418 #[pyo3(get, set)]
420 pub sequence: u32,
421 #[pyo3(get, set)]
423 pub levels: [BidAskPair; 1],
424}
425
426#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
439#[dbn_record(rtype::CMBP1, rtype::TCBBO)]
440pub struct Cmbp1Msg {
441 #[pyo3(get)]
443 pub hd: RecordHeader,
444 #[dbn(fixed_price)]
447 #[pyo3(get, set)]
448 pub price: i64,
449 #[pyo3(get, set)]
451 pub size: u32,
452 #[dbn(c_char, encode_order(2))]
455 pub action: c_char,
456 #[dbn(c_char, encode_order(3))]
460 pub side: c_char,
461 #[pyo3(get, set)]
464 pub flags: FlagSet,
465 #[doc(hidden)]
467 #[cfg_attr(feature = "serde", serde(skip))]
468 pub _reserved1: [c_char; 1],
469 #[dbn(encode_order(0), index_ts, unix_nanos)]
472 #[pyo3(get, set)]
473 pub ts_recv: u64,
474 #[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 #[pyo3(get, set)]
482 pub levels: [ConsolidatedBidAskPair; 1],
483}
484
485#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
498#[dbn_record(rtype::CBBO_1S, rtype::CBBO_1M)]
499pub struct CbboMsg {
500 #[pyo3(get)]
502 pub hd: RecordHeader,
503 #[dbn(fixed_price)]
506 #[pyo3(get, set)]
507 pub price: i64,
508 #[pyo3(get, set)]
510 pub size: u32,
511 #[doc(hidden)]
513 #[cfg_attr(feature = "serde", serde(skip))]
514 pub _reserved1: u8,
515 #[dbn(c_char, encode_order(2))]
519 pub side: c_char,
520 #[pyo3(get, set)]
523 pub flags: FlagSet,
524 #[doc(hidden)]
526 #[cfg_attr(feature = "serde", serde(skip))]
527 pub _reserved2: u8,
528 #[dbn(encode_order(0), index_ts, unix_nanos)]
531 #[pyo3(get, set)]
532 pub ts_recv: u64,
533 #[doc(hidden)]
535 #[cfg_attr(feature = "serde", serde(skip))]
536 pub _reserved3: [u8; 8],
537 #[pyo3(get, set)]
539 pub levels: [ConsolidatedBidAskPair; 1],
540}
541
542pub type TbboMsg = Mbp1Msg;
544pub type Bbo1SMsg = BboMsg;
546pub type Bbo1MMsg = BboMsg;
548
549pub type TcbboMsg = Cmbp1Msg;
551pub type Cbbo1SMsg = CbboMsg;
553pub type Cbbo1MMsg = CbboMsg;
555
556#[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))] #[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 pub hd: RecordHeader,
584 #[dbn(fixed_price)]
586 #[pyo3(set)]
587 pub open: i64,
588 #[dbn(fixed_price)]
590 #[pyo3(set)]
591 pub high: i64,
592 #[dbn(fixed_price)]
594 #[pyo3(set)]
595 pub low: i64,
596 #[dbn(fixed_price)]
598 #[pyo3(set)]
599 pub close: i64,
600 #[pyo3(set)]
602 pub volume: u64,
603}
604
605#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
618#[dbn_record(rtype::STATUS)]
619pub struct StatusMsg {
620 #[pyo3(get)]
622 pub hd: RecordHeader,
623 #[dbn(encode_order(0), index_ts, unix_nanos)]
626 #[pyo3(get, set)]
627 pub ts_recv: u64,
628 #[dbn(fmt_method)]
630 #[pyo3(get, set)]
631 pub action: u16,
632 #[dbn(fmt_method)]
634 #[pyo3(get, set)]
635 pub reason: u16,
636 #[dbn(fmt_method)]
638 #[pyo3(get, set)]
639 pub trading_event: u16,
640 #[dbn(c_char)]
642 #[pyo3(get, set)]
643 pub is_trading: c_char,
644 #[dbn(c_char)]
646 #[pyo3(get, set)]
647 pub is_quoting: c_char,
648 #[dbn(c_char)]
650 #[pyo3(get, set)]
651 pub is_short_sell_restricted: c_char,
652 #[doc(hidden)]
654 #[cfg_attr(feature = "serde", serde(skip))]
655 pub _reserved: [u8; 7],
656}
657
658#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
671#[dbn_record(rtype::INSTRUMENT_DEF)]
672pub struct InstrumentDefMsg {
673 #[pyo3(get, set)]
675 pub hd: RecordHeader,
676 #[dbn(encode_order(0), index_ts, unix_nanos)]
679 #[pyo3(get, set)]
680 pub ts_recv: u64,
681 #[dbn(fixed_price)]
684 #[pyo3(get, set)]
685 pub min_price_increment: i64,
686 #[dbn(fixed_price)]
689 #[pyo3(get, set)]
690 pub display_factor: i64,
691 #[dbn(unix_nanos)]
697 #[pyo3(get, set)]
698 pub expiration: u64,
699 #[dbn(unix_nanos)]
705 #[pyo3(get, set)]
706 pub activation: u64,
707 #[dbn(fixed_price)]
710 #[pyo3(get, set)]
711 pub high_limit_price: i64,
712 #[dbn(fixed_price)]
715 #[pyo3(get, set)]
716 pub low_limit_price: i64,
717 #[dbn(fixed_price)]
720 #[pyo3(get, set)]
721 pub max_price_variation: i64,
722 #[dbn(fixed_price)]
724 #[pyo3(get, set)]
725 pub trading_reference_price: i64,
726 #[dbn(fixed_price)]
729 #[pyo3(get, set)]
730 pub unit_of_measure_qty: i64,
731 #[dbn(fixed_price)]
734 #[pyo3(get, set)]
735 pub min_price_increment_amount: i64,
736 #[dbn(fixed_price)]
739 #[pyo3(get, set)]
740 pub price_ratio: i64,
741 #[dbn(fixed_price, encode_order(46))]
744 #[pyo3(get, set)]
745 pub strike_price: i64,
746 #[dbn(fmt_binary)]
748 #[pyo3(get, set)]
749 pub inst_attrib_value: i32,
750 #[pyo3(get, set)]
752 pub underlying_id: u32,
753 #[pyo3(get, set)]
755 pub raw_instrument_id: u32,
756 #[pyo3(get, set)]
758 pub market_depth_implied: i32,
759 #[pyo3(get, set)]
761 pub market_depth: i32,
762 #[pyo3(get, set)]
764 pub market_segment_id: u32,
765 #[pyo3(get, set)]
767 pub max_trade_vol: u32,
768 #[pyo3(get, set)]
770 pub min_lot_size: i32,
771 #[pyo3(get, set)]
773 pub min_lot_size_block: i32,
774 #[pyo3(get, set)]
777 pub min_lot_size_round_lot: i32,
778 #[pyo3(get, set)]
780 pub min_trade_vol: u32,
781 #[pyo3(get, set)]
783 pub contract_multiplier: i32,
784 #[pyo3(get, set)]
787 pub decay_quantity: i32,
788 #[pyo3(get, set)]
790 pub original_contract_size: i32,
791 #[pyo3(get, set)]
794 pub trading_reference_date: u16,
795 #[pyo3(get, set)]
797 pub appl_id: i16,
798 #[pyo3(get, set)]
800 pub maturity_year: u16,
801 #[pyo3(get, set)]
803 pub decay_start_date: u16,
804 #[pyo3(get, set)]
807 pub channel_id: u16,
808 #[dbn(fmt_method)]
810 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
811 pub currency: [c_char; 4],
812 #[dbn(fmt_method)]
814 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
815 pub settl_currency: [c_char; 4],
816 #[dbn(fmt_method)]
818 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
819 pub secsubtype: [c_char; 6],
820 #[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 #[dbn(fmt_method)]
826 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
827 pub group: [c_char; 21],
828 #[dbn(fmt_method)]
830 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
831 pub exchange: [c_char; 5],
832 #[dbn(fmt_method)]
834 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
835 pub asset: [c_char; 7],
836 #[dbn(fmt_method)]
838 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
839 pub cfi: [c_char; 7],
840 #[dbn(fmt_method)]
842 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
843 pub security_type: [c_char; 7],
844 #[dbn(fmt_method)]
846 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
847 pub unit_of_measure: [c_char; 31],
848 #[dbn(fmt_method)]
850 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
851 pub underlying: [c_char; 21],
852 #[dbn(fmt_method)]
854 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
855 pub strike_price_currency: [c_char; 4],
856 #[dbn(c_char, encode_order(4))]
858 #[pyo3(set)]
859 pub instrument_class: c_char,
860 #[dbn(c_char)]
862 #[pyo3(set)]
863 pub match_algorithm: c_char,
864 #[pyo3(get, set)]
866 pub md_security_trading_status: u8,
867 #[pyo3(get, set)]
869 pub main_fraction: u8,
870 #[pyo3(get, set)]
872 pub price_display_format: u8,
873 #[pyo3(get, set)]
875 pub settl_price_type: u8,
876 #[pyo3(get, set)]
878 pub sub_fraction: u8,
879 #[pyo3(get, set)]
881 pub underlying_product: u8,
882 #[dbn(c_char, encode_order(3))]
884 #[pyo3(set)]
885 pub security_update_action: c_char,
886 #[pyo3(get, set)]
888 pub maturity_month: u8,
889 #[pyo3(get, set)]
891 pub maturity_day: u8,
892 #[pyo3(get, set)]
894 pub maturity_week: u8,
895 #[pyo3(set)]
897 pub user_defined_instrument: UserDefinedInstrument,
898 #[pyo3(get, set)]
900 pub contract_multiplier_unit: i8,
901 #[pyo3(get, set)]
903 pub flow_schedule_type: i8,
904 #[pyo3(get, set)]
906 pub tick_rule: u8,
907 #[doc(hidden)]
909 #[cfg_attr(feature = "serde", serde(skip))]
910 pub _reserved: [u8; 10],
911}
912
913#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
925#[dbn_record(rtype::IMBALANCE)]
926pub struct ImbalanceMsg {
927 #[pyo3(get)]
929 pub hd: RecordHeader,
930 #[dbn(encode_order(0), index_ts, unix_nanos)]
933 #[pyo3(get, set)]
934 pub ts_recv: u64,
935 #[dbn(fixed_price)]
938 #[pyo3(get, set)]
939 pub ref_price: i64,
940 #[pyo3(get, set)]
942 pub auction_time: u64,
943 #[dbn(fixed_price)]
945 #[pyo3(get, set)]
946 pub cont_book_clr_price: i64,
947 #[dbn(fixed_price)]
949 #[pyo3(get, set)]
950 pub auct_interest_clr_price: i64,
951 #[dbn(fixed_price)]
953 #[pyo3(get, set)]
954 pub ssr_filling_price: i64,
955 #[dbn(fixed_price)]
957 #[pyo3(get, set)]
958 pub ind_match_price: i64,
959 #[dbn(fixed_price)]
961 #[pyo3(get, set)]
962 pub upper_collar: i64,
963 #[dbn(fixed_price)]
965 #[pyo3(get, set)]
966 pub lower_collar: i64,
967 #[pyo3(get, set)]
969 pub paired_qty: u32,
970 #[pyo3(get, set)]
972 pub total_imbalance_qty: u32,
973 #[pyo3(get, set)]
975 pub market_imbalance_qty: u32,
976 #[pyo3(get, set)]
978 pub unpaired_qty: u32,
979 #[dbn(c_char)]
981 pub auction_type: c_char,
982 #[dbn(c_char)]
984 pub side: c_char,
985 #[pyo3(get, set)]
987 pub auction_status: u8,
988 #[pyo3(get, set)]
990 pub freeze_status: u8,
991 #[pyo3(get, set)]
993 pub num_extensions: u8,
994 #[dbn(c_char)]
996 pub unpaired_side: c_char,
997 #[dbn(c_char)]
999 pub significant_imbalance: c_char,
1000 #[doc(hidden)]
1002 #[cfg_attr(feature = "serde", serde(skip))]
1003 pub _reserved: [u8; 1],
1004}
1005
1006#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
1019#[dbn_record(rtype::STATISTICS)]
1020pub struct StatMsg {
1021 pub hd: RecordHeader,
1023 #[dbn(encode_order(0), index_ts, unix_nanos)]
1026 #[pyo3(set)]
1027 pub ts_recv: u64,
1028 #[dbn(unix_nanos)]
1032 #[pyo3(set)]
1033 pub ts_ref: u64,
1034 #[dbn(fixed_price)]
1038 #[pyo3(set)]
1039 pub price: i64,
1040 #[pyo3(set)]
1043 pub quantity: i32,
1044 #[pyo3(set)]
1046 pub sequence: u32,
1047 #[pyo3(set)]
1049 pub ts_in_delta: i32,
1050 #[dbn(fmt_method)]
1053 #[pyo3(set)]
1054 pub stat_type: u16,
1055 #[pyo3(set)]
1058 pub channel_id: u16,
1059 #[dbn(fmt_method)]
1062 #[pyo3(set)]
1063 pub update_action: u8,
1064 #[dbn(fmt_binary)]
1066 #[pyo3(set)]
1067 pub stat_flags: u8,
1068 #[doc(hidden)]
1070 #[cfg_attr(feature = "serde", serde(skip))]
1071 pub _reserved: [u8; 6],
1072}
1073
1074#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
1086#[dbn_record(rtype::ERROR)]
1087pub struct ErrorMsg {
1088 #[pyo3(get)]
1090 pub hd: RecordHeader,
1091 #[dbn(fmt_method)]
1093 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
1094 pub err: [c_char; 302],
1095 #[pyo3(get, set)]
1097 pub code: u8,
1098 #[pyo3(get, set)]
1101 pub is_last: u8,
1102}
1103
1104#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
1117#[dbn_record(rtype::SYMBOL_MAPPING)]
1118pub struct SymbolMappingMsg {
1119 #[pyo3(get, set)]
1121 pub hd: RecordHeader,
1122 #[dbn(fmt_method)]
1125 #[pyo3(get, set)]
1126 pub stype_in: u8,
1127 #[dbn(fmt_method)]
1129 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
1130 pub stype_in_symbol: [c_char; SYMBOL_CSTR_LEN],
1131 #[dbn(fmt_method)]
1133 #[pyo3(get, set)]
1134 pub stype_out: u8,
1135 #[dbn(fmt_method)]
1137 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
1138 pub stype_out_symbol: [c_char; SYMBOL_CSTR_LEN],
1139 #[dbn(unix_nanos)]
1142 #[pyo3(get, set)]
1143 pub start_ts: u64,
1144 #[dbn(unix_nanos)]
1147 #[pyo3(get, set)]
1148 pub end_ts: u64,
1149}
1150
1151#[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))] #[cfg_attr(test, derive(type_layout::TypeLayout))]
1164#[dbn_record(rtype::SYSTEM)]
1165pub struct SystemMsg {
1166 #[pyo3(get, set)]
1168 pub hd: RecordHeader,
1169 #[dbn(fmt_method)]
1171 #[cfg_attr(feature = "serde", serde(with = "conv::cstr_serde"))]
1172 pub msg: [c_char; 303],
1173 #[pyo3(get, set)]
1175 pub code: u8,
1176}
1177
1178pub trait Record {
1181 fn header(&self) -> &RecordHeader;
1184
1185 fn record_size(&self) -> usize {
1187 self.header().record_size()
1188 }
1189
1190 fn rtype(&self) -> crate::Result<RType> {
1197 self.header().rtype()
1198 }
1199
1200 fn publisher(&self) -> crate::Result<Publisher> {
1207 self.header().publisher()
1208 }
1209
1210 fn raw_index_ts(&self) -> u64 {
1215 self.header().ts_event
1216 }
1217
1218 fn index_ts(&self) -> Option<time::OffsetDateTime> {
1224 ts_to_dt(self.raw_index_ts())
1225 }
1226
1227 fn index_date(&self) -> Option<time::Date> {
1231 self.index_ts().map(|dt| dt.date())
1232 }
1233}
1234
1235pub trait RecordMut {
1237 fn header_mut(&mut self) -> &mut RecordHeader;
1240}
1241
1242pub trait HasRType: Record + RecordMut {
1245 fn has_rtype(rtype: u8) -> bool;
1247}
1248
1249#[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 pub rec: T,
1257 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}