nautilus_model/orderbook/
aggregation.rs1use crate::{
19 data::order::BookOrder,
20 enums::{BookType, RecordFlag},
21};
22
23#[inline]
48fn price_to_order_id(price_raw: i128) -> u64 {
49 let build_hasher = ahash::RandomState::with_seeds(0, 0, 0, 0);
50 build_hasher.hash_one(price_raw)
51}
52
53pub(crate) fn pre_process_order(book_type: BookType, mut order: BookOrder, flags: u8) -> BookOrder {
54 match book_type {
55 BookType::L1_MBP => order.order_id = order.side as u64,
56 #[cfg(feature = "high-precision")]
57 BookType::L2_MBP => order.order_id = price_to_order_id(order.price.raw),
58 #[cfg(not(feature = "high-precision"))]
59 BookType::L2_MBP => order.order_id = price_to_order_id(order.price.raw as i128),
60 BookType::L3_MBO => {
61 if flags == 0 {
62 } else if RecordFlag::F_TOB.matches(flags) {
63 order.order_id = order.side as u64;
64 } else if RecordFlag::F_MBP.matches(flags) {
65 #[cfg(feature = "high-precision")]
66 {
67 order.order_id = price_to_order_id(order.price.raw);
68 }
69 #[cfg(not(feature = "high-precision"))]
70 {
71 order.order_id = price_to_order_id(order.price.raw as i128);
72 }
73 }
74 }
75 };
76 order
77}
78
79#[cfg(test)]
84mod tests {
85 use std::{
86 collections::HashSet,
87 sync::{LazyLock, Mutex},
88 };
89
90 use nautilus_core::MUTEX_POISONED;
91 use rstest::rstest;
92
93 use super::*;
94
95 #[rstest]
96 fn test_price_to_order_id_deterministic() {
97 let price1 = 123456789012345678901234567890_i128;
98 let price2 = 987654321098765432109876543210_i128;
99
100 let id1_a = price_to_order_id(price1);
102 let id1_b = price_to_order_id(price1);
103 assert_eq!(id1_a, id1_b, "Same price must produce same order_id");
104
105 let id2 = price_to_order_id(price2);
107 assert_ne!(
108 id1_a, id2,
109 "Different prices should produce different order_ids"
110 );
111
112 for _ in 0..100 {
114 assert_eq!(price_to_order_id(price1), id1_a);
115 assert_eq!(price_to_order_id(price2), id2);
116 }
117 }
118
119 #[rstest]
120 fn test_price_to_order_id_no_collisions() {
121 use std::collections::HashSet;
122
123 let base = 1000000000_i128;
125 let mut seen = HashSet::new();
126
127 for i in 0..1000 {
128 let price = base + i;
129 let id = price_to_order_id(price);
130 assert!(seen.insert(id), "Collision detected for price {price}");
131 }
132 }
133
134 #[rstest]
135 fn test_price_to_order_id_no_collision_across_64bit_boundary() {
136 let price1 = 1_i128;
138 let price2 = 1_i128 << 64; let id1 = price_to_order_id(price1);
141 let id2 = price_to_order_id(price2);
142
143 assert_ne!(
144 id1, id2,
145 "Collision detected: price 1 and price 2^64 must have different order_ids"
146 );
147 }
148
149 #[rstest]
150 fn test_price_to_order_id_handles_negative_prices() {
151 use std::collections::HashSet;
152
153 let mut seen = HashSet::new();
154
155 let negative_prices = vec![
157 -1_i128,
158 -2_i128,
159 -100_i128,
160 -1000000000_i128,
161 i128::MIN,
162 i128::MIN + 1,
163 ];
164
165 for &price in &negative_prices {
166 let id = price_to_order_id(price);
167 assert!(
168 seen.insert(id),
169 "Collision detected for negative price {price}"
170 );
171 }
172
173 let positive_prices = vec![1_i128, 2_i128, 100_i128, 1000000000_i128, i128::MAX];
175
176 for &price in &positive_prices {
177 let id = price_to_order_id(price);
178 assert!(
179 seen.insert(id),
180 "Collision detected between negative and positive price: {price}"
181 );
182 }
183 }
184
185 #[rstest]
186 fn test_price_to_order_id_handles_large_values() {
187 use std::collections::HashSet;
188
189 let mut seen = HashSet::new();
190
191 let large_values = vec![
194 u64::MAX as i128, 1_i128 << 64, (u64::MAX as i128) + 1000,
197 1_i128 << 65, 1_i128 << 100, i128::MAX - 1,
200 i128::MAX,
201 ];
202
203 for &price in &large_values {
204 let id = price_to_order_id(price);
205 assert!(
206 seen.insert(id),
207 "Collision detected for large price value {price}"
208 );
209 }
210 }
211
212 #[rstest]
213 fn test_price_to_order_id_multiples_of_2_pow_64() {
214 use std::collections::HashSet;
215
216 let mut seen = HashSet::new();
217
218 for i in 0..10 {
221 let price = i * (1_i128 << 64);
222 let id = price_to_order_id(price);
223 assert!(
224 seen.insert(id),
225 "Collision detected for price {price} (multiple of 2^64)"
226 );
227 }
228 }
229
230 #[rstest]
231 fn test_price_to_order_id_realistic_orderbook_prices() {
232 use std::collections::HashSet;
233
234 let mut seen = HashSet::new();
235
236 let btc_base = 50000_000000000_i128;
239 for i in -1000..1000 {
240 let price = btc_base + i; let id = price_to_order_id(price);
242 assert!(
243 seen.insert(id),
244 "Collision detected for BTC price offset {i}"
245 );
246 }
247
248 let forex_base = 1_100000000_i128;
250 for i in -10000..10000 {
251 let price = forex_base + i; let id = price_to_order_id(price);
253 assert!(
254 seen.insert(id),
255 "Collision detected for EURUSD price offset {i}"
256 );
257 }
258
259 let doge_base = 100000000_i128; for i in -100000..100000 {
262 let price = doge_base + i;
263 let id = price_to_order_id(price);
264 assert!(
265 seen.insert(id),
266 "Collision detected for DOGE price offset {i}"
267 );
268 }
269 }
270
271 #[rstest]
272 fn test_price_to_order_id_edge_case_patterns() {
273 use std::collections::HashSet;
274
275 let mut seen = HashSet::new();
276
277 for power in 0..128 {
280 let price = 1_i128 << power;
281 let id = price_to_order_id(price);
282 assert!(
283 seen.insert(id),
284 "Collision detected for 2^{power} = {price}"
285 );
286 }
287
288 for power in 0..127 {
291 let price = -(1_i128 << power);
292 let id = price_to_order_id(price);
293 assert!(
294 seen.insert(id),
295 "Collision detected for -2^{power} = {price}"
296 );
297 }
298 }
299
300 #[rstest]
301 fn test_price_to_order_id_sequential_negative_values() {
302 use std::collections::HashSet;
303
304 let mut seen = HashSet::new();
305
306 for i in -10000..=0 {
308 let price = i as i128;
309 let id = price_to_order_id(price);
310 assert!(seen.insert(id), "Collision detected for price {i}");
311 }
312 }
313
314 #[rstest]
315 #[case::max(i128::MAX)]
316 #[case::max_minus_1(i128::MAX - 1)]
317 #[case::min(i128::MIN)]
318 #[case::min_plus_1(i128::MIN + 1)]
319 #[case::u64_max(u64::MAX as i128)]
320 #[case::u64_max_minus_1((u64::MAX as i128) - 1)]
321 #[case::u64_max_plus_1((u64::MAX as i128) + 1)]
322 #[case::neg_u64_max(-(u64::MAX as i128))]
323 #[case::neg_u64_max_minus_1(-(u64::MAX as i128) - 1)]
324 #[case::neg_u64_max_plus_1(-(u64::MAX as i128) + 1)]
325 #[case::zero(0_i128)]
326 #[case::one(1_i128)]
327 #[case::neg_one(-1_i128)]
328 fn test_price_to_order_id_extreme_values_no_collision(#[case] price: i128) {
329 static SEEN: LazyLock<Mutex<HashSet<u64>>> = LazyLock::new(|| Mutex::new(HashSet::new()));
332
333 let id = price_to_order_id(price);
334 let mut seen = SEEN.lock().expect(MUTEX_POISONED);
335 assert!(
336 seen.insert(id),
337 "Collision detected for extreme value: {price} (order_id: {id})"
338 );
339 }
340
341 #[rstest]
342 fn test_price_to_order_id_avalanche_effect() {
343 let base_price = 1000000000000_i128;
346 let id1 = price_to_order_id(base_price);
347 let id2 = price_to_order_id(base_price + 1);
348
349 let xor = id1 ^ id2;
351 let differing_bits = xor.count_ones();
352
353 assert!(
356 differing_bits >= 12,
357 "Poor avalanche: only {differing_bits}/64 bits differ for adjacent prices"
358 );
359 }
360
361 #[rstest]
362 fn test_price_to_order_id_comprehensive_collision_check() {
363 use std::collections::HashSet;
364
365 let mut seen = HashSet::new();
367 let mut collision_count = 0;
368 const TOTAL_TESTS: usize = 500_000;
369
370 for i in -100_000..100_000 {
372 let id = price_to_order_id(i as i128);
373 if !seen.insert(id) {
374 collision_count += 1;
375 }
376 }
377
378 for power in 0..64 {
380 for offset in -10..=10 {
381 let price = (1_i128 << power) + offset;
382 let id = price_to_order_id(price);
383 if !seen.insert(id) {
384 collision_count += 1;
385 }
386 }
387 }
388
389 for base in [100, 1000, 10000, 100000, 1000000, 10000000] {
391 for i in 0..1000 {
392 let price = base * 1_000_000_000_i128 + i;
393 let id = price_to_order_id(price);
394 if !seen.insert(id) {
395 collision_count += 1;
396 }
397 }
398 }
399
400 let collision_rate = collision_count as f64 / TOTAL_TESTS as f64;
402
403 assert!(
408 collision_rate < 0.001,
409 "High collision rate: {collision_rate:.6}% ({collision_count}/{TOTAL_TESTS})"
410 );
411
412 println!(
413 "✓ Tested {} unique prices, {} collisions ({:.6}%)",
414 TOTAL_TESTS,
415 collision_count,
416 collision_rate * 100.0
417 );
418 }
419}