1use super::PriceAmount;
2use crate::Dec;
3
4const DEFAULT_CAPACITY: usize = 64;
5
6#[derive(Clone, Debug)]
11pub struct OrderBookSide {
12 levels: Vec<PriceAmount>,
21 desc: bool,
22 max_depth: Option<usize>,
23}
24
25#[derive(Clone, Debug)]
29pub struct OrderBook {
30 pub bids: OrderBookSide,
32 pub asks: OrderBookSide,
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub struct OrderBookTop {
40 pub bid: PriceAmount,
42 pub ask: PriceAmount,
44}
45
46#[derive(Clone, Debug, Default)]
48pub struct OrderBookDiff {
49 pub bids: Vec<PriceAmount>,
51 pub asks: Vec<PriceAmount>,
53}
54
55impl OrderBookSide {
56 fn bids(max_depth: Option<usize>) -> Self {
57 Self::new(true, max_depth)
58 }
59
60 fn asks(max_depth: Option<usize>) -> Self {
61 Self::new(false, max_depth)
62 }
63
64 fn new(desc: bool, max_depth: Option<usize>) -> Self {
65 let capacity = max_depth.unwrap_or(DEFAULT_CAPACITY).min(DEFAULT_CAPACITY);
70 Self {
71 levels: Vec::with_capacity(capacity),
72 desc,
73 max_depth,
74 }
75 }
76
77 pub fn len(&self) -> usize {
79 self.levels.len()
80 }
81
82 pub fn is_empty(&self) -> bool {
84 self.levels.is_empty()
85 }
86
87 pub fn max_depth(&self) -> Option<usize> {
89 self.max_depth
90 }
91
92 pub fn iter(&self) -> std::slice::Iter<'_, PriceAmount> {
94 self.levels.iter()
95 }
96
97 pub fn find(&self, price: Dec) -> Option<&PriceAmount> {
101 self.search(price).ok().map(|index| &self.levels[index])
102 }
103
104 pub fn best(&self) -> Option<&PriceAmount> {
106 self.levels.first()
107 }
108
109 pub fn best_price(&self) -> Option<Dec> {
111 self.best().map(|level| level.price)
112 }
113
114 pub fn worst(&self) -> Option<&PriceAmount> {
116 self.levels.last()
117 }
118
119 pub fn worst_price(&self) -> Option<Dec> {
121 self.worst().map(|level| level.price)
122 }
123
124 pub fn at(&self, n: usize) -> Option<&PriceAmount> {
126 self.levels.get(n)
127 }
128
129 pub fn price_at(&self, n: usize) -> Option<Dec> {
131 self.at(n).map(|level| level.price)
132 }
133
134 pub fn set(&mut self, entry: PriceAmount) -> Option<Dec> {
141 if !entry.is_valid() {
142 return None;
143 }
144 match self.search(entry.price) {
145 Ok(index) => {
146 let previous = self.levels[index].amount;
147 if entry.amount.is_zero() {
148 self.levels.remove(index);
149 } else {
150 self.levels[index].amount = entry.amount;
151 }
152 Some(previous)
153 }
154 Err(index) => {
155 if entry.amount.is_zero() || self.max_depth.is_some_and(|depth| index >= depth) {
156 return None;
157 }
158 self.levels.insert(index, entry);
159 if let Some(depth) = self.max_depth {
160 self.trim(depth);
161 }
162 None
163 }
164 }
165 }
166
167 pub fn set_price_amount(&mut self, price: Dec, amount: Dec) -> Option<Dec> {
169 self.set(PriceAmount { price, amount })
170 }
171
172 pub fn trim(&mut self, depth: usize) {
174 self.levels.truncate(depth);
175 }
176
177 pub fn range(&self, low: Dec, high: Dec) -> std::slice::Iter<'_, PriceAmount> {
182 let (start, end) = match self.desc {
183 true => (
184 self.levels.partition_point(|level| level.price > high),
185 self.levels.partition_point(|level| level.price >= low),
186 ),
187 false => (
188 self.levels.partition_point(|level| level.price < low),
189 self.levels.partition_point(|level| level.price <= high),
190 ),
191 };
192 self.levels[start..end.max(start)].iter()
195 }
196
197 pub fn volume_at(&self, level: usize) -> Option<Dec> {
199 self.levels
200 .iter()
201 .take(level)
202 .map(|entry| entry.amount)
203 .reduce(|acc, x| acc + x)
204 }
205
206 fn volume_in(&self, from: usize, to: usize) -> Option<Dec> {
214 self.levels
215 .iter()
216 .take(to)
217 .skip(from)
218 .map(|entry| entry.amount)
219 .reduce(|total, amount| total + amount)
220 }
221
222 pub fn price_for_quantity(&self, quantity: f64) -> Option<f64> {
225 if quantity <= 0.0 {
226 return None;
227 }
228 let mut accumulated = 0.0;
229 let mut notional = 0.0;
230 for entry in self.levels.iter() {
231 let amount = (quantity - accumulated).min(entry.amount.to_f64());
232 notional += amount * entry.price.to_f64();
233 accumulated += amount;
234 if accumulated >= quantity {
235 return Some(notional / accumulated);
236 }
237 }
238 None
239 }
240
241 pub fn amount_mean(&self) -> Option<f64> {
249 match self.levels.len() {
250 0 => None,
251 count => Some(self.total_amount().to_f64() / count as f64),
252 }
253 }
254
255 fn total_amount(&self) -> Dec {
257 self.levels.iter().map(|level| level.amount).sum()
258 }
259
260 pub fn amount_std_dev(&self) -> Option<f64> {
274 let count = self.levels.len();
275 if count == 0 {
276 return None;
277 }
278 let n = count as f64;
279 let mean = self.total_amount().to_f64() / n;
280 let variance = self
281 .levels
282 .iter()
283 .map(|level| {
284 let deviation = level.amount.to_f64() - mean;
285 deviation * deviation
286 })
287 .sum::<f64>()
288 / n;
289 Some(variance.sqrt())
290 }
291
292 #[inline]
293 fn search(&self, price: Dec) -> Result<usize, usize> {
294 match self.desc {
295 true => self
296 .levels
297 .binary_search_by(|level| price.cmp(&level.price)),
298 false => self
299 .levels
300 .binary_search_by(|level| level.price.cmp(&price)),
301 }
302 }
303}
304
305impl Default for OrderBook {
306 fn default() -> Self {
307 Self::new(None)
308 }
309}
310
311impl OrderBook {
312 pub fn new(max_depth: Option<usize>) -> Self {
315 Self {
316 bids: OrderBookSide::bids(max_depth),
317 asks: OrderBookSide::asks(max_depth),
318 }
319 }
320
321 pub fn max_depth(&self) -> Option<usize> {
323 self.bids.max_depth()
324 }
325
326 pub fn mid_price(&self) -> Option<Dec> {
330 match (self.bids.best_price(), self.asks.best_price()) {
331 (Some(bid), Some(ask)) => Some(bid.midpoint(ask)),
332 _ => None,
333 }
334 }
335
336 pub fn spread(&self) -> Option<Dec> {
340 match (self.bids.best_price(), self.asks.best_price()) {
341 (Some(bid), Some(ask)) => Some(ask - bid),
342 _ => None,
343 }
344 }
345
346 pub fn imbalance(&self) -> Option<Dec> {
360 imbalance_of(self.bids.best()?.amount, self.asks.best()?.amount)
361 }
362
363 pub fn range_imbalance(&self, from: usize, to: usize) -> Option<Dec> {
379 imbalance_of(
380 self.bids.volume_in(from, to)?,
381 self.asks.volume_in(from, to)?,
382 )
383 }
384
385 pub fn apply_diff(&mut self, diff: &OrderBookDiff) {
387 for bid in diff.bids.iter().copied() {
388 self.bids.set(bid);
389 }
390 for ask in diff.asks.iter().copied() {
391 self.asks.set(ask);
392 }
393 }
394}
395
396fn imbalance_of(bid: Dec, ask: Dec) -> Option<Dec> {
403 let total = bid.checked_add(ask)?;
404 bid.checked_sub(ask)?.checked_div(total)
405}
406
407impl From<OrderBookTop> for OrderBook {
408 fn from(top: OrderBookTop) -> Self {
409 let mut order_book = OrderBook::default();
410 order_book.bids.set(top.bid);
411 order_book.asks.set(top.ask);
412 order_book
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 #![allow(clippy::unwrap_used, clippy::expect_used)]
419
420 use super::*;
421 use crate::dec;
422 use crate::testing::{OrderBookBuilder, RandomWalk, Side};
423
424 fn bids(levels: &[(Dec, Dec)], max_depth: Option<usize>) -> OrderBookSide {
425 let mut side = OrderBookSide::bids(max_depth);
426 for (price, amount) in levels.iter().copied() {
427 side.set_price_amount(price, amount);
428 }
429 side
430 }
431
432 fn asks(levels: &[(Dec, Dec)], max_depth: Option<usize>) -> OrderBookSide {
433 let mut side = OrderBookSide::asks(max_depth);
434 for (price, amount) in levels.iter().copied() {
435 side.set_price_amount(price, amount);
436 }
437 side
438 }
439
440 #[test]
441 fn test_bids_are_ordered_best_first() {
442 let side = bids(
443 &[
444 (dec!(99), dec!(1)),
445 (dec!(101), dec!(2)),
446 (dec!(100), dec!(3)),
447 ],
448 None,
449 );
450 let prices: Vec<Dec> = side.iter().map(|level| level.price).collect();
451 assert_eq!(prices, vec![dec!(101), dec!(100), dec!(99)]);
452 assert_eq!(side.best_price(), Some(dec!(101)));
453 assert_eq!(side.worst_price(), Some(dec!(99)));
454 assert_eq!(side.price_at(1), Some(dec!(100)));
455 }
456
457 #[test]
458 fn test_asks_are_ordered_best_first() {
459 let side = asks(
460 &[
461 (dec!(101), dec!(1)),
462 (dec!(99), dec!(2)),
463 (dec!(100), dec!(3)),
464 ],
465 None,
466 );
467 let prices: Vec<Dec> = side.iter().map(|level| level.price).collect();
468 assert_eq!(prices, vec![dec!(99), dec!(100), dec!(101)]);
469 assert_eq!(side.best_price(), Some(dec!(99)));
470 assert_eq!(side.worst_price(), Some(dec!(101)));
471 }
472
473 #[test]
474 fn test_set_replaces_and_removes() {
475 let mut side = bids(&[(dec!(100), dec!(1)), (dec!(99), dec!(2))], None);
476 assert_eq!(side.set_price_amount(dec!(100), dec!(5)), Some(dec!(1)));
477 assert_eq!(side.volume_at(side.len()), Some(dec!(7)));
478 assert_eq!(side.set_price_amount(dec!(100), dec!(0)), Some(dec!(5)));
479 assert_eq!(side.len(), 1);
480 assert_eq!(side.volume_at(side.len()), Some(dec!(2)));
481 assert_eq!(side.find(dec!(100)), None);
482 assert_eq!(side.set_price_amount(dec!(98), dec!(0)), None);
483 }
484
485 #[test]
486 fn test_is_valid_rejects_what_a_book_cannot_hold() {
487 let valid = PriceAmount {
488 price: dec!(100),
489 amount: dec!(1),
490 };
491 assert!(valid.is_valid());
492 assert!(
493 PriceAmount {
494 price: dec!(100),
495 amount: Dec::ZERO,
496 }
497 .is_valid(),
498 "a zero amount is the removal of a level, not an invalid one"
499 );
500 for bad in [
501 PriceAmount {
502 price: Dec::NAN,
503 amount: dec!(1),
504 },
505 PriceAmount {
506 price: dec!(100),
507 amount: Dec::NAN,
508 },
509 PriceAmount {
510 price: dec!(100),
511 amount: dec!(-1),
512 },
513 ] {
514 assert!(!bad.is_valid(), "{bad:?}");
515 }
516 }
517
518 #[test]
519 fn test_an_invalid_level_leaves_the_book_untouched() {
520 let mut side = bids(&[(dec!(100), dec!(1)), (dec!(99), dec!(2))], None);
521 for bad in [
522 (Dec::NAN, dec!(5)),
523 (dec!(101), Dec::NAN),
524 (dec!(101), dec!(-5)),
525 ] {
526 assert_eq!(side.set_price_amount(bad.0, bad.1), None);
527 }
528 let prices: Vec<Dec> = side.iter().map(|level| level.price).collect();
529 assert_eq!(prices, vec![dec!(100), dec!(99)]);
530 assert_eq!(side.volume_at(side.len()), Some(dec!(3)));
533 assert_eq!(side.amount_mean(), Some(1.5));
534 }
535
536 #[test]
537 fn test_range_with_low_above_high_is_empty() {
538 let side = asks(&[(dec!(100), dec!(1)), (dec!(101), dec!(1))], None);
539 assert_eq!(side.range(dec!(101), dec!(100)).count(), 0);
540 let side = bids(&[(dec!(101), dec!(1)), (dec!(100), dec!(1))], None);
541 assert_eq!(side.range(dec!(101), dec!(100)).count(), 0);
542 }
543
544 #[test]
545 fn test_a_large_max_depth_does_not_reserve_it() {
546 let mut book = OrderBook::new(Some(usize::MAX));
548 book.asks.set_price_amount(dec!(100), dec!(1));
549 assert_eq!(book.asks.len(), 1);
550 assert_eq!(book.max_depth(), Some(usize::MAX));
551 }
552
553 #[test]
554 fn test_max_depth_drops_worst_levels() {
555 let mut side = bids(
556 &[
557 (dec!(100), dec!(1)),
558 (dec!(99), dec!(2)),
559 (dec!(98), dec!(3)),
560 ],
561 Some(2),
562 );
563 assert_eq!(side.len(), 2);
564 assert_eq!(side.volume_at(side.len()), Some(dec!(3)));
565 side.set_price_amount(dec!(101), dec!(4));
567 assert_eq!(side.len(), 2);
568 assert_eq!(side.best_price(), Some(dec!(101)));
569 assert_eq!(side.worst_price(), Some(dec!(100)));
570 assert_eq!(side.volume_at(side.len()), Some(dec!(5)));
571 assert_eq!(side.set_price_amount(dec!(97), dec!(9)), None);
573 assert_eq!(side.len(), 2);
574 }
575
576 #[test]
577 fn test_trim() {
578 let mut side = asks(
579 &[
580 (dec!(100), dec!(1)),
581 (dec!(101), dec!(2)),
582 (dec!(102), dec!(3)),
583 ],
584 None,
585 );
586 side.trim(1);
587 assert_eq!(side.len(), 1);
588 assert_eq!(side.volume_at(side.len()), Some(dec!(1)));
589 assert_eq!(side.amount_std_dev(), Some(0.0));
590 }
591
592 #[test]
593 fn test_volume_and_stats() {
594 let side = asks(
595 &[
596 (dec!(100), dec!(1)),
597 (dec!(101), dec!(2)),
598 (dec!(102), dec!(3)),
599 ],
600 None,
601 );
602 assert_eq!(side.volume_at(2), Some(dec!(3)));
603 assert_eq!(side.volume_at(10), Some(dec!(6)));
604 assert_eq!(side.amount_mean(), Some(2.0));
605 let std_dev = side.amount_std_dev().expect("non empty side");
606 assert!((std_dev - (2.0_f64 / 3.0).sqrt()).abs() < 1e-12);
607 }
608
609 #[test]
610 fn test_price_for_quantity() {
611 let side = asks(&[(dec!(100), dec!(1)), (dec!(102), dec!(1))], None);
612 assert_eq!(side.price_for_quantity(1.0), Some(100.0));
613 assert_eq!(side.price_for_quantity(2.0), Some(101.0));
614 assert_eq!(side.price_for_quantity(3.0), None);
615 assert_eq!(side.price_for_quantity(0.0), None);
616 }
617
618 #[test]
619 fn test_imbalance_at_the_touch() {
620 let mut book = OrderBook::new(None);
621 assert_eq!(book.imbalance(), None, "an empty book has no pressure");
622
623 book.bids.set_price_amount(dec!(100), dec!(3));
624 assert_eq!(book.imbalance(), None, "one side alone is not an imbalance");
625
626 book.asks.set_price_amount(dec!(101), dec!(1));
627 assert_eq!(book.imbalance(), Some(dec!(0.5)));
629
630 book.asks.set_price_amount(dec!(101), dec!(3));
632 assert_eq!(book.imbalance(), Some(Dec::ZERO));
633
634 book.bids.set_price_amount(dec!(100), dec!(1));
636 assert_eq!(book.imbalance(), Some(dec!(-0.5)));
637 }
638
639 #[test]
640 fn test_imbalance_reaches_its_limits_and_stops() {
641 let mut book = OrderBook::new(None);
642 book.bids.set_price_amount(dec!(100), dec!(1));
645 book.asks.set_price_amount(dec!(101), Dec::EPSILON);
646 let imbalance = book.imbalance().expect("both sides stand");
647 assert!(imbalance < Dec::ONE, "{imbalance} reached the limit");
648 assert!(imbalance > dec!(0.999999), "{imbalance} fell short of it");
649 }
650
651 #[test]
652 fn test_range_imbalance_weighs_a_band() {
653 let mut book = OrderBook::new(None);
654 for level in 0..4 {
655 let step = dec!(0.01) * Dec::from(level);
656 book.bids.set_price_amount(dec!(100) - step, dec!(2));
657 book.asks.set_price_amount(dec!(100.01) + step, Dec::ONE);
658 }
659 assert_eq!(book.range_imbalance(0, 4), Some(dec!(0.333333333333333333)));
661 assert_eq!(book.range_imbalance(0, 1), book.imbalance());
663 assert_eq!(book.range_imbalance(2, 4), Some(dec!(0.333333333333333333)));
665 assert_eq!(book.range_imbalance(2, 2), None);
667 assert_eq!(book.range_imbalance(3, 1), None);
668 assert_eq!(book.range_imbalance(0, 99), book.range_imbalance(0, 4));
670 assert_eq!(book.range_imbalance(9, 99), None);
671 }
672
673 #[test]
674 fn test_range_imbalance_of_an_empty_or_inverted_band_is_none() {
675 let mut book = OrderBook::new(None);
676 for level in 0..4 {
677 let step = dec!(0.01) * Dec::from(level);
678 book.bids.set_price_amount(dec!(100) - step, dec!(2));
679 book.asks.set_price_amount(dec!(100.01) + step, Dec::ONE);
680 }
681 for from in 0..7 {
683 for to in 0..=from {
684 assert_eq!(
685 book.range_imbalance(from, to),
686 None,
687 "range_imbalance({from}, {to}) read a band with no levels in it"
688 );
689 }
690 }
691 assert!(book.range_imbalance(0, 1).is_some());
693 }
694
695 #[test]
696 fn test_range_imbalance_on_an_unevenly_deep_book() {
697 let mut book = OrderBook::new(None);
698 book.bids.set_price_amount(dec!(100), dec!(4));
699 book.bids.set_price_amount(dec!(99), dec!(4));
700 book.asks.set_price_amount(dec!(101), dec!(2));
701 assert_eq!(book.range_imbalance(0, 2), Some(dec!(0.6)));
703 assert_eq!(book.range_imbalance(1, 2), None);
705 }
706
707 #[test]
708 fn test_apply_diff() {
709 let mut book = OrderBook::new(None);
710 book.apply_diff(&OrderBookDiff {
711 bids: vec![PriceAmount {
712 price: dec!(99),
713 amount: dec!(1),
714 }],
715 asks: vec![PriceAmount {
716 price: dec!(101),
717 amount: dec!(2),
718 }],
719 });
720 assert_eq!(book.mid_price(), Some(dec!(100)));
721 assert_eq!(book.spread(), Some(dec!(2)));
722 book.apply_diff(&OrderBookDiff {
723 bids: vec![PriceAmount {
724 price: dec!(99),
725 amount: dec!(0),
726 }],
727 ..Default::default()
728 });
729 assert!(book.bids.is_empty());
730 assert_eq!(book.mid_price(), None);
731 }
732
733 #[test]
734 fn test_order_book_from_top() {
735 let top = OrderBookTop {
736 bid: PriceAmount {
737 price: dec!(99),
738 amount: dec!(1),
739 },
740 ask: PriceAmount {
741 price: dec!(101),
742 amount: dec!(2),
743 },
744 };
745 let book = OrderBook::from(top);
746 assert_eq!(book.bids.best_price(), Some(dec!(99)));
747 assert_eq!(book.asks.best_price(), Some(dec!(101)));
748 assert_eq!(book.mid_price(), Some(dec!(100)));
749 }
750
751 #[test]
754 fn test_simulate_book_never_crossed() {
755 let mut builder = OrderBookBuilder::new()
756 .with_tick_size(dec!(0.01))
757 .with_spread(dec!(0.02))
758 .with_amount(dec!(10));
759 let prices = RandomWalk::new(100).lognormal(100.0, 0.0, 0.2).unwrap();
760 for mid in prices {
761 let (book, _) = builder.simulate(Dec::from_f64(mid).unwrap(), 5);
762 let bid = book.bids.best_price().expect("bids must be non-empty");
763 let ask = book.asks.best_price().expect("asks must be non-empty");
764 assert!(
765 bid < ask,
766 "book crossed after simulate: bid={bid} ask={ask}"
767 );
768 assert_eq!(book.spread(), Some(ask - bid));
769 assert_eq!(
770 book.bids.len(),
771 5,
772 "expected 5 bid levels, got {}",
773 book.bids.len()
774 );
775 assert_eq!(
776 book.asks.len(),
777 5,
778 "expected 5 ask levels, got {}",
779 book.asks.len()
780 );
781 }
782 }
783
784 #[test]
787 fn test_simulate_all_amounts_positive() {
788 let mut builder = OrderBookBuilder::new()
789 .with_tick_size(dec!(0.01))
790 .with_spread(dec!(0.02))
791 .with_amount(dec!(10));
792 let prices = RandomWalk::new(100).lognormal(100.0, 0.0, 0.2).unwrap();
793 for mid in prices {
794 let (book, _) = builder.simulate(Dec::from_f64(mid).unwrap(), 20);
795 for level in book.bids.iter() {
796 assert!(
797 level.amount > Dec::ZERO,
798 "bid level at {} has non-positive amount {}",
799 level.price,
800 level.amount
801 );
802 }
803 for level in book.asks.iter() {
804 assert!(
805 level.amount > Dec::ZERO,
806 "ask level at {} has non-positive amount {}",
807 level.price,
808 level.amount
809 );
810 }
811 }
812 }
813
814 #[test]
816 fn test_simulate_prices_and_amounts_aligned() {
817 let tick = dec!(0.01);
818 let lot = dec!(0.01);
819 let mut builder = OrderBookBuilder::new()
820 .with_tick_size(tick)
821 .with_lot_size(lot)
822 .with_spread(dec!(0.02))
823 .with_amount(dec!(10));
824 let prices = RandomWalk::new(100).lognormal(100.0, 0.0, 0.2).unwrap();
825 for mid in prices {
826 let (book, _) = builder.simulate(Dec::from_f64(mid).unwrap(), 20);
827 for level in book.bids.iter().chain(book.asks.iter()) {
828 assert_eq!(
829 level.price.into_raw() % tick.into_raw(),
830 0,
831 "price {} is not a multiple of tick {}",
832 level.price,
833 tick
834 );
835 assert_eq!(
836 level.amount.into_raw() % lot.into_raw(),
837 0,
838 "amount {} is not a multiple of lot {}",
839 level.amount,
840 lot
841 );
842 }
843 }
844 }
845
846 #[test]
849 fn test_simulate_trades_are_crossed_levels() {
850 let mut builder = OrderBookBuilder::new()
851 .with_tick_size(dec!(0.01))
852 .with_spread(dec!(0.02))
853 .with_amount(dec!(10));
854 let prices = RandomWalk::new(100).lognormal(100.0, 0.0, 0.2).unwrap();
855 for mid in prices {
856 let (book, events) = builder.simulate(Dec::from_f64(mid).unwrap(), 20);
857 let bid = book.bids.best_price().expect("bids must be non-empty");
858 let ask = book.asks.best_price().expect("asks must be non-empty");
859 for trade in &events.trades {
860 match trade.side {
861 Side::Sell => assert!(
862 trade.price_amount.price > bid,
863 "bid-side trade at {} is not above new bid {}",
864 trade.price_amount.price,
865 bid
866 ),
867 Side::Buy => assert!(
868 trade.price_amount.price < ask,
869 "ask-side trade at {} is not below new ask {}",
870 trade.price_amount.price,
871 ask
872 ),
873 }
874 }
875 }
876 }
877
878 #[test]
880 fn test_simulate_new_orders_in_book() {
881 let mut builder = OrderBookBuilder::new()
882 .with_tick_size(dec!(0.01))
883 .with_spread(dec!(0.02))
884 .with_amount(dec!(10));
885 let prices = RandomWalk::new(100).lognormal(100.0, 0.0, 0.2).unwrap();
886 for mid in prices {
887 let (book, events) = builder.simulate(Dec::from_f64(mid).unwrap(), 20);
888 for order in &events.new_orders {
889 match order.side {
890 Side::Buy => assert!(
891 book.bids
892 .iter()
893 .any(|level| level.price == order.price_amount.price),
894 "new bid at {} not found in book",
895 order.price_amount.price
896 ),
897 Side::Sell => assert!(
898 book.asks
899 .iter()
900 .any(|level| level.price == order.price_amount.price),
901 "new ask at {} not found in book",
902 order.price_amount.price
903 ),
904 }
905 }
906 }
907 }
908
909 #[test]
912 fn test_simulate_volume_at_properties() {
913 let mut builder = OrderBookBuilder::new()
914 .with_tick_size(dec!(0.01))
915 .with_spread(dec!(0.02))
916 .with_amount(dec!(10));
917 let prices = RandomWalk::new(100).lognormal(100.0, 0.0, 0.2).unwrap();
918 for mid in prices {
919 let (book, _) = builder.simulate(Dec::from_f64(mid).unwrap(), 20);
920 for side in [&book.bids, &book.asks] {
921 assert_eq!(side.volume_at(0), None);
923
924 let best_amount = side.best().unwrap().amount;
926 assert_eq!(side.volume_at(1), Some(best_amount));
927
928 let mut prev = Dec::ZERO;
930 for n in 1..=side.len() {
931 let vol = side.volume_at(n).unwrap();
932 assert!(
933 vol >= prev,
934 "volume_at({n})={vol} < volume_at({})={prev}",
935 n - 1
936 );
937 prev = vol;
938 }
939
940 let total: Dec = side.iter().map(|level| level.amount).sum();
942 assert_eq!(side.volume_at(side.len()), Some(total));
943 }
944 }
945 }
946
947 #[test]
948 fn test_range_empty_returns_nothing() {
949 let book = OrderBookBuilder::new()
950 .with_ask(dec!(100), dec!(1))
951 .with_ask(dec!(101), dec!(1))
952 .build();
953 let result: Vec<_> = book.asks.range(dec!(200), dec!(300)).collect();
954 assert!(result.is_empty());
955 }
956
957 #[test]
958 fn test_range_asks_ascending_best_first() {
959 let book = OrderBookBuilder::new()
960 .with_ask(dec!(100), dec!(1))
961 .with_ask(dec!(101), dec!(2))
962 .with_ask(dec!(102), dec!(3))
963 .with_ask(dec!(103), dec!(4))
964 .build();
965 let prices: Vec<_> = book
966 .asks
967 .range(dec!(100), dec!(102))
968 .map(|l| l.price)
969 .collect();
970 assert_eq!(prices, vec![dec!(100), dec!(101), dec!(102)]);
971 }
972
973 #[test]
974 fn test_range_bids_descending_best_first() {
975 let book = OrderBookBuilder::new()
976 .with_bid(dec!(100), dec!(1))
977 .with_bid(dec!(101), dec!(2))
978 .with_bid(dec!(102), dec!(3))
979 .with_bid(dec!(103), dec!(4))
980 .build();
981 let prices: Vec<_> = book
982 .bids
983 .range(dec!(101), dec!(103))
984 .map(|l| l.price)
985 .collect();
986 assert_eq!(prices, vec![dec!(103), dec!(102), dec!(101)]);
987 }
988
989 #[test]
990 fn test_range_bounds_not_in_book() {
991 let book = OrderBookBuilder::new()
992 .with_ask(dec!(99), dec!(1))
993 .with_ask(dec!(100), dec!(2))
994 .with_ask(dec!(101), dec!(3))
995 .with_ask(dec!(102), dec!(4))
996 .build();
997 let prices: Vec<_> = book
998 .asks
999 .range(dec!(99.5), dec!(101.5))
1000 .map(|l| l.price)
1001 .collect();
1002 assert_eq!(prices, vec![dec!(100), dec!(101)]);
1003 }
1004
1005 #[test]
1006 fn test_amount_stats_after_trim() {
1007 let mut book = OrderBookBuilder::new()
1010 .with_bid(dec!(101), dec!(1))
1011 .with_bid(dec!(102), dec!(2))
1012 .with_bid(dec!(103), dec!(3))
1013 .with_bid(dec!(104), dec!(4))
1014 .with_bid(dec!(105), dec!(5))
1015 .build();
1016 book.bids.trim(3);
1017 assert_eq!(book.bids.len(), 3);
1018 assert_eq!(book.bids.amount_mean(), Some(4.0));
1019 let std_dev = book.bids.amount_std_dev().unwrap();
1020 let expected = (2.0f64 / 3.0).sqrt();
1021 let diff = (std_dev - expected).abs();
1022 assert!(diff < 1e-7, "std_dev={std_dev} expected={expected}");
1023 }
1024
1025 #[test]
1026 fn test_amount_stats_empty() {
1027 let book = OrderBookBuilder::new().build();
1028 assert_eq!(book.bids.amount_mean(), None);
1029 assert_eq!(book.bids.amount_std_dev(), None);
1030 }
1031
1032 #[test]
1033 fn test_amount_stats_single_level() {
1034 let book = OrderBookBuilder::new().with_bid(dec!(100), dec!(5)).build();
1035 assert_eq!(book.bids.amount_mean(), Some(5.0));
1036 assert_eq!(book.bids.amount_std_dev(), Some(0.0));
1037 }
1038
1039 #[test]
1040 fn test_amount_stats_multiple_levels() {
1041 let book = OrderBookBuilder::new()
1043 .with_bid(dec!(101), dec!(1))
1044 .with_bid(dec!(102), dec!(2))
1045 .with_bid(dec!(103), dec!(3))
1046 .with_bid(dec!(104), dec!(4))
1047 .with_bid(dec!(105), dec!(5))
1048 .build();
1049 assert_eq!(book.bids.amount_mean(), Some(3.0));
1050 let std_dev = book.bids.amount_std_dev().unwrap();
1051 let expected = 2.0f64.sqrt();
1052 let diff = (std_dev - expected).abs();
1053 assert!(diff < 1e-7, "std_dev={std_dev} expected={expected}");
1054 }
1055
1056 #[test]
1057 fn test_amount_stats_after_removal() {
1058 let mut book = OrderBookBuilder::new()
1060 .with_bid(dec!(100), dec!(2))
1061 .with_bid(dec!(101), dec!(4))
1062 .with_bid(dec!(102), dec!(6))
1063 .build();
1064 book.bids.set_price_amount(dec!(100), Dec::ZERO);
1065 assert_eq!(book.bids.amount_mean(), Some(5.0));
1066 assert_eq!(book.bids.amount_std_dev(), Some(1.0));
1067 }
1068
1069 #[test]
1070 fn test_amount_stats_after_update() {
1071 let mut book = OrderBookBuilder::new()
1073 .with_bid(dec!(100), dec!(2))
1074 .with_bid(dec!(101), dec!(4))
1075 .build();
1076 book.bids.set_price_amount(dec!(100), dec!(6));
1077 assert_eq!(book.bids.amount_mean(), Some(5.0));
1078 assert_eq!(book.bids.amount_std_dev(), Some(1.0));
1079 }
1080}