rusty_backtest/features/
queue.rs1use super::{Level, decimal_to_f64_or_nan};
6use rust_decimal::prelude::*;
7use smallvec::SmallVec;
8
9#[inline(always)]
15pub fn calculate_queue_imbalance(
16 bids: &SmallVec<[Level; 25]>,
17 asks: &SmallVec<[Level; 25]>,
18) -> f64 {
19 if let (Some(best_bid), Some(best_ask)) = (bids.first(), asks.first()) {
20 let total_qty = best_bid.quantity + best_ask.quantity;
21 if total_qty > Decimal::ZERO {
22 decimal_to_f64_or_nan((best_bid.quantity - best_ask.quantity) / total_qty)
23 } else {
24 0.0
25 }
26 } else {
27 0.0
28 }
29}
30
31#[inline(always)]
35pub fn calculate_weighted_queue_imbalance(
36 bids: &SmallVec<[Level; 25]>,
37 asks: &SmallVec<[Level; 25]>,
38 max_levels: usize,
39) -> f64 {
40 let levels = max_levels.min(bids.len()).min(asks.len());
41 if levels == 0 {
42 return 0.0;
43 }
44
45 let mut weighted_bid_qty = 0.0;
46 let mut weighted_ask_qty = 0.0;
47 let mut _total_weight = 0.0;
48
49 for i in 0..levels {
50 let weight = 1.0 / (i as f64 + 1.0); weighted_bid_qty += decimal_to_f64_or_nan(bids[i].quantity) * weight;
52 weighted_ask_qty += decimal_to_f64_or_nan(asks[i].quantity) * weight;
53 _total_weight += weight;
54 }
55
56 let total_weighted_qty = weighted_bid_qty + weighted_ask_qty;
57 if total_weighted_qty > 0.0 {
58 (weighted_bid_qty - weighted_ask_qty) / total_weighted_qty
59 } else {
60 0.0
61 }
62}
63
64pub struct QueueAnalyzer {
66 imbalance_history: Vec<f64>,
67 window_size: usize,
68}
69
70impl QueueAnalyzer {
71 #[must_use]
73 pub fn new(window_size: usize) -> Self {
74 Self {
75 imbalance_history: Vec::with_capacity(window_size * 2),
76 window_size,
77 }
78 }
79
80 pub fn add_measurement(&mut self, imbalance: f64) {
82 self.imbalance_history.push(imbalance);
83
84 if self.imbalance_history.len() > self.window_size * 2 {
86 let drain_count = self.window_size;
87 self.imbalance_history.drain(0..drain_count);
88 }
89 }
90
91 pub fn get_average_imbalance(&self) -> f64 {
93 if self.imbalance_history.is_empty() {
94 return 0.0;
95 }
96
97 let recent_window = &self.imbalance_history[self
98 .imbalance_history
99 .len()
100 .saturating_sub(self.window_size)..];
101 recent_window.iter().sum::<f64>() / recent_window.len() as f64
102 }
103
104 pub fn get_imbalance_volatility(&self) -> f64 {
106 if self.imbalance_history.len() < 2 {
107 return 0.0;
108 }
109
110 let recent_window = &self.imbalance_history[self
111 .imbalance_history
112 .len()
113 .saturating_sub(self.window_size)..];
114 let mean = self.get_average_imbalance();
115
116 let variance = recent_window
117 .iter()
118 .map(|&x| (x - mean).powi(2))
119 .sum::<f64>()
120 / recent_window.len() as f64;
121
122 variance.sqrt()
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use rust_decimal_macros::dec;
130
131 #[test]
132 fn test_queue_imbalance_balanced() {
133 let bids = smallvec::smallvec![Level {
134 price: dec!(49999),
135 quantity: dec!(10.0),
136 order_count: 1
137 }];
138 let asks = smallvec::smallvec![Level {
139 price: dec!(50001),
140 quantity: dec!(10.0),
141 order_count: 1
142 }];
143
144 let imbalance = calculate_queue_imbalance(&bids, &asks);
145 assert_eq!(imbalance, 0.0); }
147
148 #[test]
149 fn test_queue_imbalance_bid_heavy() {
150 let bids = smallvec::smallvec![Level {
151 price: dec!(49999),
152 quantity: dec!(15.0),
153 order_count: 1
154 }];
155 let asks = smallvec::smallvec![Level {
156 price: dec!(50001),
157 quantity: dec!(5.0),
158 order_count: 1
159 }];
160
161 let imbalance = calculate_queue_imbalance(&bids, &asks);
162 assert_eq!(imbalance, 0.5); }
164
165 #[test]
166 fn test_queue_imbalance_ask_heavy() {
167 let bids = smallvec::smallvec![Level {
168 price: dec!(49999),
169 quantity: dec!(5.0),
170 order_count: 1
171 }];
172 let asks = smallvec::smallvec![Level {
173 price: dec!(50001),
174 quantity: dec!(15.0),
175 order_count: 1
176 }];
177
178 let imbalance = calculate_queue_imbalance(&bids, &asks);
179 assert_eq!(imbalance, -0.5); }
181
182 #[test]
183 fn test_weighted_queue_imbalance_single_level() {
184 let bids = smallvec::smallvec![Level {
185 price: dec!(49999),
186 quantity: dec!(10.0),
187 order_count: 1
188 }];
189 let asks = smallvec::smallvec![Level {
190 price: dec!(50001),
191 quantity: dec!(10.0),
192 order_count: 1
193 }];
194
195 let imbalance = calculate_weighted_queue_imbalance(&bids, &asks, 1);
196 assert_eq!(imbalance, 0.0); }
198
199 #[test]
200 fn test_weighted_queue_imbalance_multi_level() {
201 let bids = smallvec::smallvec![
202 Level {
203 price: dec!(49999),
204 quantity: dec!(10.0),
205 order_count: 1
206 },
207 Level {
208 price: dec!(49998),
209 quantity: dec!(20.0),
210 order_count: 2
211 },
212 Level {
213 price: dec!(49997),
214 quantity: dec!(30.0),
215 order_count: 3
216 }
217 ];
218 let asks = smallvec::smallvec![
219 Level {
220 price: dec!(50001),
221 quantity: dec!(10.0),
222 order_count: 1
223 },
224 Level {
225 price: dec!(50002),
226 quantity: dec!(20.0),
227 order_count: 2
228 },
229 Level {
230 price: dec!(50003),
231 quantity: dec!(30.0),
232 order_count: 3
233 }
234 ];
235
236 let imbalance = calculate_weighted_queue_imbalance(&bids, &asks, 3);
237 assert_eq!(imbalance, 0.0); }
239
240 #[test]
241 fn test_weighted_queue_imbalance_bid_heavy() {
242 let bids = smallvec::smallvec![
243 Level {
244 price: dec!(49999),
245 quantity: dec!(20.0),
246 order_count: 1
247 },
248 Level {
249 price: dec!(49998),
250 quantity: dec!(30.0),
251 order_count: 2
252 }
253 ];
254 let asks = smallvec::smallvec![
255 Level {
256 price: dec!(50001),
257 quantity: dec!(10.0),
258 order_count: 1
259 },
260 Level {
261 price: dec!(50002),
262 quantity: dec!(15.0),
263 order_count: 2
264 }
265 ];
266
267 let imbalance = calculate_weighted_queue_imbalance(&bids, &asks, 2);
268 assert!((imbalance - 0.333_333_333_333_333_3).abs() < 1e-10);
273 }
274
275 #[test]
276 fn test_weighted_queue_imbalance_empty_book() {
277 let bids: SmallVec<[Level; 25]> = smallvec::smallvec![];
278 let asks: SmallVec<[Level; 25]> = smallvec::smallvec![];
279
280 let imbalance = calculate_weighted_queue_imbalance(&bids, &asks, 5);
281 assert_eq!(imbalance, 0.0); }
283
284 #[test]
285 fn test_weighted_queue_imbalance_one_sided() {
286 let bids = smallvec::smallvec![Level {
287 price: dec!(49999),
288 quantity: dec!(10.0),
289 order_count: 1
290 }];
291 let asks: SmallVec<[Level; 25]> = smallvec::smallvec![];
292
293 let imbalance = calculate_weighted_queue_imbalance(&bids, &asks, 1);
294 assert_eq!(imbalance, 0.0); }
296
297 #[test]
298 fn test_weighted_queue_imbalance_max_levels_exceeds_book() {
299 let bids = smallvec::smallvec![Level {
300 price: dec!(49999),
301 quantity: dec!(10.0),
302 order_count: 1
303 }];
304 let asks = smallvec::smallvec![Level {
305 price: dec!(50001),
306 quantity: dec!(20.0),
307 order_count: 1
308 }];
309
310 let imbalance = calculate_weighted_queue_imbalance(&bids, &asks, 10);
312 assert!((imbalance - (-0.333_333_333_333_333_3)).abs() < 1e-10);
314 }
315}