rusty_feeder/exchange/bybit/futures/data/
orderbook.rs

1//! Bybit Futures orderbook data structures
2//!
3//! This module defines types for handling Bybit Futures orderbook data via WebSocket,
4//! optimized for high-frequency trading scenarios. The implementation uses
5//! fixed-size arrays where possible to avoid heap allocations.
6
7use serde::{Deserialize, Serialize};
8use smallvec::SmallVec;
9use smartstring::alias::String;
10
11/// Bybit Futures orderbook message response from WebSocket
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct OrderbookResponse {
14    /// Topic being subscribed to (e.g., "orderbook.50.BTCUSDT")
15    pub topic: String,
16
17    /// Type of message (e.g., "snapshot" or "delta")
18    #[serde(rename = "type")]
19    pub message_type: String,
20
21    /// Timestamp in milliseconds (from Bybit server)
22    pub ts: u64,
23
24    /// Last update ID for orderbook synchronization
25    pub data: OrderbookData,
26}
27
28/// Orderbook data from Bybit Futures
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct OrderbookData {
31    /// Symbol
32    pub s: String,
33
34    /// Bids (price, quantity)
35    pub b: SmallVec<[[String; 2]; 32]>,
36
37    /// Asks (price, quantity)
38    pub a: SmallVec<[[String; 2]; 32]>,
39
40    /// Update ID
41    pub u: u64,
42
43    /// Sequence number
44    pub seq: u64,
45}
46
47/// Parsed orderbook data with converted price and quantity values
48#[derive(Debug, Clone)]
49pub struct ParsedOrderbookData {
50    /// Symbol
51    pub symbol: String,
52
53    /// Bids (price, quantity) sorted by price descending
54    pub bids: SmallVec<[(f64, f64); 64]>,
55
56    /// Asks (price, quantity) sorted by price ascending
57    pub asks: SmallVec<[(f64, f64); 64]>,
58
59    /// Update ID
60    pub update_id: u64,
61
62    /// Sequence number
63    pub sequence: u64,
64
65    /// Timestamp in nanoseconds
66    pub timestamp_ns: u64,
67}
68
69impl From<OrderbookResponse> for ParsedOrderbookData {
70    fn from(response: OrderbookResponse) -> Self {
71        // Convert bids and asks to SmallVec with f64 values
72        let mut bids = SmallVec::with_capacity(response.data.b.len());
73        let mut asks = SmallVec::with_capacity(response.data.a.len());
74
75        // Process bids - price and quantity as f64 and insert in sorted order (descending)
76        for bid in &response.data.b {
77            if let (Ok(price), Ok(quantity)) = (bid[0].parse::<f64>(), bid[1].parse::<f64>()) {
78                // Find the correct insertion position using binary search
79                // For bids, we want descending order (higher prices first)
80                let insert_pos = match bids.binary_search_by(|existing: &(f64, f64)| {
81                    // Use partial_cmp for f64 and handle NaN values
82                    match existing.0.partial_cmp(&price) {
83                        Some(std::cmp::Ordering::Equal) => std::cmp::Ordering::Equal,
84                        Some(std::cmp::Ordering::Greater) => std::cmp::Ordering::Less,
85                        Some(std::cmp::Ordering::Less) => std::cmp::Ordering::Greater,
86                        None => std::cmp::Ordering::Equal, // Handle NaN (shouldn't happen in practice)
87                    }
88                }) {
89                    Ok(pos) => pos,  // Exact match (shouldn't happen with unique prices)
90                    Err(pos) => pos, // This is where we should insert
91                };
92
93                bids.insert(insert_pos, (price, quantity));
94            }
95        }
96
97        // Process asks - price and quantity as f64 and insert in sorted order (ascending)
98        for ask in &response.data.a {
99            if let (Ok(price), Ok(quantity)) = (ask[0].parse::<f64>(), ask[1].parse::<f64>()) {
100                // Find the correct insertion position using binary search
101                // For asks, we want ascending order (lower prices first)
102                let insert_pos = match asks.binary_search_by(|existing: &(f64, f64)| {
103                    // Use partial_cmp for f64 and handle NaN values
104                    existing
105                        .0
106                        .partial_cmp(&price)
107                        .unwrap_or(std::cmp::Ordering::Equal)
108                }) {
109                    Ok(pos) => pos,  // Exact match (shouldn't happen with unique prices)
110                    Err(pos) => pos, // This is where we should insert
111                };
112
113                asks.insert(insert_pos, (price, quantity));
114            }
115        }
116
117        Self {
118            symbol: response.data.s,
119            bids,
120            asks,
121            update_id: response.data.u,
122            sequence: response.data.seq,
123            timestamp_ns: response.ts * 1_000_000, // Convert ms to ns
124        }
125    }
126}
127
128/// Implementation of OrderbookResponse
129impl OrderbookResponse {
130    /// Get the symbol from the orderbook data
131    pub fn symbol(&self) -> &str {
132        &self.data.s
133    }
134
135    /// Get the update ID for orderbook synchronization
136    pub const fn update_id(&self) -> u64 {
137        self.data.u
138    }
139
140    /// Get the timestamp in nanoseconds
141    pub const fn timestamp_ns(&self) -> u64 {
142        // Convert from ms to ns
143        self.ts * 1_000_000
144    }
145
146    /// Parse into more efficient internal representation
147    pub fn parse(&self) -> ParsedOrderbookData {
148        self.clone().into()
149    }
150}