1use rust_decimal::prelude::ToPrimitive;
7use simd_aligned::VecSimd;
8use smallvec::SmallVec;
9use smartstring::alias::String;
10use std::sync::{Arc, RwLock};
11use wide::f64x4;
12
13use crate::data::orderbook::{OrderBook, PriceLevel};
14
15#[derive(Debug, Clone)]
50pub struct SimdPriceLevels<const N: usize = 64> {
51 pub prices: VecSimd<f64x4>,
54
55 pub quantities: VecSimd<f64x4>,
58
59 pub count: usize,
61}
62
63impl<const N: usize> Default for SimdPriceLevels<N> {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69impl<const N: usize> SimdPriceLevels<N> {
70 #[inline]
72 #[must_use]
73 pub fn new() -> Self {
74 let aligned_capacity = (N + 3) & !3;
76
77 Self {
78 prices: VecSimd::with(0.0, aligned_capacity),
80 quantities: VecSimd::with(0.0, aligned_capacity),
81 count: 0,
82 }
83 }
84
85 #[inline]
87 #[must_use]
88 pub fn with_capacity(capacity: usize) -> Self {
89 let effective_capacity = capacity.min(N);
91 let aligned_capacity = (effective_capacity + 3) & !3;
93
94 Self {
95 prices: VecSimd::with(0.0, aligned_capacity),
97 quantities: VecSimd::with(0.0, aligned_capacity),
98 count: 0,
99 }
100 }
101
102 #[must_use]
104 pub fn from_levels(levels: &[PriceLevel]) -> Self {
105 let count = levels.len().min(N);
106 let aligned_capacity = (count + 3) & !3;
107
108 let mut prices = VecSimd::with(0.0, aligned_capacity);
110 let mut quantities = VecSimd::with(0.0, aligned_capacity);
111
112 let prices_flat = prices.flat_mut();
114 let quantities_flat = quantities.flat_mut();
115
116 for (i, level) in levels.iter().enumerate().take(count) {
117 prices_flat[i] = level.price.to_f64().unwrap_or_else(|| {
118 #[cfg(debug_assertions)]
119 eprintln!(
120 "Warning: Order book price conversion failed for level {}: {}",
121 i, level.price
122 );
123 f64::NAN
124 });
125 quantities_flat[i] = level.quantity.to_f64().unwrap_or_else(|| {
126 #[cfg(debug_assertions)]
127 eprintln!(
128 "Warning: Order book quantity conversion failed for level {}: {}",
129 i, level.quantity
130 );
131 f64::NAN
132 });
133 }
134
135 Self {
136 prices,
137 quantities,
138 count,
139 }
140 }
141
142 #[must_use]
144 pub fn from_smallvec<const M: usize>(levels: &SmallVec<[PriceLevel; M]>) -> Self {
145 Self::from_levels(levels.as_slice())
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn total_volume(&self) -> f64 {
152 if self.count == 0 {
153 return 0.0;
154 }
155
156 let chunks = self.count.div_ceil(4);
157 let mut sum = f64x4::splat(0.0);
158
159 for i in 0..chunks {
160 sum += self.quantities[i];
161 }
162
163 let total = sum.reduce_add();
165
166 let padding = chunks * 4 - self.count;
168 if padding > 0 {
169 let quantities_flat = self.quantities.flat();
170 let padding_sum: f64 = quantities_flat[self.count..chunks * 4].iter().sum();
171 total - padding_sum
172 } else {
173 total
174 }
175 }
176
177 #[inline]
179 #[must_use]
180 pub fn weighted_average_price(&self) -> Option<f64> {
181 if self.count == 0 {
182 return None;
183 }
184
185 let chunks = self.count.div_ceil(4);
186 let mut weighted_sum = f64x4::splat(0.0);
187 let mut volume_sum = f64x4::splat(0.0);
188
189 for i in 0..chunks {
190 weighted_sum += self.prices[i] * self.quantities[i];
191 volume_sum += self.quantities[i];
192 }
193
194 let total_weighted = weighted_sum.reduce_add();
195 let total_volume = volume_sum.reduce_add();
196
197 let padding = chunks * 4 - self.count;
199 if padding > 0 {
200 let prices_flat = self.prices.flat();
201 let quantities_flat = self.quantities.flat();
202 let mut padding_weighted = 0.0;
203 let mut padding_volume = 0.0;
204
205 for i in self.count..chunks * 4 {
206 padding_weighted += prices_flat[i] * quantities_flat[i];
207 padding_volume += quantities_flat[i];
208 }
209
210 let adjusted_weighted = total_weighted - padding_weighted;
211 let adjusted_volume = total_volume - padding_volume;
212
213 if adjusted_volume > 0.0 {
214 Some(adjusted_weighted / adjusted_volume)
215 } else {
216 None
217 }
218 } else if total_volume > 0.0 {
219 Some(total_weighted / total_volume)
220 } else {
221 None
222 }
223 }
224
225 #[inline]
227 #[must_use]
228 pub fn cumulative_volume_to_target(&self, target_volume: f64) -> Option<(usize, f64)> {
229 if self.count == 0 || target_volume <= 0.0 {
230 return None;
231 }
232
233 let quantities_flat = self.quantities.flat();
234 let mut cumulative = 0.0;
235
236 for (i, &quantity) in quantities_flat[..self.count].iter().enumerate() {
237 cumulative += quantity;
238 if cumulative >= target_volume {
239 return Some((i + 1, cumulative));
240 }
241 }
242
243 Some((self.count, cumulative))
244 }
245
246 pub fn clear(&mut self) {
248 self.count = 0;
249 let prices_flat = self.prices.flat_mut();
251 let quantities_flat = self.quantities.flat_mut();
252 for i in 0..prices_flat.len() {
253 prices_flat[i] = 0.0;
254 quantities_flat[i] = 0.0;
255 }
256 }
257}
258
259#[derive(Debug, Clone)]
261pub struct SimdOrderBook<const N: usize = 64> {
262 pub symbol: String,
264 pub exchange_timestamp_ns: u64,
266 pub system_timestamp_ns: u64,
268 pub bids: SimdPriceLevels<N>,
270 pub asks: SimdPriceLevels<N>,
272}
273
274impl<const N: usize> SimdOrderBook<N> {
275 #[must_use]
277 pub fn from_orderbook<const M: usize>(book: &OrderBook<M>) -> Self {
278 Self {
279 symbol: book.symbol.clone(),
280 exchange_timestamp_ns: book.exchange_timestamp_ns,
281 system_timestamp_ns: book.system_timestamp_ns,
282 bids: SimdPriceLevels::from_smallvec(&book.bids),
283 asks: SimdPriceLevels::from_smallvec(&book.asks),
284 }
285 }
286
287 #[inline]
289 #[must_use]
290 pub fn mid_price(&self) -> Option<f64> {
291 if self.bids.count > 0 && self.asks.count > 0 {
292 let best_bid = self.bids.prices.flat()[0];
293 let best_ask = self.asks.prices.flat()[0];
294 Some(f64::midpoint(best_bid, best_ask))
295 } else {
296 None
297 }
298 }
299
300 #[inline]
302 #[must_use]
303 pub fn spread(&self) -> Option<f64> {
304 if self.bids.count > 0 && self.asks.count > 0 {
305 let best_bid = self.bids.prices.flat()[0];
306 let best_ask = self.asks.prices.flat()[0];
307 Some(best_ask - best_bid)
308 } else {
309 None
310 }
311 }
312
313 #[inline]
315 #[must_use]
316 pub fn imbalance(&self) -> Option<f64> {
317 let bid_volume = self.bids.total_volume();
318 let ask_volume = self.asks.total_volume();
319 let total = bid_volume + ask_volume;
320
321 if total > 0.0 {
322 Some((bid_volume - ask_volume) / total)
323 } else {
324 None
325 }
326 }
327
328 #[inline]
330 #[must_use]
331 pub fn side_vwap(&self, is_bid: bool, target_volume: f64) -> Option<f64> {
332 let levels = if is_bid { &self.bids } else { &self.asks };
333
334 if levels.count == 0 || target_volume <= 0.0 {
335 return None;
336 }
337
338 let prices_flat = levels.prices.flat();
339 let quantities_flat = levels.quantities.flat();
340
341 let mut cumulative_volume = 0.0;
342 let mut weighted_sum = 0.0;
343
344 for i in 0..levels.count {
345 let quantity = quantities_flat[i];
346 let remaining = target_volume - cumulative_volume;
347
348 if remaining <= 0.0 {
349 break;
350 }
351
352 let used_quantity = quantity.min(remaining);
353 weighted_sum += prices_flat[i] * used_quantity;
354 cumulative_volume += used_quantity;
355
356 if cumulative_volume >= target_volume {
357 break;
358 }
359 }
360
361 if cumulative_volume > 0.0 {
362 Some(weighted_sum / cumulative_volume)
363 } else {
364 None
365 }
366 }
367
368 #[must_use]
370 pub fn bids(&self) -> SmallVec<[PriceLevel; N]> {
371 use rust_decimal::Decimal;
372 use rust_decimal::prelude::FromPrimitive;
373
374 let mut levels = SmallVec::<[PriceLevel; N]>::with_capacity(20);
375 let prices_flat = self.bids.prices.flat();
376 let quantities_flat = self.bids.quantities.flat();
377
378 for i in 0..self.bids.count {
379 if let (Some(price), Some(quantity)) = (
380 Decimal::from_f64(prices_flat[i]),
381 Decimal::from_f64(quantities_flat[i]),
382 ) {
383 levels.push(PriceLevel::new(price, quantity));
384 }
385 }
386
387 levels
388 }
389
390 #[must_use]
392 pub fn asks(&self) -> SmallVec<[PriceLevel; N]> {
393 use rust_decimal::Decimal;
394 use rust_decimal::prelude::FromPrimitive;
395
396 let mut levels = SmallVec::<[PriceLevel; N]>::with_capacity(20);
397 let prices_flat = self.asks.prices.flat();
398 let quantities_flat = self.asks.quantities.flat();
399
400 for i in 0..self.asks.count {
401 if let (Some(price), Some(quantity)) = (
402 Decimal::from_f64(prices_flat[i]),
403 Decimal::from_f64(quantities_flat[i]),
404 ) {
405 levels.push(PriceLevel::new(price, quantity));
406 }
407 }
408
409 levels
410 }
411
412 #[inline(always)]
414 pub fn apply_snapshot<const M: usize>(
415 &mut self,
416 snapshot: crate::data::book_snapshot::OrderBookSnapshot<M>,
417 ) {
418 self.exchange_timestamp_ns = snapshot.timestamp_event;
419 self.system_timestamp_ns = crate::common::current_time_ns();
420
421 let bid_levels: SmallVec<[PriceLevel; M]> = snapshot
423 .bids
424 .into_iter()
425 .map(|level| PriceLevel::new(level.price, level.quantity))
426 .collect();
427 self.bids = SimdPriceLevels::from_smallvec(&bid_levels);
428
429 let ask_levels: SmallVec<[PriceLevel; M]> = snapshot
431 .asks
432 .into_iter()
433 .map(|level| PriceLevel::new(level.price, level.quantity))
434 .collect();
435 self.asks = SimdPriceLevels::from_smallvec(&ask_levels);
436 }
437}
438
439#[derive(Debug, Clone)]
444pub struct SharedSimdOrderBook<const N: usize = 64>(Arc<RwLock<SimdOrderBook<N>>>);
445
446impl<const N: usize> SharedSimdOrderBook<N> {
447 #[must_use]
449 pub fn new(order_book: SimdOrderBook<N>) -> Self {
450 Self(Arc::new(RwLock::new(order_book)))
451 }
452
453 #[must_use]
455 pub fn from_orderbook<const M: usize>(order_book: &OrderBook<M>) -> Self {
456 let simd_book = SimdOrderBook::from_orderbook(order_book);
457 Self::new(simd_book)
458 }
459
460 #[must_use]
462 pub fn from_shared_orderbook<const M: usize>(
463 shared_book: &crate::data::orderbook::SharedOrderBook<M>,
464 ) -> Self {
465 shared_book.read(Self::from_orderbook)
467 }
468
469 pub fn read<R, F>(&self, f: F) -> R
475 where
476 F: FnOnce(&SimdOrderBook<N>) -> R,
477 {
478 let guard = self.0.read().expect("RwLock poisoned");
479 f(&guard)
480 }
481
482 pub fn write<R, F>(&self, f: F) -> R
488 where
489 F: FnOnce(&mut SimdOrderBook<N>) -> R,
490 {
491 let mut guard = self.0.write().expect("RwLock poisoned");
492 f(&mut guard)
493 }
494
495 #[must_use]
497 pub fn symbol(&self) -> String {
498 self.read(|book| book.symbol.clone())
499 }
500
501 #[must_use]
503 pub fn exchange_timestamp_ns(&self) -> u64 {
504 self.read(|book| book.exchange_timestamp_ns)
505 }
506
507 #[must_use]
509 pub fn system_timestamp_ns(&self) -> u64 {
510 self.read(|book| book.system_timestamp_ns)
511 }
512
513 #[must_use]
515 pub fn mid_price(&self) -> Option<f64> {
516 self.read(SimdOrderBook::mid_price)
517 }
518
519 #[must_use]
521 pub fn spread(&self) -> Option<f64> {
522 self.read(SimdOrderBook::spread)
523 }
524
525 #[must_use]
527 pub fn imbalance(&self) -> Option<f64> {
528 self.read(SimdOrderBook::imbalance)
529 }
530
531 #[must_use]
533 pub fn side_vwap(&self, is_bid: bool, target_volume: f64) -> Option<f64> {
534 self.read(|book| book.side_vwap(is_bid, target_volume))
535 }
536
537 #[must_use]
539 pub fn total_bid_volume(&self) -> f64 {
540 self.read(|book| book.bids.total_volume())
541 }
542
543 #[must_use]
545 pub fn total_ask_volume(&self) -> f64 {
546 self.read(|book| book.asks.total_volume())
547 }
548
549 pub fn update_from_orderbook<const M: usize>(&self, book: &OrderBook<M>) {
552 self.write(|simd_book| {
553 *simd_book = SimdOrderBook::from_orderbook(book);
554 });
555 }
556}
557
558pub type SimdPriceLevels64 = SimdPriceLevels<64>;
560pub(crate) type SimdPriceLevels32 = SimdPriceLevels<32>;
562pub(crate) type SimdPriceLevels128 = SimdPriceLevels<128>;
564
565pub type SimdOrderBook64 = SimdOrderBook<64>;
567pub(crate) type SimdOrderBook32 = SimdOrderBook<32>;
569pub(crate) type SimdOrderBook128 = SimdOrderBook<128>;
571
572pub type SharedSimdOrderBook64 = SharedSimdOrderBook<64>;
574pub(crate) type SharedSimdOrderBook32 = SharedSimdOrderBook<32>;
576pub(crate) type SharedSimdOrderBook128 = SharedSimdOrderBook<128>;
578
579pub use SharedSimdOrderBook64 as DefaultSharedSimdOrderBook;
581pub use SimdOrderBook64 as DefaultSimdOrderBook;
582pub use SimdPriceLevels64 as DefaultSimdPriceLevels;
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587 use rust_decimal::{Decimal, prelude::FromPrimitive};
588
589 #[test]
590 fn test_simd_price_levels_total_volume() {
591 let levels = vec![
592 PriceLevel::new(
593 Decimal::from_f64(100.0).unwrap(),
594 Decimal::from_f64(10.0).unwrap(),
595 ),
596 PriceLevel::new(
597 Decimal::from_f64(99.5).unwrap(),
598 Decimal::from_f64(20.0).unwrap(),
599 ),
600 PriceLevel::new(
601 Decimal::from_f64(99.0).unwrap(),
602 Decimal::from_f64(15.0).unwrap(),
603 ),
604 ];
605
606 let simd_levels = SimdPriceLevels::<64>::from_levels(&levels);
607 let total = simd_levels.total_volume();
608
609 assert!((total - 45.0).abs() < 1e-10);
610 }
611
612 #[test]
613 fn test_simd_orderbook_operations() {
614 let mut bids = SmallVec::<[PriceLevel; 64]>::with_capacity(20);
615 bids.push(PriceLevel::new(
616 Decimal::from_f64(100.0).unwrap(),
617 Decimal::from_f64(10.0).unwrap(),
618 ));
619 bids.push(PriceLevel::new(
620 Decimal::from_f64(99.5).unwrap(),
621 Decimal::from_f64(20.0).unwrap(),
622 ));
623
624 let mut asks = SmallVec::<[PriceLevel; 64]>::with_capacity(20);
625 asks.push(PriceLevel::new(
626 Decimal::from_f64(100.5).unwrap(),
627 Decimal::from_f64(15.0).unwrap(),
628 ));
629 asks.push(PriceLevel::new(
630 Decimal::from_f64(101.0).unwrap(),
631 Decimal::from_f64(25.0).unwrap(),
632 ));
633
634 let book = OrderBook::new("TEST", 0, 0, bids, asks);
635 let simd_book = SimdOrderBook::<64>::from_orderbook(&book);
636
637 let mid = simd_book.mid_price().unwrap();
639 assert!((mid - 100.25).abs() < 1e-10);
640
641 let spread = simd_book.spread().unwrap();
643 assert!((spread - 0.5).abs() < 1e-10);
644
645 let imbalance = simd_book.imbalance().unwrap();
647 assert!((imbalance - (-0.142_857_1)).abs() < 1e-6); }
649
650 #[test]
651 fn test_shared_simd_orderbook() {
652 let mut bids = SmallVec::<[PriceLevel; 64]>::with_capacity(20);
653 bids.push(PriceLevel::new(
654 Decimal::from_f64(100.0).unwrap(),
655 Decimal::from_f64(10.0).unwrap(),
656 ));
657
658 let mut asks = SmallVec::<[PriceLevel; 64]>::with_capacity(20);
659 asks.push(PriceLevel::new(
660 Decimal::from_f64(100.5).unwrap(),
661 Decimal::from_f64(15.0).unwrap(),
662 ));
663
664 let book = OrderBook::new("TEST", 0, 0, bids, asks);
665 let shared_simd_book = SharedSimdOrderBook::<64>::from_orderbook(&book);
666
667 let symbol = shared_simd_book.symbol();
669 assert_eq!(symbol, "TEST");
670
671 let mid = shared_simd_book.mid_price().unwrap();
672 assert!((mid - 100.25).abs() < 1e-10);
673
674 let spread = shared_simd_book.spread().unwrap();
675 assert!((spread - 0.5).abs() < 1e-10);
676
677 let bid_volume = shared_simd_book.total_bid_volume();
678 assert!((bid_volume - 10.0).abs() < 1e-10);
679
680 let ask_volume = shared_simd_book.total_ask_volume();
681 assert!((ask_volume - 15.0).abs() < 1e-10);
682
683 let mut new_bids = SmallVec::<[PriceLevel; 64]>::with_capacity(20);
685 new_bids.push(PriceLevel::new(
686 Decimal::from_f64(99.0).unwrap(),
687 Decimal::from_f64(20.0).unwrap(),
688 ));
689
690 let mut new_asks = SmallVec::<[PriceLevel; 64]>::with_capacity(20);
691 new_asks.push(PriceLevel::new(
692 Decimal::from_f64(101.0).unwrap(),
693 Decimal::from_f64(25.0).unwrap(),
694 ));
695
696 let new_book = OrderBook::new("TEST", 1000, 2000, new_bids, new_asks);
697 shared_simd_book.update_from_orderbook(&new_book);
698
699 let new_mid = shared_simd_book.mid_price().unwrap();
701 assert!((new_mid - 100.0).abs() < 1e-10); let new_spread = shared_simd_book.spread().unwrap();
704 assert!((new_spread - 2.0).abs() < 1e-10); }
706
707 #[test]
708 fn test_shared_orderbook_conversion() {
709 use crate::data::orderbook::SharedOrderBook;
710
711 let mut bids = SmallVec::<[PriceLevel; 64]>::with_capacity(20);
712 bids.push(PriceLevel::new(
713 Decimal::from_f64(50000.0).unwrap(),
714 Decimal::from_f64(1.5).unwrap(),
715 ));
716
717 let mut asks = SmallVec::<[PriceLevel; 64]>::with_capacity(20);
718 asks.push(PriceLevel::new(
719 Decimal::from_f64(50100.0).unwrap(),
720 Decimal::from_f64(2.0).unwrap(),
721 ));
722
723 let book = OrderBook::new("BTC-USDT", 1_000_000, 2_000_000, bids, asks);
724 let shared_book = SharedOrderBook::new(book);
725
726 let shared_simd_book = SharedSimdOrderBook::<64>::from_shared_orderbook(&shared_book);
728
729 assert_eq!(shared_simd_book.symbol(), "BTC-USDT");
731 assert_eq!(shared_simd_book.exchange_timestamp_ns(), 1_000_000);
732 assert_eq!(shared_simd_book.system_timestamp_ns(), 2_000_000);
733
734 let mid = shared_simd_book.mid_price().unwrap();
735 assert!((mid - 50050.0).abs() < 1e-6); let spread = shared_simd_book.spread().unwrap();
738 assert!((spread - 100.0).abs() < 1e-6); }
740
741 #[test]
742 fn test_const_generic_sizes() {
743 let levels = vec![
745 PriceLevel::new(
746 Decimal::from_f64(100.0).unwrap(),
747 Decimal::from_f64(10.0).unwrap(),
748 ),
749 PriceLevel::new(
750 Decimal::from_f64(99.5).unwrap(),
751 Decimal::from_f64(20.0).unwrap(),
752 ),
753 ];
754
755 let simd_levels_32 = SimdPriceLevels::<32>::from_levels(&levels);
756 let simd_levels_128 = SimdPriceLevels::<128>::from_levels(&levels);
757
758 assert_eq!(simd_levels_32.count, 2);
759 assert_eq!(simd_levels_128.count, 2);
760 assert!((simd_levels_32.total_volume() - 30.0).abs() < 1e-10);
761 assert!((simd_levels_128.total_volume() - 30.0).abs() < 1e-10);
762 }
763}