1use core::fmt::NumBuffer;
17use std::{collections::VecDeque, ffi::c_char, num::NonZeroUsize};
18
19use ahash::AHashMap;
20use databento::dbn;
21use nautilus_core::{UnixNanos, datetime::NANOSECONDS_IN_SECOND};
22use nautilus_model::{
23 data::{
24 Bar, BarSpecification, BarType, BookOrder, DEPTH10_LEN, Data, InstrumentStatus,
25 OrderBookDelta, OrderBookDepth10, QuoteTick, TradeTick,
26 },
27 enums::{AggregationSource, BarAggregation, FromU16, MarketStatusAction, OrderSide, PriceType},
28 identifiers::{InstrumentId, TradeId},
29};
30
31use super::primitives::{
32 decode_price_or_undef, decode_quantity, parse_aggressor_side, parse_book_action,
33 parse_optional_bool, parse_order_side, parse_status_reason, parse_status_trading_event,
34};
35
36const STEP_ONE: NonZeroUsize = NonZeroUsize::new(1).unwrap();
37
38const BAR_SPEC_1S: BarSpecification = BarSpecification {
39 step: STEP_ONE,
40 aggregation: BarAggregation::Second,
41 price_type: PriceType::Last,
42};
43const BAR_SPEC_1M: BarSpecification = BarSpecification {
44 step: STEP_ONE,
45 aggregation: BarAggregation::Minute,
46 price_type: PriceType::Last,
47};
48const BAR_SPEC_1H: BarSpecification = BarSpecification {
49 step: STEP_ONE,
50 aggregation: BarAggregation::Hour,
51 price_type: PriceType::Last,
52};
53const BAR_SPEC_1D: BarSpecification = BarSpecification {
54 step: STEP_ONE,
55 aggregation: BarAggregation::Day,
56 price_type: PriceType::Last,
57};
58
59pub(super) const BAR_CLOSE_ADJUSTMENT_1S: u64 = NANOSECONDS_IN_SECOND;
60pub(super) const BAR_CLOSE_ADJUSTMENT_1M: u64 = NANOSECONDS_IN_SECOND * 60;
61pub(super) const BAR_CLOSE_ADJUSTMENT_1H: u64 = NANOSECONDS_IN_SECOND * 60 * 60;
62pub(super) const BAR_CLOSE_ADJUSTMENT_1D: u64 = NANOSECONDS_IN_SECOND * 60 * 60 * 24;
63
64const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
66const FNV_PRIME: u64 = 0x0100_0000_01b3;
67
68fn fnv1a_mix_bytes(hash: &mut u64, bytes: &[u8]) {
69 for &byte in bytes {
70 *hash ^= u64::from(byte);
71 *hash = hash.wrapping_mul(FNV_PRIME);
72 }
73}
74
75fn fnv1a_finish_field(hash: &mut u64) {
76 *hash ^= 0xff;
77 *hash = hash.wrapping_mul(FNV_PRIME);
78}
79
80fn fnv1a_mix(hash: &mut u64, bytes: &[u8]) {
81 fnv1a_mix_bytes(hash, bytes);
82 fnv1a_finish_field(hash);
83}
84
85pub(super) fn derive_cmbp_trade_id(
91 instrument_id: InstrumentId,
92 ts_event: u64,
93 ts_recv: u64,
94 price: i64,
95 size: u32,
96 side: c_char,
97) -> TradeId {
98 let mut hash: u64 = FNV_OFFSET_BASIS;
99 fnv1a_mix_bytes(&mut hash, instrument_id.symbol.as_str().as_bytes());
100 fnv1a_mix_bytes(&mut hash, b".");
101 fnv1a_mix_bytes(&mut hash, instrument_id.venue.as_str().as_bytes());
102 fnv1a_finish_field(&mut hash);
103 fnv1a_mix(&mut hash, &ts_event.to_le_bytes());
104 fnv1a_mix(&mut hash, &ts_recv.to_le_bytes());
105 fnv1a_mix(&mut hash, &price.to_le_bytes());
106 fnv1a_mix(&mut hash, &size.to_le_bytes());
107 fnv1a_mix(&mut hash, &[side as u8]);
108 trade_id_from_hash(hash)
109}
110
111fn trade_id_from_hash(hash: u64) -> TradeId {
112 const HEX: &[u8; 16] = b"0123456789abcdef";
113
114 let mut bytes = [0u8; 16];
115 let mut value = hash;
116 for byte in bytes.iter_mut().rev() {
117 *byte = HEX[(value & 0x0f) as usize];
118 value >>= 4;
119 }
120
121 TradeId::from_bytes(&bytes).expect("16 lowercase hex bytes are valid TradeId")
122}
123
124#[inline(always)]
125#[must_use]
126pub(super) fn is_trade_msg(action: c_char) -> bool {
127 action as u8 as char == 'T'
128}
129
130#[inline(always)]
135#[must_use]
136fn has_valid_bid_ask(bid_px: i64, ask_px: i64) -> bool {
137 bid_px != i64::MAX && ask_px != i64::MAX
138}
139
140pub fn decode_mbo_msg(
149 msg: &dbn::MboMsg,
150 instrument_id: InstrumentId,
151 price_precision: u8,
152 ts_init: Option<UnixNanos>,
153 include_trades: bool,
154) -> anyhow::Result<(Option<OrderBookDelta>, Option<TradeTick>)> {
155 let side = parse_order_side(msg.side);
156 if is_trade_msg(msg.action) {
157 if include_trades && msg.size > 0 {
158 let price = decode_price_or_undef(msg.price, price_precision);
159 let size = decode_quantity(msg.size as u64);
160 let aggressor_side = parse_aggressor_side(msg.side);
161 let trade_id = TradeId::new(msg.sequence.format_into(&mut NumBuffer::new()));
162 let ts_event = msg.ts_recv.into();
163 let ts_init = ts_init.unwrap_or(ts_event);
164
165 let trade = TradeTick::new(
166 instrument_id,
167 price,
168 size,
169 aggressor_side,
170 trade_id,
171 ts_event,
172 ts_init,
173 );
174 return Ok((None, Some(trade)));
175 }
176
177 return Ok((None, None));
178 }
179
180 if matches!(msg.action(), Ok(dbn::Action::Fill | dbn::Action::None)) {
188 return Ok((None, None));
189 }
190
191 let action = parse_book_action(msg.action)?;
192 let price = decode_price_or_undef(msg.price, price_precision);
193 let size = decode_quantity(msg.size as u64);
194 let order = BookOrder::new(side, price, size, msg.order_id);
195
196 let ts_event = msg.ts_recv.into();
197 let ts_init = ts_init.unwrap_or(ts_event);
198
199 let sequence = if msg.flags.is_snapshot() {
202 0
203 } else {
204 msg.sequence
205 };
206
207 let delta = OrderBookDelta::new(
208 instrument_id,
209 action,
210 order,
211 msg.flags.raw(),
212 sequence.into(),
213 ts_event,
214 ts_init,
215 );
216
217 Ok((Some(delta), None))
218}
219
220#[derive(Debug)]
221struct QueuedMboDelta {
222 delta: OrderBookDelta,
223 ready: bool,
224}
225
226#[derive(Debug, Default)]
231pub(crate) struct MboDeltaBuffer {
232 queue: VecDeque<QueuedMboDelta>,
233 tail: Option<(InstrumentId, u64)>,
234 tails: AHashMap<InstrumentId, u64>,
235 head: u64,
236 next: u64,
237}
238
239impl MboDeltaBuffer {
240 pub(crate) fn push(
241 &mut self,
242 msg: &dbn::MboMsg,
243 instrument_id: InstrumentId,
244 delta: Option<OrderBookDelta>,
245 ) {
246 if let Some(delta) = delta {
247 self.release(instrument_id, 0);
248
249 let index = self.next;
250 self.next = self.next.checked_add(1).expect("MBO delta index overflow");
251 let ready = msg.flags.is_last();
252 self.queue.push_back(QueuedMboDelta { delta, ready });
253
254 if !ready
255 && let Some((tail_instrument_id, tail)) = self.tail.replace((instrument_id, index))
256 {
257 self.tails.insert(tail_instrument_id, tail);
258 }
259 } else if msg.flags.is_last() {
260 self.release(instrument_id, dbn::flags::LAST);
261 }
262 }
263
264 pub(crate) fn pop_ready(&mut self) -> Option<OrderBookDelta> {
265 if !self.queue.front().is_some_and(|queued| queued.ready) {
266 return None;
267 }
268
269 self.head = self.head.checked_add(1).expect("MBO delta index overflow");
270 self.queue.pop_front().map(|queued| queued.delta)
271 }
272
273 pub(crate) fn finish(&mut self) {
274 self.tail = None;
275 self.tails.clear();
276
277 for queued in &mut self.queue {
278 queued.ready = true;
279 }
280 }
281
282 fn release(&mut self, instrument_id: InstrumentId, flags: u8) {
283 let tail = match self.tail {
284 Some((tail_instrument_id, tail)) if tail_instrument_id == instrument_id => {
285 self.tail = None;
286 tail
287 }
288 _ => {
289 let Some(tail) = self.tails.remove(&instrument_id) else {
290 return;
291 };
292 tail
293 }
294 };
295 let offset = tail
296 .checked_sub(self.head)
297 .and_then(|offset| usize::try_from(offset).ok())
298 .expect("MBO delta tail must be queued");
299 let queued = self
300 .queue
301 .get_mut(offset)
302 .expect("MBO delta tail must be queued");
303 queued.delta.flags |= flags;
304 queued.ready = true;
305 }
306}
307
308pub fn decode_trade_msg(
314 msg: &dbn::TradeMsg,
315 instrument_id: InstrumentId,
316 price_precision: u8,
317 ts_init: Option<UnixNanos>,
318) -> anyhow::Result<TradeTick> {
319 let ts_event = msg.ts_recv.into();
320 let ts_init = ts_init.unwrap_or(ts_event);
321
322 let trade = TradeTick::new(
323 instrument_id,
324 decode_price_or_undef(msg.price, price_precision),
325 decode_quantity(msg.size as u64),
326 parse_aggressor_side(msg.side),
327 TradeId::new(msg.sequence.format_into(&mut NumBuffer::new())),
328 ts_event,
329 ts_init,
330 );
331
332 Ok(trade)
333}
334
335pub fn decode_tbbo_msg(
344 msg: &dbn::TbboMsg,
345 instrument_id: InstrumentId,
346 price_precision: u8,
347 ts_init: Option<UnixNanos>,
348) -> anyhow::Result<(Option<QuoteTick>, TradeTick)> {
349 let top_level = &msg.levels[0];
350 let ts_event = msg.ts_recv.into();
351 let ts_init = ts_init.unwrap_or(ts_event);
352
353 let maybe_quote = if has_valid_bid_ask(top_level.bid_px, top_level.ask_px) {
354 Some(QuoteTick::new(
355 instrument_id,
356 decode_price_or_undef(top_level.bid_px, price_precision),
357 decode_price_or_undef(top_level.ask_px, price_precision),
358 decode_quantity(top_level.bid_sz as u64),
359 decode_quantity(top_level.ask_sz as u64),
360 ts_event,
361 ts_init,
362 ))
363 } else {
364 None
365 };
366
367 let trade = TradeTick::new(
368 instrument_id,
369 decode_price_or_undef(msg.price, price_precision),
370 decode_quantity(msg.size as u64),
371 parse_aggressor_side(msg.side),
372 TradeId::new(msg.sequence.format_into(&mut NumBuffer::new())),
373 ts_event,
374 ts_init,
375 );
376
377 Ok((maybe_quote, trade))
378}
379
380pub fn decode_mbp1_msg(
388 msg: &dbn::Mbp1Msg,
389 instrument_id: InstrumentId,
390 price_precision: u8,
391 ts_init: Option<UnixNanos>,
392 include_trades: bool,
393) -> anyhow::Result<(Option<QuoteTick>, Option<TradeTick>)> {
394 let top_level = &msg.levels[0];
395 let ts_event = msg.ts_recv.into();
396 let ts_init = ts_init.unwrap_or(ts_event);
397
398 let maybe_quote = if has_valid_bid_ask(top_level.bid_px, top_level.ask_px) {
399 Some(QuoteTick::new(
400 instrument_id,
401 decode_price_or_undef(top_level.bid_px, price_precision),
402 decode_price_or_undef(top_level.ask_px, price_precision),
403 decode_quantity(top_level.bid_sz as u64),
404 decode_quantity(top_level.ask_sz as u64),
405 ts_event,
406 ts_init,
407 ))
408 } else {
409 None
410 };
411
412 let maybe_trade = if include_trades && is_trade_msg(msg.action) {
413 Some(TradeTick::new(
414 instrument_id,
415 decode_price_or_undef(msg.price, price_precision),
416 decode_quantity(msg.size as u64),
417 parse_aggressor_side(msg.side),
418 TradeId::new(msg.sequence.format_into(&mut NumBuffer::new())),
419 ts_event,
420 ts_init,
421 ))
422 } else {
423 None
424 };
425
426 Ok((maybe_quote, maybe_trade))
427}
428
429pub fn decode_bbo_msg(
437 msg: &dbn::BboMsg,
438 instrument_id: InstrumentId,
439 price_precision: u8,
440 ts_init: Option<UnixNanos>,
441) -> anyhow::Result<Option<QuoteTick>> {
442 let top_level = &msg.levels[0];
443 if !has_valid_bid_ask(top_level.bid_px, top_level.ask_px) {
444 return Ok(None);
445 }
446
447 let ts_event = msg.ts_recv.into();
448 let ts_init = ts_init.unwrap_or(ts_event);
449
450 let quote = QuoteTick::new(
451 instrument_id,
452 decode_price_or_undef(top_level.bid_px, price_precision),
453 decode_price_or_undef(top_level.ask_px, price_precision),
454 decode_quantity(top_level.bid_sz as u64),
455 decode_quantity(top_level.ask_sz as u64),
456 ts_event,
457 ts_init,
458 );
459
460 Ok(Some(quote))
461}
462
463pub fn decode_mbp10_msg(
469 msg: &dbn::Mbp10Msg,
470 instrument_id: InstrumentId,
471 price_precision: u8,
472 ts_init: Option<UnixNanos>,
473) -> anyhow::Result<OrderBookDepth10> {
474 let mut bids = [BookOrder::default(); DEPTH10_LEN];
475 let mut asks = [BookOrder::default(); DEPTH10_LEN];
476 let mut bid_counts = [0u32; DEPTH10_LEN];
477 let mut ask_counts = [0u32; DEPTH10_LEN];
478
479 for (index, level) in msg.levels.iter().enumerate() {
480 if level.bid_px != i64::MAX {
483 bids[index] = BookOrder::new(
484 OrderSide::Buy,
485 decode_price_or_undef(level.bid_px, price_precision),
486 decode_quantity(level.bid_sz as u64),
487 0,
488 );
489 }
490
491 if level.ask_px != i64::MAX {
492 asks[index] = BookOrder::new(
493 OrderSide::Sell,
494 decode_price_or_undef(level.ask_px, price_precision),
495 decode_quantity(level.ask_sz as u64),
496 0,
497 );
498 }
499
500 bid_counts[index] = level.bid_ct;
501 ask_counts[index] = level.ask_ct;
502 }
503
504 let ts_event = msg.ts_recv.into();
505 let ts_init = ts_init.unwrap_or(ts_event);
506
507 let depth = OrderBookDepth10::new(
508 instrument_id,
509 bids,
510 asks,
511 bid_counts,
512 ask_counts,
513 msg.flags.raw(),
514 msg.sequence.into(),
515 ts_event,
516 ts_init,
517 );
518
519 Ok(depth)
520}
521
522pub fn decode_cmbp1_msg(
531 msg: &dbn::Cmbp1Msg,
532 instrument_id: InstrumentId,
533 price_precision: u8,
534 ts_init: Option<UnixNanos>,
535 include_trades: bool,
536) -> anyhow::Result<(Option<QuoteTick>, Option<TradeTick>)> {
537 let top_level = &msg.levels[0];
538 let ts_event = msg.ts_recv.into();
539 let ts_init = ts_init.unwrap_or(ts_event);
540
541 let maybe_quote = if has_valid_bid_ask(top_level.bid_px, top_level.ask_px) {
542 Some(QuoteTick::new(
543 instrument_id,
544 decode_price_or_undef(top_level.bid_px, price_precision),
545 decode_price_or_undef(top_level.ask_px, price_precision),
546 decode_quantity(top_level.bid_sz as u64),
547 decode_quantity(top_level.ask_sz as u64),
548 ts_event,
549 ts_init,
550 ))
551 } else {
552 None
553 };
554
555 let maybe_trade = if include_trades && is_trade_msg(msg.action) {
556 let trade_id = derive_cmbp_trade_id(
558 instrument_id,
559 msg.hd.ts_event,
560 msg.ts_recv,
561 msg.price,
562 msg.size,
563 msg.side,
564 );
565 Some(TradeTick::new(
566 instrument_id,
567 decode_price_or_undef(msg.price, price_precision),
568 decode_quantity(msg.size as u64),
569 parse_aggressor_side(msg.side),
570 trade_id,
571 ts_event,
572 ts_init,
573 ))
574 } else {
575 None
576 };
577
578 Ok((maybe_quote, maybe_trade))
579}
580
581pub fn decode_cbbo_msg(
589 msg: &dbn::CbboMsg,
590 instrument_id: InstrumentId,
591 price_precision: u8,
592 ts_init: Option<UnixNanos>,
593) -> anyhow::Result<Option<QuoteTick>> {
594 let top_level = &msg.levels[0];
595 if !has_valid_bid_ask(top_level.bid_px, top_level.ask_px) {
596 return Ok(None);
597 }
598
599 let ts_event = msg.ts_recv.into();
600 let ts_init = ts_init.unwrap_or(ts_event);
601
602 let quote = QuoteTick::new(
603 instrument_id,
604 decode_price_or_undef(top_level.bid_px, price_precision),
605 decode_price_or_undef(top_level.ask_px, price_precision),
606 decode_quantity(top_level.bid_sz as u64),
607 decode_quantity(top_level.ask_sz as u64),
608 ts_event,
609 ts_init,
610 );
611
612 Ok(Some(quote))
613}
614
615pub fn decode_tcbbo_msg(
624 msg: &dbn::TcbboMsg,
625 instrument_id: InstrumentId,
626 price_precision: u8,
627 ts_init: Option<UnixNanos>,
628) -> anyhow::Result<(Option<QuoteTick>, TradeTick)> {
629 let (maybe_quote, maybe_trade) =
630 decode_cmbp1_msg(msg, instrument_id, price_precision, ts_init, true)?;
631 let trade = maybe_trade.ok_or_else(|| {
632 anyhow::anyhow!(
633 "Invalid `TcbboMsg`: expected trade action, was {}",
634 msg.action as u8 as char
635 )
636 })?;
637
638 Ok((maybe_quote, trade))
639}
640
641pub fn decode_bar_type(
645 msg: &dbn::OhlcvMsg,
646 instrument_id: InstrumentId,
647) -> anyhow::Result<BarType> {
648 let bar_type = match msg.hd.rtype {
649 32 => {
650 BarType::new(instrument_id, BAR_SPEC_1S, AggregationSource::External)
652 }
653 33 => {
654 BarType::new(instrument_id, BAR_SPEC_1M, AggregationSource::External)
656 }
657 34 => {
658 BarType::new(instrument_id, BAR_SPEC_1H, AggregationSource::External)
660 }
661 35 => {
662 BarType::new(instrument_id, BAR_SPEC_1D, AggregationSource::External)
664 }
665 36 => {
666 BarType::new(instrument_id, BAR_SPEC_1D, AggregationSource::External)
668 }
669 _ => anyhow::bail!(
670 "`rtype` is not a supported bar aggregation, was {}",
671 msg.hd.rtype
672 ),
673 };
674
675 Ok(bar_type)
676}
677
678pub fn decode_ts_event_adjustment(msg: &dbn::OhlcvMsg) -> anyhow::Result<UnixNanos> {
682 let adjustment = match msg.hd.rtype {
683 32 => {
684 BAR_CLOSE_ADJUSTMENT_1S
686 }
687 33 => {
688 BAR_CLOSE_ADJUSTMENT_1M
690 }
691 34 => {
692 BAR_CLOSE_ADJUSTMENT_1H
694 }
695 35 | 36 => {
696 BAR_CLOSE_ADJUSTMENT_1D
698 }
699 _ => anyhow::bail!(
700 "`rtype` is not a supported bar aggregation, was {}",
701 msg.hd.rtype
702 ),
703 };
704
705 Ok(adjustment.into())
706}
707
708pub fn decode_ohlcv_msg(
712 msg: &dbn::OhlcvMsg,
713 instrument_id: InstrumentId,
714 price_precision: u8,
715 ts_init: Option<UnixNanos>,
716 timestamp_on_close: bool,
717) -> anyhow::Result<Bar> {
718 let bar_type = decode_bar_type(msg, instrument_id)?;
719 let ts_event_adjustment = decode_ts_event_adjustment(msg)?;
720
721 let ts_event_raw = msg.hd.ts_event.into();
722 let ts_close = ts_event_raw + ts_event_adjustment;
723 let ts_init = ts_init.unwrap_or(ts_close); let ts_event = if timestamp_on_close {
726 ts_close
727 } else {
728 ts_event_raw
729 };
730
731 let bar = Bar::new(
732 bar_type,
733 decode_price_or_undef(msg.open, price_precision),
734 decode_price_or_undef(msg.high, price_precision),
735 decode_price_or_undef(msg.low, price_precision),
736 decode_price_or_undef(msg.close, price_precision),
737 decode_quantity(msg.volume),
738 ts_event,
739 ts_init,
740 );
741
742 Ok(bar)
743}
744
745pub fn decode_status_msg(
751 msg: &dbn::StatusMsg,
752 instrument_id: InstrumentId,
753 ts_init: Option<UnixNanos>,
754) -> anyhow::Result<InstrumentStatus> {
755 let ts_event = msg.hd.ts_event.into();
756 let ts_init = ts_init.unwrap_or(ts_event);
757
758 let action = MarketStatusAction::from_u16(msg.action)
759 .ok_or_else(|| anyhow::anyhow!("Invalid `MarketStatusAction` value: {}", msg.action))?;
760
761 let status = InstrumentStatus::new(
762 instrument_id,
763 action,
764 ts_event,
765 ts_init,
766 parse_status_reason(msg.reason)?,
767 parse_status_trading_event(msg.trading_event)?,
768 parse_optional_bool(msg.is_trading),
769 parse_optional_bool(msg.is_quoting),
770 parse_optional_bool(msg.is_short_sell_restricted),
771 );
772
773 Ok(status)
774}
775
776pub fn decode_record(
780 record: &dbn::RecordRef,
781 instrument_id: InstrumentId,
782 price_precision: u8,
783 ts_init: Option<UnixNanos>,
784 include_trades: bool,
785 bars_timestamp_on_close: bool,
786) -> anyhow::Result<(Option<Data>, Option<Data>)> {
787 let result = if let Some(msg) = record.get::<dbn::MboMsg>() {
788 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
789 let result = decode_mbo_msg(
790 msg,
791 instrument_id,
792 price_precision,
793 Some(ts_init),
794 include_trades,
795 )?;
796
797 match result {
798 (Some(delta), None) => (Some(Data::Delta(delta)), None),
799 (None, Some(trade)) => (Some(Data::Trade(trade)), None),
800 (None, None) => (None, None),
801 _ => anyhow::bail!("Invalid `MboMsg` parsing combination"),
802 }
803 } else if let Some(msg) = record.get::<dbn::TradeMsg>() {
804 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
805 let trade = decode_trade_msg(msg, instrument_id, price_precision, Some(ts_init))?;
806 (Some(Data::Trade(trade)), None)
807 } else if let Some(msg) = record.get::<dbn::Mbp1Msg>() {
808 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
809 let (maybe_quote, maybe_trade) = decode_mbp1_msg(
810 msg,
811 instrument_id,
812 price_precision,
813 Some(ts_init),
814 include_trades,
815 )?;
816 (maybe_quote.map(Data::Quote), maybe_trade.map(Data::Trade))
817 } else if let Some(msg) = record.get::<dbn::Bbo1SMsg>() {
818 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
819 let maybe_quote = decode_bbo_msg(msg, instrument_id, price_precision, Some(ts_init))?;
820 (maybe_quote.map(Data::Quote), None)
821 } else if let Some(msg) = record.get::<dbn::Bbo1MMsg>() {
822 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
823 let maybe_quote = decode_bbo_msg(msg, instrument_id, price_precision, Some(ts_init))?;
824 (maybe_quote.map(Data::Quote), None)
825 } else if let Some(msg) = record.get::<dbn::Mbp10Msg>() {
826 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
827 let depth = decode_mbp10_msg(msg, instrument_id, price_precision, Some(ts_init))?;
828 (Some(Data::from(depth)), None)
829 } else if let Some(msg) = record.get::<dbn::OhlcvMsg>() {
830 let bar = decode_ohlcv_msg(
833 msg,
834 instrument_id,
835 price_precision,
836 ts_init,
837 bars_timestamp_on_close,
838 )?;
839 (Some(Data::Bar(bar)), None)
840 } else if let Some(msg) = record.get::<dbn::Cmbp1Msg>() {
841 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
842 if msg.hd.rtype == dbn::enums::rtype::TCBBO {
843 let (maybe_quote, trade) =
844 decode_tcbbo_msg(msg, instrument_id, price_precision, Some(ts_init))?;
845 (maybe_quote.map(Data::Quote), Some(Data::Trade(trade)))
846 } else {
847 let (maybe_quote, maybe_trade) = decode_cmbp1_msg(
848 msg,
849 instrument_id,
850 price_precision,
851 Some(ts_init),
852 include_trades,
853 )?;
854 (maybe_quote.map(Data::Quote), maybe_trade.map(Data::Trade))
855 }
856 } else if let Some(msg) = record.get::<dbn::TbboMsg>() {
857 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
859 let (maybe_quote, trade) =
860 decode_tbbo_msg(msg, instrument_id, price_precision, Some(ts_init))?;
861 (maybe_quote.map(Data::Quote), Some(Data::Trade(trade)))
862 } else if let Some(msg) = record.get::<dbn::CbboMsg>() {
863 let ts_init = determine_timestamp(ts_init, msg.ts_recv.into());
864 let maybe_quote = decode_cbbo_msg(msg, instrument_id, price_precision, Some(ts_init))?;
865 (maybe_quote.map(Data::Quote), None)
866 } else {
867 anyhow::bail!("DBN message type is not currently supported")
868 };
869
870 Ok(result)
871}
872
873const fn determine_timestamp(ts_init: Option<UnixNanos>, msg_timestamp: UnixNanos) -> UnixNanos {
874 match ts_init {
875 Some(ts_init) => ts_init,
876 None => msg_timestamp,
877 }
878}