nautilus_binance/spot/sbe/stream/
best_bid_ask.rs1use ustr::Ustr;
30
31use super::{MessageHeader, StreamDecodeError};
32use crate::spot::sbe::cursor::SbeCursor;
33
34#[derive(Debug, Clone)]
36pub struct BestBidAskStreamEvent {
37 pub event_time_us: i64,
39 pub book_update_id: i64,
41 pub price_exponent: i8,
43 pub qty_exponent: i8,
45 pub bid_price_mantissa: i64,
47 pub bid_qty_mantissa: i64,
49 pub ask_price_mantissa: i64,
51 pub ask_qty_mantissa: i64,
53 pub symbol: Ustr,
55}
56
57impl BestBidAskStreamEvent {
58 pub const BLOCK_LENGTH: usize = 50;
60
61 pub const MIN_BUFFER_SIZE: usize = MessageHeader::ENCODED_LENGTH + Self::BLOCK_LENGTH + 1;
63
64 pub fn decode(buf: &[u8]) -> Result<Self, StreamDecodeError> {
70 let header = MessageHeader::decode(buf)?;
71 header.validate_schema()?;
72 Self::decode_validated(buf)
73 }
74
75 pub(crate) fn decode_validated(buf: &[u8]) -> Result<Self, StreamDecodeError> {
77 let mut cursor = SbeCursor::new_at(buf, MessageHeader::ENCODED_LENGTH);
78 Self::decode_body(&mut cursor)
79 }
80
81 #[inline]
82 fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, StreamDecodeError> {
83 let event_time_us = cursor.read_i64_le()?;
84 let book_update_id = cursor.read_i64_le()?;
85 let price_exponent = cursor.read_i8()?;
86 let qty_exponent = cursor.read_i8()?;
87 let bid_price_mantissa = cursor.read_i64_le()?;
88 let bid_qty_mantissa = cursor.read_i64_le()?;
89 let ask_price_mantissa = cursor.read_i64_le()?;
90 let ask_qty_mantissa = cursor.read_i64_le()?;
91
92 let symbol = Ustr::from(cursor.read_var_string8_ref()?);
93
94 Ok(Self {
95 event_time_us,
96 book_update_id,
97 price_exponent,
98 qty_exponent,
99 bid_price_mantissa,
100 bid_qty_mantissa,
101 ask_price_mantissa,
102 ask_qty_mantissa,
103 symbol,
104 })
105 }
106
107 #[inline]
109 #[must_use]
110 pub fn bid_price(&self) -> f64 {
111 super::mantissa_to_f64(self.bid_price_mantissa, self.price_exponent)
112 }
113
114 #[inline]
116 #[must_use]
117 pub fn bid_qty(&self) -> f64 {
118 super::mantissa_to_f64(self.bid_qty_mantissa, self.qty_exponent)
119 }
120
121 #[inline]
123 #[must_use]
124 pub fn ask_price(&self) -> f64 {
125 super::mantissa_to_f64(self.ask_price_mantissa, self.price_exponent)
126 }
127
128 #[inline]
130 #[must_use]
131 pub fn ask_qty(&self) -> f64 {
132 super::mantissa_to_f64(self.ask_qty_mantissa, self.qty_exponent)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use rstest::rstest;
139
140 use super::*;
141 use crate::spot::sbe::stream::{STREAM_SCHEMA_ID, template_id};
142
143 fn make_valid_buffer() -> Vec<u8> {
144 let mut buf = vec![0u8; 70];
145
146 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());
149 buf[4..6].copy_from_slice(&STREAM_SCHEMA_ID.to_le_bytes());
150 buf[6..8].copy_from_slice(&0u16.to_le_bytes()); let body = &mut buf[8..];
154 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;
165 body[51..58].copy_from_slice(b"BTCUSDT");
166
167 buf
168 }
169
170 #[rstest]
171 fn test_decode_valid() {
172 let buf = make_valid_buffer();
173 let event = BestBidAskStreamEvent::decode(&buf).unwrap();
174
175 assert_eq!(event.event_time_us, 1000000);
176 assert_eq!(event.book_update_id, 12345);
177 assert_eq!(event.price_exponent, -2);
178 assert_eq!(event.qty_exponent, -8);
179 assert_eq!(event.symbol, "BTCUSDT");
180 assert!((event.bid_price() - 42000.0).abs() < 0.01);
181 }
182
183 #[rstest]
184 fn test_decode_truncated_header() {
185 let buf = [0u8; 5];
186 let err = BestBidAskStreamEvent::decode(&buf).unwrap_err();
187 assert!(matches!(err, StreamDecodeError::BufferTooShort { .. }));
188 }
189
190 #[rstest]
191 fn test_decode_truncated_body() {
192 let mut buf = make_valid_buffer();
193 buf.truncate(40); let err = BestBidAskStreamEvent::decode(&buf).unwrap_err();
195 assert!(matches!(err, StreamDecodeError::BufferTooShort { .. }));
196 }
197
198 #[rstest]
199 fn test_decode_wrong_schema() {
200 let mut buf = make_valid_buffer();
201 buf[4..6].copy_from_slice(&99u16.to_le_bytes()); let err = BestBidAskStreamEvent::decode(&buf).unwrap_err();
203 assert!(matches!(err, StreamDecodeError::SchemaMismatch { .. }));
204 }
205
206 #[rstest]
207 fn test_decode_validated_matches_decode() {
208 let buf = make_valid_buffer();
209
210 let decode_event = BestBidAskStreamEvent::decode(&buf).unwrap();
211 let validated_event = BestBidAskStreamEvent::decode_validated(&buf).unwrap();
212
213 assert_eq!(validated_event.event_time_us, decode_event.event_time_us);
214 assert_eq!(validated_event.book_update_id, decode_event.book_update_id);
215 assert_eq!(validated_event.price_exponent, decode_event.price_exponent);
216 assert_eq!(validated_event.qty_exponent, decode_event.qty_exponent);
217 assert_eq!(
218 validated_event.bid_price_mantissa,
219 decode_event.bid_price_mantissa
220 );
221 assert_eq!(validated_event.symbol, decode_event.symbol);
222 }
223}