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}