nautilus_binance/common/sbe/stream/
best_bid_ask.rs1use super::{MessageHeader, StreamDecodeError, decode_var_string8, read_i8, read_i64_le};
30
31#[derive(Debug, Clone)]
33pub struct BestBidAskStreamEvent {
34 pub event_time_us: i64,
36 pub book_update_id: i64,
38 pub price_exponent: i8,
40 pub qty_exponent: i8,
42 pub bid_price_mantissa: i64,
44 pub bid_qty_mantissa: i64,
46 pub ask_price_mantissa: i64,
48 pub ask_qty_mantissa: i64,
50 pub symbol: String,
52}
53
54impl BestBidAskStreamEvent {
55 pub const BLOCK_LENGTH: usize = 50;
57
58 pub const MIN_BUFFER_SIZE: usize = MessageHeader::ENCODED_LENGTH + Self::BLOCK_LENGTH + 1;
60
61 pub fn decode(buf: &[u8]) -> Result<Self, StreamDecodeError> {
67 let header = MessageHeader::decode(buf)?;
68 header.validate_schema()?;
69
70 let body = &buf[MessageHeader::ENCODED_LENGTH..];
71
72 if body.len() < Self::BLOCK_LENGTH + 1 {
73 return Err(StreamDecodeError::BufferTooShort {
74 expected: Self::MIN_BUFFER_SIZE,
75 actual: buf.len(),
76 });
77 }
78
79 let event_time_us = read_i64_le(body, 0)?;
80 let book_update_id = read_i64_le(body, 8)?;
81 let price_exponent = read_i8(body, 16)?;
82 let qty_exponent = read_i8(body, 17)?;
83 let bid_price_mantissa = read_i64_le(body, 18)?;
84 let bid_qty_mantissa = read_i64_le(body, 26)?;
85 let ask_price_mantissa = read_i64_le(body, 34)?;
86 let ask_qty_mantissa = read_i64_le(body, 42)?;
87
88 let (symbol, _) = decode_var_string8(&body[50..])?;
89
90 Ok(Self {
91 event_time_us,
92 book_update_id,
93 price_exponent,
94 qty_exponent,
95 bid_price_mantissa,
96 bid_qty_mantissa,
97 ask_price_mantissa,
98 ask_qty_mantissa,
99 symbol,
100 })
101 }
102
103 #[inline]
105 #[must_use]
106 pub fn bid_price(&self) -> f64 {
107 super::mantissa_to_f64(self.bid_price_mantissa, self.price_exponent)
108 }
109
110 #[inline]
112 #[must_use]
113 pub fn bid_qty(&self) -> f64 {
114 super::mantissa_to_f64(self.bid_qty_mantissa, self.qty_exponent)
115 }
116
117 #[inline]
119 #[must_use]
120 pub fn ask_price(&self) -> f64 {
121 super::mantissa_to_f64(self.ask_price_mantissa, self.price_exponent)
122 }
123
124 #[inline]
126 #[must_use]
127 pub fn ask_qty(&self) -> f64 {
128 super::mantissa_to_f64(self.ask_qty_mantissa, self.qty_exponent)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use rstest::rstest;
135
136 use super::*;
137 use crate::common::sbe::stream::{STREAM_SCHEMA_ID, template_id};
138
139 fn make_valid_buffer() -> Vec<u8> {
140 let mut buf = vec![0u8; 70];
141
142 buf[0..2].copy_from_slice(&50u16.to_le_bytes()); buf[2..4].copy_from_slice(&template_id::BEST_BID_ASK_STREAM_EVENT.to_le_bytes());
145 buf[4..6].copy_from_slice(&STREAM_SCHEMA_ID.to_le_bytes());
146 buf[6..8].copy_from_slice(&0u16.to_le_bytes()); let body = &mut buf[8..];
150 body[0..8].copy_from_slice(&1000000i64.to_le_bytes()); body[8..16].copy_from_slice(&12345i64.to_le_bytes()); body[16] = (-2i8) as u8; body[17] = (-8i8) as u8; body[18..26].copy_from_slice(&4200000i64.to_le_bytes()); body[26..34].copy_from_slice(&100000000i64.to_le_bytes()); body[34..42].copy_from_slice(&4200100i64.to_le_bytes()); body[42..50].copy_from_slice(&200000000i64.to_le_bytes()); body[50] = 7;
161 body[51..58].copy_from_slice(b"BTCUSDT");
162
163 buf
164 }
165
166 #[rstest]
167 fn test_decode_valid() {
168 let buf = make_valid_buffer();
169 let event = BestBidAskStreamEvent::decode(&buf).unwrap();
170
171 assert_eq!(event.event_time_us, 1000000);
172 assert_eq!(event.book_update_id, 12345);
173 assert_eq!(event.price_exponent, -2);
174 assert_eq!(event.qty_exponent, -8);
175 assert_eq!(event.symbol, "BTCUSDT");
176 assert!((event.bid_price() - 42000.0).abs() < 0.01);
177 }
178
179 #[rstest]
180 fn test_decode_truncated_header() {
181 let buf = [0u8; 5];
182 let err = BestBidAskStreamEvent::decode(&buf).unwrap_err();
183 assert!(matches!(err, StreamDecodeError::BufferTooShort { .. }));
184 }
185
186 #[rstest]
187 fn test_decode_truncated_body() {
188 let mut buf = make_valid_buffer();
189 buf.truncate(40); let err = BestBidAskStreamEvent::decode(&buf).unwrap_err();
191 assert!(matches!(err, StreamDecodeError::BufferTooShort { .. }));
192 }
193
194 #[rstest]
195 fn test_decode_wrong_schema() {
196 let mut buf = make_valid_buffer();
197 buf[4..6].copy_from_slice(&99u16.to_le_bytes()); let err = BestBidAskStreamEvent::decode(&buf).unwrap_err();
199 assert!(matches!(err, StreamDecodeError::SchemaMismatch { .. }));
200 }
201}