nautilus_binance/spot/sbe/stream/
depth_diff.rs1use ustr::Ustr;
33
34use super::{MessageHeader, PriceLevel, StreamDecodeError};
35use crate::spot::sbe::cursor::SbeCursor;
36
37#[derive(Debug, Clone)]
39pub struct DepthDiffStreamEvent {
40 pub event_time_us: i64,
42 pub first_book_update_id: i64,
44 pub last_book_update_id: i64,
46 pub price_exponent: i8,
48 pub qty_exponent: i8,
50 pub bids: Vec<PriceLevel>,
52 pub asks: Vec<PriceLevel>,
54 pub symbol: Ustr,
56}
57
58impl DepthDiffStreamEvent {
59 pub const BLOCK_LENGTH: usize = 26;
61
62 pub fn decode(buf: &[u8]) -> Result<Self, StreamDecodeError> {
69 let header = MessageHeader::decode(buf)?;
70 header.validate_schema()?;
71 Self::decode_validated(buf)
72 }
73
74 pub(crate) fn decode_validated(buf: &[u8]) -> Result<Self, StreamDecodeError> {
76 let mut cursor = SbeCursor::new_at(buf, MessageHeader::ENCODED_LENGTH);
77 Self::decode_body(&mut cursor)
78 }
79
80 #[inline]
81 fn decode_body(cursor: &mut SbeCursor<'_>) -> Result<Self, StreamDecodeError> {
82 let event_time_us = cursor.read_i64_le()?;
83 let first_book_update_id = cursor.read_i64_le()?;
84 let last_book_update_id = cursor.read_i64_le()?;
85 let price_exponent = cursor.read_i8()?;
86 let qty_exponent = cursor.read_i8()?;
87
88 let (bid_block_length, num_bids) = cursor.read_group_header_16()?;
89 let bids = cursor.read_group(bid_block_length, u32::from(num_bids), PriceLevel::decode)?;
90
91 let (ask_block_length, num_asks) = cursor.read_group_header_16()?;
92 let asks = cursor.read_group(ask_block_length, u32::from(num_asks), PriceLevel::decode)?;
93
94 let symbol = Ustr::from(cursor.read_var_string8_ref()?);
95
96 Ok(Self {
97 event_time_us,
98 first_book_update_id,
99 last_book_update_id,
100 price_exponent,
101 qty_exponent,
102 bids,
103 asks,
104 symbol,
105 })
106 }
107
108 #[inline]
110 #[must_use]
111 pub fn level_price(&self, level: &PriceLevel) -> f64 {
112 super::mantissa_to_f64(level.price_mantissa, self.price_exponent)
113 }
114
115 #[inline]
117 #[must_use]
118 pub fn level_qty(&self, level: &PriceLevel) -> f64 {
119 super::mantissa_to_f64(level.qty_mantissa, self.qty_exponent)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use rstest::rstest;
126
127 use super::*;
128 use crate::spot::sbe::stream::{STREAM_SCHEMA_ID, template_id};
129
130 fn make_valid_buffer(num_bids: usize, num_asks: usize) -> Vec<u8> {
131 let level_block_len = 16u16;
132 let body_size = 26
133 + 4
134 + (num_bids * level_block_len as usize)
135 + 4
136 + (num_asks * level_block_len as usize)
137 + 8;
138 let mut buf = vec![0u8; 8 + body_size];
139
140 buf[0..2].copy_from_slice(&26u16.to_le_bytes()); buf[2..4].copy_from_slice(&template_id::DEPTH_DIFF_STREAM_EVENT.to_le_bytes());
143 buf[4..6].copy_from_slice(&STREAM_SCHEMA_ID.to_le_bytes());
144 buf[6..8].copy_from_slice(&0u16.to_le_bytes()); let body = &mut buf[8..];
148 body[0..8].copy_from_slice(&1000000i64.to_le_bytes()); body[8..16].copy_from_slice(&12345i64.to_le_bytes()); body[16..24].copy_from_slice(&12350i64.to_le_bytes()); body[24] = (-2i8) as u8; body[25] = (-8i8) as u8; let mut offset = 26;
155
156 body[offset..offset + 2].copy_from_slice(&level_block_len.to_le_bytes());
158 body[offset + 2..offset + 4].copy_from_slice(&(num_bids as u16).to_le_bytes());
159 offset += 4;
160
161 for i in 0..num_bids {
163 body[offset..offset + 8].copy_from_slice(&(4200000i64 - i as i64 * 100).to_le_bytes());
164 body[offset + 8..offset + 16].copy_from_slice(&100000000i64.to_le_bytes());
165 offset += level_block_len as usize;
166 }
167
168 body[offset..offset + 2].copy_from_slice(&level_block_len.to_le_bytes());
170 body[offset + 2..offset + 4].copy_from_slice(&(num_asks as u16).to_le_bytes());
171 offset += 4;
172
173 for i in 0..num_asks {
175 body[offset..offset + 8].copy_from_slice(&(4200100i64 + i as i64 * 100).to_le_bytes());
176 body[offset + 8..offset + 16].copy_from_slice(&200000000i64.to_le_bytes());
177 offset += level_block_len as usize;
178 }
179
180 body[offset] = 7;
182 body[offset + 1..offset + 8].copy_from_slice(b"BTCUSDT");
183
184 buf
185 }
186
187 #[rstest]
188 fn test_decode_valid() {
189 let buf = make_valid_buffer(3, 2);
190 let event = DepthDiffStreamEvent::decode(&buf).unwrap();
191
192 assert_eq!(event.event_time_us, 1000000);
193 assert_eq!(event.first_book_update_id, 12345);
194 assert_eq!(event.last_book_update_id, 12350);
195 assert_eq!(event.bids.len(), 3);
196 assert_eq!(event.asks.len(), 2);
197 assert_eq!(event.symbol, "BTCUSDT");
198 }
199
200 #[rstest]
201 fn test_decode_empty_updates() {
202 let buf = make_valid_buffer(0, 0);
203 let event = DepthDiffStreamEvent::decode(&buf).unwrap();
204
205 assert!(event.bids.is_empty());
206 assert!(event.asks.is_empty());
207 }
208
209 #[rstest]
210 fn test_decode_truncated() {
211 let mut buf = make_valid_buffer(5, 5);
212 buf.truncate(60); let err = DepthDiffStreamEvent::decode(&buf).unwrap_err();
214 assert!(matches!(err, StreamDecodeError::BufferTooShort { .. }));
215 }
216
217 #[rstest]
218 fn test_decode_wrong_schema() {
219 let mut buf = make_valid_buffer(3, 2);
220 buf[4..6].copy_from_slice(&99u16.to_le_bytes());
221 let err = DepthDiffStreamEvent::decode(&buf).unwrap_err();
222 assert!(matches!(err, StreamDecodeError::SchemaMismatch { .. }));
223 }
224
225 #[rstest]
226 fn test_decode_validated_matches_decode() {
227 let buf = make_valid_buffer(3, 2);
228
229 let decode_event = DepthDiffStreamEvent::decode(&buf).unwrap();
230 let validated_event = DepthDiffStreamEvent::decode_validated(&buf).unwrap();
231
232 assert_eq!(validated_event.event_time_us, decode_event.event_time_us);
233 assert_eq!(
234 validated_event.first_book_update_id,
235 decode_event.first_book_update_id
236 );
237 assert_eq!(
238 validated_event.last_book_update_id,
239 decode_event.last_book_update_id
240 );
241 assert_eq!(validated_event.price_exponent, decode_event.price_exponent);
242 assert_eq!(validated_event.qty_exponent, decode_event.qty_exponent);
243 assert_eq!(validated_event.bids.len(), decode_event.bids.len());
244 assert_eq!(validated_event.asks.len(), decode_event.asks.len());
245 assert_eq!(validated_event.symbol, decode_event.symbol);
246 }
247}