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
53#[inline]
55fn price_based_order_id(order: &BookOrder) -> u64 {
56 #[cfg(feature = "high-precision")]
57 {
58 price_to_order_id(order.price.raw)
59 }
60 #[cfg(not(feature = "high-precision"))]
61 {
62 price_to_order_id(order.price.raw as i128)
63 }
64}
65
66pub(crate) fn pre_process_order(book_type: BookType, mut order: BookOrder, flags: u8) -> BookOrder {
67 match book_type {
68 BookType::L1_MBP => order.order_id = order.side as u64,
69 BookType::L2_MBP => order.order_id = price_based_order_id(&order),
70 BookType::L3_MBO => {
71 if RecordFlag::F_TOB.matches(flags) {
72 order.order_id = order.side as u64;
73 } else if RecordFlag::F_MBP.matches(flags) {
74 order.order_id = price_based_order_id(&order);
75 }
76 }
77 };
78 order
79}
80
81#[cfg(test)]
82mod tests {
83 use std::sync::{LazyLock, Mutex};
84
85 use ahash::AHashSet;
86 use nautilus_core::MUTEX_POISONED;
87 use rstest::rstest;
88
89 use super::*;
90
91 #[rstest]
92 fn test_price_to_order_id_deterministic() {
93 let price1 = 123456789012345678901234567890_i128;
94 let price2 = 987654321098765432109876543210_i128;
95
96 let id1_a = price_to_order_id(price1);
98 let id1_b = price_to_order_id(price1);
99 assert_eq!(id1_a, id1_b, "Same price must produce same order_id");
100
101 let id2 = price_to_order_id(price2);
103 assert_ne!(
104 id1_a, id2,
105 "Different prices should produce different order_ids"
106 );
107
108 for _ in 0..100 {
110 assert_eq!(price_to_order_id(price1), id1_a);
111 assert_eq!(price_to_order_id(price2), id2);
112 }
113 }
114
115 #[rstest]
116 fn test_price_to_order_id_no_collisions() {
117 let base = 1000000000_i128;
119 let mut seen = AHashSet::new();
120
121 for i in 0..1000 {
122 let price = base + i;
123 let id = price_to_order_id(price);
124 assert!(seen.insert(id), "Collision detected for price {price}");
125 }
126 }
127
128 #[rstest]
129 fn test_price_to_order_id_no_collision_across_64bit_boundary() {
130 let price1 = 1_i128;
132 let price2 = 1_i128 << 64; let id1 = price_to_order_id(price1);
135 let id2 = price_to_order_id(price2);
136
137 assert_ne!(
138 id1, id2,
139 "Collision detected: price 1 and price 2^64 must have different order_ids"
140 );
141 }
142
143 #[rstest]
144 fn test_price_to_order_id_handles_negative_prices() {
145 let mut seen = AHashSet::new();
146
147 let negative_prices = vec![
149 -1_i128,
150 -2_i128,
151 -100_i128,
152 -1000000000_i128,
153 i128::MIN,
154 i128::MIN + 1,
155 ];
156
157 for &price in &negative_prices {
158 let id = price_to_order_id(price);
159 assert!(
160 seen.insert(id),
161 "Collision detected for negative price {price}"
162 );
163 }
164
165 let positive_prices = vec![1_i128, 2_i128, 100_i128, 1000000000_i128, i128::MAX];
167
168 for &price in &positive_prices {
169 let id = price_to_order_id(price);
170 assert!(
171 seen.insert(id),
172 "Collision detected between negative and positive price: {price}"
173 );
174 }
175 }
176
177 #[rstest]
178 fn test_price_to_order_id_handles_large_values() {
179 let mut seen = AHashSet::new();
180
181 let large_values = vec![
184 u64::MAX as i128, 1_i128 << 64, (u64::MAX as i128) + 1000,
187 1_i128 << 65, 1_i128 << 100, i128::MAX - 1,
190 i128::MAX,
191 ];
192
193 for &price in &large_values {
194 let id = price_to_order_id(price);
195 assert!(
196 seen.insert(id),
197 "Collision detected for large price value {price}"
198 );
199 }
200 }
201
202 #[rstest]
203 fn test_price_to_order_id_multiples_of_2_pow_64() {
204 let mut seen = AHashSet::new();
205
206 for i in 0..10 {
209 let price = i * (1_i128 << 64);
210 let id = price_to_order_id(price);
211 assert!(
212 seen.insert(id),
213 "Collision detected for price {price} (multiple of 2^64)"
214 );
215 }
216 }
217
218 #[rstest]
219 fn test_price_to_order_id_realistic_orderbook_prices() {
220 let mut seen = AHashSet::new();
221
222 let btc_base = 50000_000000000_i128;
225 for i in -1000..1000 {
226 let price = btc_base + i; let id = price_to_order_id(price);
228 assert!(
229 seen.insert(id),
230 "Collision detected for BTC price offset {i}"
231 );
232 }
233
234 let forex_base = 1_100000000_i128;
236 for i in -10000..10000 {
237 let price = forex_base + i; let id = price_to_order_id(price);
239 assert!(
240 seen.insert(id),
241 "Collision detected for EURUSD price offset {i}"
242 );
243 }
244
245 let doge_base = 100000000_i128; for i in -100000..100000 {
248 let price = doge_base + i;
249 let id = price_to_order_id(price);
250 assert!(
251 seen.insert(id),
252 "Collision detected for DOGE price offset {i}"
253 );
254 }
255 }
256
257 #[rstest]
258 fn test_price_to_order_id_edge_case_patterns() {
259 let mut seen = AHashSet::new();
260
261 for power in 0..128 {
264 let price = 1_i128 << power;
265 let id = price_to_order_id(price);
266 assert!(
267 seen.insert(id),
268 "Collision detected for 2^{power} = {price}"
269 );
270 }
271
272 for power in 0..127 {
275 let price = -(1_i128 << power);
276 let id = price_to_order_id(price);
277 assert!(
278 seen.insert(id),
279 "Collision detected for -2^{power} = {price}"
280 );
281 }
282 }
283
284 #[rstest]
285 fn test_price_to_order_id_sequential_negative_values() {
286 let mut seen = AHashSet::new();
287
288 for i in -10000..=0 {
290 let price = i as i128;
291 let id = price_to_order_id(price);
292 assert!(seen.insert(id), "Collision detected for price {i}");
293 }
294 }
295
296 #[rstest]
297 #[case::max(i128::MAX)]
298 #[case::max_minus_1(i128::MAX - 1)]
299 #[case::min(i128::MIN)]
300 #[case::min_plus_1(i128::MIN + 1)]
301 #[case::u64_max(u64::MAX as i128)]
302 #[case::u64_max_minus_1((u64::MAX as i128) - 1)]
303 #[case::u64_max_plus_1((u64::MAX as i128) + 1)]
304 #[case::neg_u64_max(-(u64::MAX as i128))]
305 #[case::neg_u64_max_minus_1(-(u64::MAX as i128) - 1)]
306 #[case::neg_u64_max_plus_1(-(u64::MAX as i128) + 1)]
307 #[case::zero(0_i128)]
308 #[case::one(1_i128)]
309 #[case::neg_one(-1_i128)]
310 fn test_price_to_order_id_extreme_values_no_collision(#[case] price: i128) {
311 static SEEN: LazyLock<Mutex<AHashSet<u64>>> = LazyLock::new(|| Mutex::new(AHashSet::new()));
314
315 let id = price_to_order_id(price);
316 let mut seen = SEEN.lock().expect(MUTEX_POISONED);
317 assert!(
318 seen.insert(id),
319 "Collision detected for extreme value: {price} (order_id: {id})"
320 );
321 }
322
323 #[rstest]
324 fn test_price_to_order_id_avalanche_effect() {
325 let base_price = 1000000000000_i128;
328 let id1 = price_to_order_id(base_price);
329 let id2 = price_to_order_id(base_price + 1);
330
331 let xor = id1 ^ id2;
333 let differing_bits = xor.count_ones();
334
335 assert!(
338 differing_bits >= 12,
339 "Poor avalanche: only {differing_bits}/64 bits differ for adjacent prices"
340 );
341 }
342
343 #[rstest]
344 fn test_price_to_order_id_comprehensive_collision_check() {
345 const TOTAL_TESTS: usize = 500_000;
346
347 let mut seen = AHashSet::new();
349 let mut collision_count = 0;
350
351 for i in -100_000..100_000 {
353 let id = price_to_order_id(i as i128);
354 if !seen.insert(id) {
355 collision_count += 1;
356 }
357 }
358
359 for power in 0..64 {
361 for offset in -10..=10 {
362 let price = (1_i128 << power) + offset;
363 let id = price_to_order_id(price);
364 if !seen.insert(id) {
365 collision_count += 1;
366 }
367 }
368 }
369
370 for base in [100, 1000, 10000, 100000, 1000000, 10000000] {
372 for i in 0..1000 {
373 let price = base * 1_000_000_000_i128 + i;
374 let id = price_to_order_id(price);
375 if !seen.insert(id) {
376 collision_count += 1;
377 }
378 }
379 }
380
381 let collision_rate = collision_count as f64 / TOTAL_TESTS as f64;
383
384 assert!(
389 collision_rate < 0.001,
390 "High collision rate: {collision_rate:.6}% ({collision_count}/{TOTAL_TESTS})"
391 );
392
393 println!(
394 "✓ Tested {} unique prices, {} collisions ({:.6}%)",
395 TOTAL_TESTS,
396 collision_count,
397 collision_rate * 100.0
398 );
399 }
400}