rusty_feeder/exchange/binance/spot/data/
orderbook.rs

1//! Order book message types for Binance Spot WebSocket API
2//!
3//! This module provides optimized data structures for processing
4//! order book messages from Binance Spot WebSocket streams.
5
6use rust_decimal::Decimal;
7use rust_decimal_macros::dec;
8use serde::{Deserialize, Serialize};
9use smallvec::SmallVec;
10use smartstring::alias::String;
11
12/// Depth update message from Binance Spot WebSocket (partial or diff)
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[repr(align(16))] // Align for better memory access
15pub struct OrderbookMessage {
16    /// Event type
17    #[serde(rename = "e")]
18    pub event_type: String,
19
20    /// Event time
21    #[serde(rename = "E")]
22    pub event_time: u64,
23
24    /// Symbol
25    #[serde(rename = "s")]
26    pub symbol: String,
27
28    /// First update ID in event
29    #[serde(rename = "U")]
30    pub first_update_id: u64,
31
32    /// Final update ID in event
33    #[serde(rename = "u")]
34    pub final_update_id: u64,
35
36    /// Bids to update
37    #[serde(rename = "b")]
38    pub bids: SmallVec<[SmallVec<[String; 2]>; 32]>,
39
40    /// Asks to update
41    #[serde(rename = "a")]
42    pub asks: SmallVec<[SmallVec<[String; 2]>; 32]>,
43}
44
45/// Parsed orderbook data with Decimal values
46#[derive(Debug, Clone)]
47#[repr(align(16))] // Align for better memory access
48pub struct ParsedOrderbookData {
49    /// Symbol
50    pub symbol: String,
51
52    /// First update ID
53    pub first_update_id: u64,
54
55    /// Final update ID
56    pub final_update_id: u64,
57
58    /// Event timestamp
59    pub event_time: u64,
60
61    /// Bid updates [price, quantity]
62    pub bids: SmallVec<[(Decimal, Decimal); 32]>,
63
64    /// Ask updates [price, quantity]
65    pub asks: SmallVec<[(Decimal, Decimal); 32]>,
66}
67
68impl From<OrderbookMessage> for ParsedOrderbookData {
69    fn from(msg: OrderbookMessage) -> Self {
70        let mut bids = SmallVec::with_capacity(msg.bids.len());
71        let mut asks = SmallVec::with_capacity(msg.asks.len());
72
73        // Parse bids and insert in sorted order (descending)
74        for bid in msg.bids {
75            if bid.len() >= 2 {
76                let price = bid[0].parse().unwrap_or(dec!(0));
77                let quantity = bid[1].parse().unwrap_or(dec!(0));
78
79                // Find the correct insertion position using binary search
80                // For bids, we want descending order (higher prices first)
81                let insert_pos = match bids.binary_search_by(|existing: &(Decimal, Decimal)| {
82                    existing.0.cmp(&price).reverse()
83                }) {
84                    Ok(pos) => pos,  // Exact match (shouldn't happen with unique prices)
85                    Err(pos) => pos, // This is where we should insert
86                };
87
88                bids.insert(insert_pos, (price, quantity));
89            }
90        }
91
92        // Parse asks and insert in sorted order (ascending)
93        for ask in msg.asks {
94            if ask.len() >= 2 {
95                let price = ask[0].parse().unwrap_or(dec!(0));
96                let quantity = ask[1].parse().unwrap_or(dec!(0));
97
98                // Find the correct insertion position using binary search
99                // For asks, we want ascending order (lower prices first)
100                let insert_pos = match asks
101                    .binary_search_by(|existing: &(Decimal, Decimal)| existing.0.cmp(&price))
102                {
103                    Ok(pos) => pos,  // Exact match (shouldn't happen with unique prices)
104                    Err(pos) => pos, // This is where we should insert
105                };
106
107                asks.insert(insert_pos, (price, quantity));
108            }
109        }
110
111        ParsedOrderbookData {
112            symbol: msg.symbol,
113            first_update_id: msg.first_update_id,
114            final_update_id: msg.final_update_id,
115            event_time: msg.event_time,
116            bids,
117            asks,
118        }
119    }
120}
121
122/// Orderbook snapshot message from Binance Spot REST API
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct OrderbookSnapshot {
125    /// Last update ID
126    #[serde(rename = "lastUpdateId")]
127    pub last_update_id: u64,
128
129    /// Bid price and quantity pairs
130    pub bids: SmallVec<[SmallVec<[String; 2]>; 32]>,
131
132    /// Ask price and quantity pairs
133    pub asks: SmallVec<[SmallVec<[String; 2]>; 32]>,
134}
135
136/// Parsed orderbook snapshot with Decimal values
137#[derive(Debug, Clone)]
138#[repr(align(16))] // Align for better memory access
139pub struct ParsedOrderbookSnapshot {
140    /// Last update ID
141    pub last_update_id: u64,
142
143    /// Bid updates [price, quantity]
144    pub bids: SmallVec<[(Decimal, Decimal); 32]>,
145
146    /// Ask updates [price, quantity]
147    pub asks: SmallVec<[(Decimal, Decimal); 32]>,
148}
149
150impl From<OrderbookSnapshot> for ParsedOrderbookSnapshot {
151    fn from(snapshot: OrderbookSnapshot) -> Self {
152        let mut bids = SmallVec::with_capacity(snapshot.bids.len());
153        let mut asks = SmallVec::with_capacity(snapshot.asks.len());
154
155        // Parse bids and insert in sorted order (descending)
156        for bid in snapshot.bids {
157            if bid.len() >= 2 {
158                let price = bid[0].parse().unwrap_or(dec!(0));
159                let quantity = bid[1].parse().unwrap_or(dec!(0));
160
161                // Find the correct insertion position using binary search
162                // For bids, we want descending order (higher prices first)
163                let insert_pos = match bids.binary_search_by(|existing: &(Decimal, Decimal)| {
164                    existing.0.cmp(&price).reverse()
165                }) {
166                    Ok(pos) => pos,  // Exact match (shouldn't happen with unique prices)
167                    Err(pos) => pos, // This is where we should insert
168                };
169
170                bids.insert(insert_pos, (price, quantity));
171            }
172        }
173
174        // Parse asks and insert in sorted order (ascending)
175        for ask in snapshot.asks {
176            if ask.len() >= 2 {
177                let price = ask[0].parse().unwrap_or(dec!(0));
178                let quantity = ask[1].parse().unwrap_or(dec!(0));
179
180                // Find the correct insertion position using binary search
181                // For asks, we want ascending order (lower prices first)
182                let insert_pos = match asks
183                    .binary_search_by(|existing: &(Decimal, Decimal)| existing.0.cmp(&price))
184                {
185                    Ok(pos) => pos,  // Exact match (shouldn't happen with unique prices)
186                    Err(pos) => pos, // This is where we should insert
187                };
188
189                asks.insert(insert_pos, (price, quantity));
190            }
191        }
192
193        ParsedOrderbookSnapshot {
194            last_update_id: snapshot.last_update_id,
195            bids,
196            asks,
197        }
198    }
199}