1use crate::error::FinError;
20use crate::types::{Price, Quantity, Side, Symbol};
21use rust_decimal::Decimal;
22use std::collections::BTreeMap;
23
24#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
26pub struct PriceLevel {
27 pub price: Price,
29 pub quantity: Quantity,
31}
32
33#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
35pub enum DeltaAction {
36 Set,
38 Remove,
40}
41
42#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
44pub struct BookDelta {
45 pub side: Side,
47 pub price: Price,
49 pub quantity: Quantity,
51 pub action: DeltaAction,
53 pub sequence: u64,
55}
56
57#[derive(Debug, Clone)]
59pub struct OrderBook {
60 pub symbol: Symbol,
62 bids: BTreeMap<Decimal, Decimal>,
65 asks: BTreeMap<Decimal, Decimal>,
67 sequence: u64,
69}
70
71impl OrderBook {
72 pub fn new(symbol: Symbol) -> Self {
74 Self {
75 symbol,
76 bids: BTreeMap::new(),
77 asks: BTreeMap::new(),
78 sequence: 0,
79 }
80 }
81
82 #[allow(clippy::needless_pass_by_value)]
87 pub fn apply_delta(&mut self, delta: BookDelta) -> Result<(), FinError> {
88 let expected = self.sequence + 1;
89 if delta.sequence != expected {
90 return Err(FinError::SequenceMismatch {
91 expected,
92 got: delta.sequence,
93 });
94 }
95 let prev_val = match delta.side {
97 Side::Bid => self.bids.get(&delta.price.value()).copied(),
98 Side::Ask => self.asks.get(&delta.price.value()).copied(),
99 };
100
101 let book_side = match delta.side {
102 Side::Bid => &mut self.bids,
103 Side::Ask => &mut self.asks,
104 };
105 match delta.action {
106 DeltaAction::Set => {
107 book_side.insert(delta.price.value(), delta.quantity.value());
108 }
109 DeltaAction::Remove => {
110 book_side.remove(&delta.price.value());
111 }
112 }
113 self.sequence = delta.sequence;
114
115 let maybe_inversion = {
118 let best_bid_p = self.bids.keys().next_back().copied();
119 let best_ask_p = self.asks.keys().next().copied();
120 match (best_bid_p, best_ask_p) {
121 (Some(b), Some(a)) if b >= a => Some((b, a)),
122 _ => None,
123 }
124 };
125 if let Some((best_bid_p, best_ask_p)) = maybe_inversion {
126 match delta.action {
128 DeltaAction::Set => match delta.side {
129 Side::Bid => {
130 self.bids.remove(&delta.price.value());
131 }
132 Side::Ask => {
133 self.asks.remove(&delta.price.value());
134 }
135 },
136 DeltaAction::Remove => match delta.side {
139 Side::Bid => {
140 if let Some(qty) = prev_val {
141 self.bids.insert(delta.price.value(), qty);
142 }
143 }
144 Side::Ask => {
145 if let Some(qty) = prev_val {
146 self.asks.insert(delta.price.value(), qty);
147 }
148 }
149 },
150 }
151 self.sequence = expected - 1;
152 return Err(FinError::InvertedSpread {
153 best_bid: best_bid_p,
154 best_ask: best_ask_p,
155 });
156 }
157
158 Ok(())
159 }
160
161 pub fn best_bid(&self) -> Option<PriceLevel> {
166 self.bids.iter().next_back().and_then(|(p, q)| {
167 Some(PriceLevel {
168 price: Price::new(*p).ok()?,
169 quantity: Quantity::new(*q).unwrap_or_else(|_| Quantity::zero()),
170 })
171 })
172 }
173
174 pub fn best_quote(&self) -> Option<(PriceLevel, PriceLevel)> {
178 Some((self.best_bid()?, self.best_ask()?))
179 }
180
181 pub fn best_ask(&self) -> Option<PriceLevel> {
186 self.asks.iter().next().and_then(|(p, q)| {
187 Some(PriceLevel {
188 price: Price::new(*p).ok()?,
189 quantity: Quantity::new(*q).unwrap_or_else(|_| Quantity::zero()),
190 })
191 })
192 }
193
194 pub fn mid_price(&self) -> Option<Decimal> {
196 let bid = self.best_bid()?.price.value();
197 let ask = self.best_ask()?.price.value();
198 Some((bid + ask) / Decimal::TWO)
199 }
200
201 pub fn spread(&self) -> Option<Decimal> {
203 let bid = self.best_bid()?.price.value();
204 let ask = self.best_ask()?.price.value();
205 Some(ask - bid)
206 }
207
208 pub fn spread_pct(&self) -> Option<Decimal> {
212 let mid = self.mid_price()?;
213 if mid.is_zero() {
214 return None;
215 }
216 let spread = self.spread()?;
217 Some(spread / mid * Decimal::ONE_HUNDRED)
218 }
219
220 pub fn depth_at(&self, side: Side, price: Price) -> Option<Decimal> {
222 let key = price.value();
223 match side {
224 Side::Bid => self.bids.get(&key).copied(),
225 Side::Ask => self.asks.get(&key).copied(),
226 }
227 }
228
229 pub fn top_bids(&self, n: usize) -> Vec<PriceLevel> {
231 self.bids
232 .iter()
233 .rev()
234 .take(n)
235 .filter_map(|(p, q)| {
236 let price = Price::new(*p).ok()?;
237 let quantity = Quantity::new(*q).ok()?;
238 Some(PriceLevel { price, quantity })
239 })
240 .collect()
241 }
242
243 pub fn top_asks(&self, n: usize) -> Vec<PriceLevel> {
245 self.asks
246 .iter()
247 .take(n)
248 .filter_map(|(p, q)| {
249 let price = Price::new(*p).ok()?;
250 let quantity = Quantity::new(*q).ok()?;
251 Some(PriceLevel { price, quantity })
252 })
253 .collect()
254 }
255
256 pub fn vwap_for_qty(&self, side: Side, qty: Quantity) -> Result<Decimal, FinError> {
263 let target = qty.value();
264 if target <= Decimal::ZERO {
265 return Ok(Decimal::ZERO);
266 }
267 match side {
268 Side::Bid => Self::vwap_fill(self.bids.iter().rev(), target),
269 Side::Ask => Self::vwap_fill(self.asks.iter(), target),
270 }
271 }
272
273 fn vwap_fill<'a>(
274 levels: impl Iterator<Item = (&'a Decimal, &'a Decimal)>,
275 target: Decimal,
276 ) -> Result<Decimal, FinError> {
277 let mut remaining = target;
278 let mut total_cost = Decimal::ZERO;
279
280 for (price, avail_qty) in levels {
281 let fill = remaining.min(*avail_qty);
282 total_cost += fill * price;
283 remaining -= fill;
284 if remaining <= Decimal::ZERO {
285 break;
286 }
287 }
288
289 if remaining > Decimal::ZERO {
290 return Err(FinError::InsufficientLiquidity(target));
291 }
292
293 Ok(total_cost / target)
294 }
295
296 pub fn sequence(&self) -> u64 {
298 self.sequence
299 }
300
301 pub fn snapshot(&self, n: usize) -> (Vec<PriceLevel>, Vec<PriceLevel>) {
306 (self.top_bids(n), self.top_asks(n))
307 }
308
309 pub fn bid_count(&self) -> usize {
311 self.bids.len()
312 }
313
314 pub fn ask_count(&self) -> usize {
316 self.asks.len()
317 }
318
319 pub fn level_count(&self, side: Side) -> usize {
321 match side {
322 Side::Bid => self.bids.len(),
323 Side::Ask => self.asks.len(),
324 }
325 }
326
327 pub fn clear(&mut self) {
329 self.bids.clear();
330 self.asks.clear();
331 self.sequence = 0;
332 }
333
334 pub fn remove_all(&mut self, side: crate::types::Side) {
338 use crate::types::Side;
339 match side {
340 Side::Bid => self.bids.clear(),
341 Side::Ask => self.asks.clear(),
342 }
343 }
344
345 pub fn is_crossed(&self) -> bool {
351 match (self.best_bid(), self.best_ask()) {
352 (Some(bid), Some(ask)) => bid.price >= ask.price,
353 _ => false,
354 }
355 }
356
357 pub fn is_empty(&self) -> bool {
359 self.bids.is_empty() && self.asks.is_empty()
360 }
361
362 pub fn total_levels(&self) -> usize {
364 self.bids.len() + self.asks.len()
365 }
366
367 pub fn cumulative_depth(&self, side: Side, price: Price) -> Decimal {
374 let p = price.value();
375 match side {
376 Side::Bid => self
377 .bids
378 .range(p..)
379 .map(|(_, qty)| *qty)
380 .sum(),
381 Side::Ask => self
382 .asks
383 .range(..=p)
384 .map(|(_, qty)| *qty)
385 .sum(),
386 }
387 }
388
389 pub fn total_bid_volume(&self) -> Decimal {
391 self.bids.values().copied().sum()
392 }
393
394 pub fn total_ask_volume(&self) -> Decimal {
396 self.asks.values().copied().sum()
397 }
398
399 pub fn best_bid_price(&self) -> Option<Price> {
401 self.bids.keys().next_back().and_then(|p| Price::new(*p).ok())
402 }
403
404 pub fn best_ask_price(&self) -> Option<Price> {
406 self.asks.keys().next().and_then(|p| Price::new(*p).ok())
407 }
408
409 pub fn best_bid_qty(&self) -> Option<Quantity> {
411 self.bids
412 .values()
413 .next_back()
414 .and_then(|q| Quantity::new(*q).ok())
415 }
416
417 pub fn best_ask_qty(&self) -> Option<Quantity> {
419 self.asks
420 .values()
421 .next()
422 .and_then(|q| Quantity::new(*q).ok())
423 }
424
425 pub fn liquidity_at_pct(&self, side: Side, pct_from_mid: Decimal) -> Option<Decimal> {
430 let mid = self.mid_price()?;
431 let band = mid * pct_from_mid / Decimal::ONE_HUNDRED;
432 let (lo, hi) = match side {
433 Side::Bid => (mid - band, mid),
434 Side::Ask => (mid, mid + band),
435 };
436 let qty: Decimal = match side {
437 Side::Bid => self
438 .bids
439 .range(lo..=hi)
440 .map(|(_, q)| *q)
441 .sum(),
442 Side::Ask => self
443 .asks
444 .range(lo..=hi)
445 .map(|(_, q)| *q)
446 .sum(),
447 };
448 Some(qty)
449 }
450
451 pub fn has_price(&self, side: Side, price: Price) -> bool {
453 let key = price.value();
454 match side {
455 Side::Bid => self.bids.contains_key(&key),
456 Side::Ask => self.asks.contains_key(&key),
457 }
458 }
459
460 pub fn weighted_mid(&self) -> Option<Decimal> {
466 let bid = self.best_bid()?;
467 let ask = self.best_ask()?;
468 let bid_qty = bid.quantity.value();
469 let ask_qty = ask.quantity.value();
470 let total = bid_qty + ask_qty;
471 if total.is_zero() {
472 return None;
473 }
474 Some((bid.price.value() * ask_qty + ask.price.value() * bid_qty) / total)
475 }
476
477 pub fn imbalance(&self) -> Option<Decimal> {
482 let bid_vol = self.total_bid_volume();
483 let ask_vol = self.total_ask_volume();
484 let total = bid_vol + ask_vol;
485 if total == Decimal::ZERO {
486 return None;
487 }
488 Some((bid_vol - ask_vol) / total)
489 }
490
491 pub fn depth_ratio(&self, n: usize) -> Option<Decimal> {
496 let bid_vol: Decimal = self.bids.values().rev().take(n).copied().sum();
497 let ask_vol: Decimal = self.asks.values().take(n).copied().sum();
498 if ask_vol.is_zero() {
499 return None;
500 }
501 Some(bid_vol / ask_vol)
502 }
503
504 pub fn weighted_mid_price(&self) -> Option<Decimal> {
509 let (bid_p, bid_q) = self.bids.iter().next_back()?;
510 let (ask_p, ask_q) = self.asks.iter().next()?;
511 let total_q = bid_q + ask_q;
512 if total_q.is_zero() {
513 return None;
514 }
515 Some((*bid_p * *ask_q + *ask_p * *bid_q) / total_q)
516 }
517
518 pub fn price_levels_between(&self, side: Side, lo: Price, hi: Price) -> Vec<PriceLevel> {
522 let lo_val = lo.value();
523 let hi_val = hi.value();
524 match side {
525 Side::Bid => self
526 .bids
527 .range(lo_val..=hi_val)
528 .map(|(p, q)| PriceLevel {
529 price: Price::new(*p).unwrap_or(lo),
530 quantity: crate::types::Quantity::new(*q).unwrap_or_else(|_| crate::types::Quantity::zero()),
531 })
532 .collect(),
533 Side::Ask => self
534 .asks
535 .range(lo_val..=hi_val)
536 .map(|(p, q)| PriceLevel {
537 price: Price::new(*p).unwrap_or(lo),
538 quantity: crate::types::Quantity::new(*q).unwrap_or_else(|_| crate::types::Quantity::zero()),
539 })
540 .collect(),
541 }
542 }
543
544 pub fn tick_size(&self) -> Option<Decimal> {
549 let bid_tick = self
550 .bids
551 .keys()
552 .collect::<Vec<_>>()
553 .windows(2)
554 .map(|w| (*w[1] - *w[0]).abs())
555 .filter(|d| !d.is_zero())
556 .reduce(Decimal::min);
557 let ask_tick = self
558 .asks
559 .keys()
560 .collect::<Vec<_>>()
561 .windows(2)
562 .map(|w| (*w[1] - *w[0]).abs())
563 .filter(|d| !d.is_zero())
564 .reduce(Decimal::min);
565 match (bid_tick, ask_tick) {
566 (Some(b), Some(a)) => Some(b.min(a)),
567 (Some(b), None) => Some(b),
568 (None, Some(a)) => Some(a),
569 (None, None) => None,
570 }
571 }
572
573 pub fn bid_ask_ratio(&self) -> Option<Decimal> {
578 let bid = self.total_bid_volume();
579 let ask = self.total_ask_volume();
580 if ask.is_zero() || bid.is_zero() {
581 return None;
582 }
583 Some(bid / ask)
584 }
585
586 pub fn price_impact(&self, side: crate::types::Side, qty: crate::types::Quantity) -> Option<Decimal> {
592 use crate::types::Side;
593 if qty.is_zero() {
594 return None;
595 }
596 let levels: Vec<_> = match side {
597 Side::Bid => {
598 let mut asks: Vec<_> = self.asks.iter().collect();
600 asks.sort_by(|a, b| a.0.cmp(b.0));
601 asks.into_iter().map(|(p, q)| (*p, *q)).collect()
602 }
603 Side::Ask => {
604 let mut bids: Vec<_> = self.bids.iter().collect();
606 bids.sort_by(|a, b| b.0.cmp(a.0));
607 bids.into_iter().map(|(p, q)| (*p, *q)).collect()
608 }
609 };
610 let target = qty.value();
611 let mut remaining = target;
612 let mut notional = Decimal::ZERO;
613 for (price, level_qty) in levels {
614 let fill = level_qty.min(remaining);
615 notional += price * fill;
616 remaining -= fill;
617 if remaining <= Decimal::ZERO {
618 break;
619 }
620 }
621 if remaining > Decimal::ZERO {
622 None } else {
624 Some(notional / target)
625 }
626 }
627
628 pub fn bid_depth(&self, n: usize) -> Vec<PriceLevel> {
632 self.bids
633 .iter()
634 .rev()
635 .take(n)
636 .map(|(price, qty)| PriceLevel {
637 price: Price::new(*price).unwrap(),
638 quantity: Quantity::new(*qty).unwrap(),
639 })
640 .collect()
641 }
642
643 pub fn ask_depth(&self, n: usize) -> Vec<PriceLevel> {
647 self.asks
648 .iter()
649 .take(n)
650 .map(|(price, qty)| PriceLevel {
651 price: Price::new(*price).unwrap(),
652 quantity: Quantity::new(*qty).unwrap(),
653 })
654 .collect()
655 }
656
657 pub fn depth_imbalance(&self) -> Option<Decimal> {
664 let bid_qty: Decimal = self.bids.values().sum();
665 let ask_qty: Decimal = self.asks.values().sum();
666 let total = bid_qty + ask_qty;
667 if total.is_zero() {
668 return None;
669 }
670 Some((bid_qty - ask_qty) / total)
671 }
672
673 pub fn ask_bid_ratio(&self) -> Option<Decimal> {
678 let bid_qty: Decimal = self.bids.values().sum();
679 let ask_qty: Decimal = self.asks.values().sum();
680 if bid_qty.is_zero() {
681 return None;
682 }
683 Some(ask_qty / bid_qty)
684 }
685
686 pub fn total_bid_depth(&self) -> Decimal {
688 self.bids.values().sum()
689 }
690
691 pub fn total_ask_depth(&self) -> Decimal {
693 self.asks.values().sum()
694 }
695
696 pub fn price_at_volume(&self, side: Side, target_qty: Decimal) -> Option<Price> {
705 if target_qty.is_zero() {
706 return None;
707 }
708 let mut remaining = target_qty;
709 let mut last_price: Option<Price> = None;
710
711 match side {
712 Side::Ask => {
713 for (&px, &qty) in &self.asks {
714 last_price = Price::new(px).ok();
715 if qty >= remaining {
716 return last_price;
717 }
718 remaining -= qty;
719 }
720 }
721 Side::Bid => {
722 for (&px, &qty) in self.bids.iter().rev() {
723 last_price = Price::new(px).ok();
724 if qty >= remaining {
725 return last_price;
726 }
727 remaining -= qty;
728 }
729 }
730 }
731 last_price
732 }
733
734 pub fn top_n_bid_levels(&self, n: usize) -> Vec<PriceLevel> {
738 if n == 0 {
739 return vec![];
740 }
741 self.bids
742 .iter()
743 .rev()
744 .take(n)
745 .filter_map(|(&px, &qty)| {
746 let price = Price::new(px).ok()?;
747 let quantity = Quantity::new(qty).ok()?;
748 Some(PriceLevel { price, quantity })
749 })
750 .collect()
751 }
752
753 pub fn top_n_ask_levels(&self, n: usize) -> Vec<PriceLevel> {
757 if n == 0 {
758 return vec![];
759 }
760 self.asks
761 .iter()
762 .take(n)
763 .filter_map(|(&px, &qty)| {
764 let price = Price::new(px).ok()?;
765 let quantity = Quantity::new(qty).ok()?;
766 Some(PriceLevel { price, quantity })
767 })
768 .collect()
769 }
770
771 pub fn cumulative_bid_qty(&self, n: usize) -> Decimal {
776 if n == 0 {
777 return Decimal::ZERO;
778 }
779 self.bids.iter().rev().take(n).map(|(_, &qty)| qty).sum()
780 }
781
782 pub fn bid_depth_skew(&self, n: usize) -> Option<Decimal> {
788 if n == 0 {
789 return None;
790 }
791 let bid_qty = self.cumulative_bid_qty(n);
792 let ask_qty = self.cumulative_ask_qty(n);
793 let total = bid_qty + ask_qty;
794 if total.is_zero() {
795 return None;
796 }
797 bid_qty.checked_div(total)
798 }
799
800 pub fn spread_bps(&self) -> Option<Decimal> {
805 let bid = self.best_bid()?.price.value();
806 let ask = self.best_ask()?.price.value();
807 let mid = (bid + ask) / Decimal::TWO;
808 if mid.is_zero() {
809 return None;
810 }
811 let spread = ask - bid;
812 spread.checked_div(mid).map(|r| r * Decimal::from(10_000u32))
813 }
814
815 pub fn cumulative_ask_qty(&self, n: usize) -> Decimal {
820 if n == 0 {
821 return Decimal::ZERO;
822 }
823 self.asks.iter().take(n).map(|(_, &qty)| qty).sum()
824 }
825}
826
827#[cfg(test)]
828mod tests {
829 use super::*;
830 use rust_decimal_macros::dec;
831
832 fn make_book() -> OrderBook {
833 OrderBook::new(Symbol::new("AAPL").unwrap())
834 }
835
836 fn set_delta(side: Side, price: &str, qty: &str, seq: u64) -> BookDelta {
837 BookDelta {
838 side,
839 price: Price::new(price.parse().unwrap()).unwrap(),
840 quantity: Quantity::new(qty.parse().unwrap()).unwrap(),
841 action: DeltaAction::Set,
842 sequence: seq,
843 }
844 }
845
846 fn remove_delta(side: Side, price: &str, seq: u64) -> BookDelta {
847 BookDelta {
848 side,
849 price: Price::new(price.parse().unwrap()).unwrap(),
850 quantity: Quantity::zero(),
851 action: DeltaAction::Remove,
852 sequence: seq,
853 }
854 }
855
856 #[test]
857 fn test_orderbook_apply_delta_updates_bid() {
858 let mut book = make_book();
859 book.apply_delta(set_delta(Side::Bid, "100", "10", 1))
860 .unwrap();
861 let best = book.best_bid().unwrap();
862 assert_eq!(best.price.value(), dec!(100));
863 assert_eq!(best.quantity.value(), dec!(10));
864 }
865
866 #[test]
867 fn test_orderbook_apply_delta_updates_ask() {
868 let mut book = make_book();
869 book.apply_delta(set_delta(Side::Ask, "101", "5", 1))
870 .unwrap();
871 let best = book.best_ask().unwrap();
872 assert_eq!(best.price.value(), dec!(101));
873 assert_eq!(best.quantity.value(), dec!(5));
874 }
875
876 #[test]
877 fn test_orderbook_sequence_mismatch_returns_error() {
878 let mut book = make_book();
879 let result = book.apply_delta(set_delta(Side::Bid, "100", "10", 2));
880 assert!(matches!(
881 result,
882 Err(FinError::SequenceMismatch {
883 expected: 1,
884 got: 2
885 })
886 ));
887 }
888
889 #[test]
890 fn test_orderbook_sequence_advances_correctly() {
891 let mut book = make_book();
892 book.apply_delta(set_delta(Side::Bid, "100", "10", 1))
893 .unwrap();
894 assert_eq!(book.sequence(), 1);
895 book.apply_delta(set_delta(Side::Ask, "101", "5", 2))
896 .unwrap();
897 assert_eq!(book.sequence(), 2);
898 }
899
900 #[test]
901 fn test_orderbook_best_bid_max_price() {
902 let mut book = make_book();
903 book.apply_delta(set_delta(Side::Bid, "99", "10", 1))
904 .unwrap();
905 book.apply_delta(set_delta(Side::Bid, "100", "5", 2))
906 .unwrap();
907 book.apply_delta(set_delta(Side::Bid, "98", "20", 3))
908 .unwrap();
909 let best = book.best_bid().unwrap();
910 assert_eq!(best.price.value(), dec!(100));
911 }
912
913 #[test]
914 fn test_orderbook_best_ask_min_price() {
915 let mut book = make_book();
916 book.apply_delta(set_delta(Side::Ask, "102", "10", 1))
917 .unwrap();
918 book.apply_delta(set_delta(Side::Ask, "101", "5", 2))
919 .unwrap();
920 book.apply_delta(set_delta(Side::Ask, "103", "20", 3))
921 .unwrap();
922 let best = book.best_ask().unwrap();
923 assert_eq!(best.price.value(), dec!(101));
924 }
925
926 #[test]
927 fn test_orderbook_spread_positive() {
928 let mut book = make_book();
929 book.apply_delta(set_delta(Side::Bid, "100", "10", 1))
930 .unwrap();
931 book.apply_delta(set_delta(Side::Ask, "101", "5", 2))
932 .unwrap();
933 let spread = book.spread().unwrap();
934 assert_eq!(spread, dec!(1));
935 assert!(spread > Decimal::ZERO);
936 }
937
938 #[test]
939 fn test_orderbook_mid_price() {
940 let mut book = make_book();
941 book.apply_delta(set_delta(Side::Bid, "100", "10", 1))
942 .unwrap();
943 book.apply_delta(set_delta(Side::Ask, "102", "5", 2))
944 .unwrap();
945 let mid = book.mid_price().unwrap();
946 assert_eq!(mid, dec!(101));
947 }
948
949 #[test]
950 fn test_orderbook_spread_none_when_empty() {
951 let book = make_book();
952 assert!(book.spread().is_none());
953 }
954
955 #[test]
956 fn test_orderbook_vwap_insufficient_liquidity() {
957 let mut book = make_book();
958 book.apply_delta(set_delta(Side::Ask, "101", "5", 1))
959 .unwrap();
960 let result = book.vwap_for_qty(Side::Ask, Quantity::new(dec!(100)).unwrap());
961 assert!(matches!(result, Err(FinError::InsufficientLiquidity(_))));
962 }
963
964 #[test]
965 fn test_orderbook_vwap_single_level() {
966 let mut book = make_book();
967 book.apply_delta(set_delta(Side::Ask, "100", "10", 1))
968 .unwrap();
969 let vwap = book
970 .vwap_for_qty(Side::Ask, Quantity::new(dec!(5)).unwrap())
971 .unwrap();
972 assert_eq!(vwap, dec!(100));
973 }
974
975 #[test]
976 fn test_orderbook_vwap_multi_level() {
977 let mut book = make_book();
978 book.apply_delta(set_delta(Side::Ask, "100", "5", 1))
979 .unwrap();
980 book.apply_delta(set_delta(Side::Ask, "101", "5", 2))
981 .unwrap();
982 let vwap = book
984 .vwap_for_qty(Side::Ask, Quantity::new(dec!(10)).unwrap())
985 .unwrap();
986 assert_eq!(vwap, dec!(100.5));
987 }
988
989 #[test]
990 fn test_orderbook_remove_level_delta() {
991 let mut book = make_book();
992 book.apply_delta(set_delta(Side::Bid, "100", "10", 1))
993 .unwrap();
994 book.apply_delta(remove_delta(Side::Bid, "100", 2)).unwrap();
995 assert!(book.best_bid().is_none());
996 }
997
998 #[test]
999 fn test_orderbook_top_bids_order() {
1000 let mut book = make_book();
1001 book.apply_delta(set_delta(Side::Bid, "98", "10", 1))
1002 .unwrap();
1003 book.apply_delta(set_delta(Side::Bid, "100", "5", 2))
1004 .unwrap();
1005 book.apply_delta(set_delta(Side::Bid, "99", "20", 3))
1006 .unwrap();
1007 let top = book.top_bids(2);
1008 assert_eq!(top[0].price.value(), dec!(100));
1009 assert_eq!(top[1].price.value(), dec!(99));
1010 }
1011
1012 #[test]
1013 fn test_orderbook_top_asks_order() {
1014 let mut book = make_book();
1015 book.apply_delta(set_delta(Side::Ask, "103", "10", 1))
1016 .unwrap();
1017 book.apply_delta(set_delta(Side::Ask, "101", "5", 2))
1018 .unwrap();
1019 book.apply_delta(set_delta(Side::Ask, "102", "20", 3))
1020 .unwrap();
1021 let top = book.top_asks(2);
1022 assert_eq!(top[0].price.value(), dec!(101));
1023 assert_eq!(top[1].price.value(), dec!(102));
1024 }
1025
1026 #[test]
1027 fn test_orderbook_bid_count_ask_count() {
1028 let mut book = make_book();
1029 book.apply_delta(set_delta(Side::Bid, "100", "1", 1))
1030 .unwrap();
1031 book.apply_delta(set_delta(Side::Ask, "101", "1", 2))
1032 .unwrap();
1033 assert_eq!(book.bid_count(), 1);
1034 assert_eq!(book.ask_count(), 1);
1035 }
1036
1037 #[test]
1038 fn test_orderbook_vwap_zero_qty_returns_zero() {
1039 let mut book = make_book();
1040 book.apply_delta(set_delta(Side::Ask, "100", "10", 1))
1041 .unwrap();
1042 let vwap = book.vwap_for_qty(Side::Ask, Quantity::zero()).unwrap();
1043 assert_eq!(vwap, Decimal::ZERO);
1044 }
1045
1046 #[test]
1049 fn test_apply_delta_rejects_inverted_spread() {
1050 let mut book = make_book();
1051 book.apply_delta(set_delta(Side::Ask, "100", "5", 1))
1053 .unwrap();
1054 let result = book.apply_delta(set_delta(Side::Bid, "101", "5", 2));
1056 assert!(
1057 matches!(result, Err(FinError::InvertedSpread { .. })),
1058 "expected InvertedSpread, got {:?}",
1059 result
1060 );
1061 }
1062
1063 #[test]
1064 fn test_apply_delta_inverted_spread_rolls_back_sequence() {
1065 let mut book = make_book();
1066 book.apply_delta(set_delta(Side::Ask, "100", "5", 1))
1067 .unwrap();
1068 assert_eq!(book.sequence(), 1);
1069 let _ = book.apply_delta(set_delta(Side::Bid, "101", "5", 2));
1071 assert_eq!(
1072 book.sequence(),
1073 1,
1074 "sequence must not advance on rejected delta"
1075 );
1076 }
1077
1078 #[test]
1079 fn test_apply_delta_inverted_spread_rolled_back_book_state() {
1080 let mut book = make_book();
1081 book.apply_delta(set_delta(Side::Ask, "100", "5", 1))
1082 .unwrap();
1083 let _ = book.apply_delta(set_delta(Side::Bid, "101", "5", 2));
1085 assert!(
1086 book.best_bid().is_none(),
1087 "rejected bid must not appear in book"
1088 );
1089 }
1090
1091 #[test]
1093 fn test_empty_book_mid_price_returns_none() {
1094 let book = make_book();
1095 assert!(
1096 book.mid_price().is_none(),
1097 "empty book mid_price must be None"
1098 );
1099 }
1100
1101 #[test]
1103 fn test_empty_book_best_bid_returns_none() {
1104 let book = make_book();
1105 assert!(book.best_bid().is_none());
1106 }
1107
1108 #[test]
1110 fn test_empty_book_best_ask_returns_none() {
1111 let book = make_book();
1112 assert!(book.best_ask().is_none());
1113 }
1114
1115 #[test]
1117 fn test_best_bid_after_many_inserts_and_removes() {
1118 let mut book = make_book();
1119 book.apply_delta(set_delta(Side::Bid, "100", "10", 1))
1120 .unwrap();
1121 book.apply_delta(set_delta(Side::Bid, "105", "5", 2))
1122 .unwrap();
1123 book.apply_delta(set_delta(Side::Bid, "103", "8", 3))
1124 .unwrap();
1125 book.apply_delta(remove_delta(Side::Bid, "105", 4)).unwrap();
1127 let best = book.best_bid().unwrap();
1128 assert_eq!(
1129 best.price.value(),
1130 dec!(103),
1131 "best bid after removing top level must be 103"
1132 );
1133 }
1134
1135 #[test]
1136 fn test_best_ask_after_many_inserts_and_removes() {
1137 let mut book = make_book();
1138 book.apply_delta(set_delta(Side::Ask, "110", "10", 1))
1139 .unwrap();
1140 book.apply_delta(set_delta(Side::Ask, "108", "5", 2))
1141 .unwrap();
1142 book.apply_delta(set_delta(Side::Ask, "109", "8", 3))
1143 .unwrap();
1144 book.apply_delta(remove_delta(Side::Ask, "108", 4)).unwrap();
1146 let best = book.best_ask().unwrap();
1147 assert_eq!(
1148 best.price.value(),
1149 dec!(109),
1150 "best ask after removing top level must be 109"
1151 );
1152 }
1153
1154 #[test]
1156 fn test_crossed_book_ask_at_bid_price_rejected() {
1157 let mut book = make_book();
1158 book.apply_delta(set_delta(Side::Bid, "100", "10", 1))
1159 .unwrap();
1160 let result = book.apply_delta(set_delta(Side::Ask, "100", "5", 2));
1161 assert!(
1162 matches!(result, Err(FinError::InvertedSpread { .. })),
1163 "ask at bid price must produce InvertedSpread"
1164 );
1165 }
1166
1167 #[test]
1169 fn test_empty_book_spread_returns_none() {
1170 let book = make_book();
1171 assert!(book.spread().is_none());
1172 }
1173
1174 #[test]
1175 fn test_orderbook_snapshot_returns_top_n_both_sides() {
1176 let mut book = make_book();
1177 book.apply_delta(set_delta(Side::Bid, "99", "10", 1)).unwrap();
1178 book.apply_delta(set_delta(Side::Bid, "100", "5", 2)).unwrap();
1179 book.apply_delta(set_delta(Side::Ask, "101", "3", 3)).unwrap();
1180 book.apply_delta(set_delta(Side::Ask, "102", "7", 4)).unwrap();
1181 let (bids, asks) = book.snapshot(2);
1182 assert_eq!(bids.len(), 2);
1183 assert_eq!(asks.len(), 2);
1184 assert_eq!(bids[0].price.value(), dec!(100));
1185 assert_eq!(asks[0].price.value(), dec!(101));
1186 }
1187
1188 #[test]
1189 fn test_orderbook_snapshot_empty_book() {
1190 let book = make_book();
1191 let (bids, asks) = book.snapshot(5);
1192 assert!(bids.is_empty());
1193 assert!(asks.is_empty());
1194 }
1195
1196 #[test]
1197 fn test_orderbook_clear_removes_all_levels() {
1198 let mut book = make_book();
1199 book.apply_delta(set_delta(Side::Bid, "99", "10", 1)).unwrap();
1200 book.apply_delta(set_delta(Side::Ask, "101", "5", 2)).unwrap();
1201 assert_eq!(book.bid_count(), 1);
1202 assert_eq!(book.ask_count(), 1);
1203 book.clear();
1204 assert_eq!(book.bid_count(), 0);
1205 assert_eq!(book.ask_count(), 0);
1206 assert_eq!(book.sequence(), 0);
1207 }
1208
1209 #[test]
1210 fn test_orderbook_clear_allows_fresh_deltas() {
1211 let mut book = make_book();
1212 book.apply_delta(set_delta(Side::Bid, "100", "5", 1)).unwrap();
1213 book.clear();
1214 assert!(book.apply_delta(set_delta(Side::Bid, "100", "5", 1)).is_ok());
1216 }
1217
1218 #[test]
1219 fn test_orderbook_total_bid_volume() {
1220 let mut book = make_book();
1221 book.apply_delta(set_delta(Side::Bid, "100", "5", 1)).unwrap();
1222 book.apply_delta(set_delta(Side::Bid, "99", "3", 2)).unwrap();
1223 assert_eq!(book.total_bid_volume(), dec!(8));
1224 }
1225
1226 #[test]
1227 fn test_orderbook_total_ask_volume() {
1228 let mut book = make_book();
1229 book.apply_delta(set_delta(Side::Ask, "101", "4", 1)).unwrap();
1230 book.apply_delta(set_delta(Side::Ask, "102", "6", 2)).unwrap();
1231 assert_eq!(book.total_ask_volume(), dec!(10));
1232 }
1233
1234 #[test]
1235 fn test_orderbook_total_bid_volume_empty() {
1236 let book = make_book();
1237 assert_eq!(book.total_bid_volume(), dec!(0));
1238 }
1239
1240 #[test]
1241 fn test_orderbook_imbalance_balanced() {
1242 let mut book = make_book();
1243 book.apply_delta(set_delta(Side::Bid, "100", "5", 1)).unwrap();
1244 book.apply_delta(set_delta(Side::Ask, "101", "5", 2)).unwrap();
1245 assert_eq!(book.imbalance().unwrap(), dec!(0));
1246 }
1247
1248 #[test]
1249 fn test_orderbook_imbalance_bid_heavy() {
1250 let mut book = make_book();
1251 book.apply_delta(set_delta(Side::Bid, "100", "9", 1)).unwrap();
1252 book.apply_delta(set_delta(Side::Ask, "101", "1", 2)).unwrap();
1253 assert_eq!(book.imbalance().unwrap(), dec!(0.8));
1255 }
1256
1257 #[test]
1258 fn test_orderbook_imbalance_ask_heavy() {
1259 let mut book = make_book();
1260 book.apply_delta(set_delta(Side::Bid, "100", "1", 1)).unwrap();
1261 book.apply_delta(set_delta(Side::Ask, "101", "9", 2)).unwrap();
1262 assert_eq!(book.imbalance().unwrap(), dec!(-0.8));
1264 }
1265
1266 #[test]
1267 fn test_orderbook_imbalance_empty_returns_none() {
1268 let book = make_book();
1269 assert!(book.imbalance().is_none());
1270 }
1271
1272 #[test]
1273 fn test_orderbook_has_price_bid_present() {
1274 let mut book = make_book();
1275 book.apply_delta(set_delta(Side::Bid, "100", "5", 1)).unwrap();
1276 let price = Price::new(dec!(100)).unwrap();
1277 assert!(book.has_price(Side::Bid, price));
1278 assert!(!book.has_price(Side::Ask, price));
1279 }
1280
1281 #[test]
1282 fn test_orderbook_has_price_ask_present() {
1283 let mut book = make_book();
1284 book.apply_delta(set_delta(Side::Ask, "101", "3", 1)).unwrap();
1285 let price = Price::new(dec!(101)).unwrap();
1286 assert!(book.has_price(Side::Ask, price));
1287 assert!(!book.has_price(Side::Bid, price));
1288 }
1289
1290 #[test]
1291 fn test_orderbook_has_price_absent() {
1292 let book = make_book();
1293 let price = Price::new(dec!(100)).unwrap();
1294 assert!(!book.has_price(Side::Bid, price));
1295 assert!(!book.has_price(Side::Ask, price));
1296 }
1297
1298 #[test]
1299 fn test_orderbook_has_price_false_after_remove() {
1300 let mut book = make_book();
1301 book.apply_delta(set_delta(Side::Bid, "100", "5", 1)).unwrap();
1302 book.apply_delta(BookDelta {
1303 side: Side::Bid,
1304 price: Price::new(dec!(100)).unwrap(),
1305 quantity: Quantity::zero(),
1306 action: DeltaAction::Remove,
1307 sequence: 2,
1308 })
1309 .unwrap();
1310 let price = Price::new(dec!(100)).unwrap();
1311 assert!(!book.has_price(Side::Bid, price));
1312 }
1313
1314 #[test]
1315 fn test_orderbook_level_count_bids() {
1316 let mut book = make_book();
1317 book.apply_delta(set_delta(Side::Bid, "100", "10", 1)).unwrap();
1318 book.apply_delta(set_delta(Side::Bid, "99", "5", 2)).unwrap();
1319 assert_eq!(book.level_count(Side::Bid), 2);
1320 assert_eq!(book.level_count(Side::Ask), 0);
1321 }
1322
1323 #[test]
1324 fn test_orderbook_level_count_asks() {
1325 let mut book = make_book();
1326 book.apply_delta(set_delta(Side::Ask, "101", "3", 1)).unwrap();
1327 assert_eq!(book.level_count(Side::Ask), 1);
1328 assert_eq!(book.level_count(Side::Bid), 0);
1329 }
1330
1331 #[test]
1332 fn test_orderbook_weighted_mid_equal_qty() {
1333 let mut book = make_book();
1334 book.apply_delta(set_delta(Side::Bid, "100", "5", 1)).unwrap();
1335 book.apply_delta(set_delta(Side::Ask, "102", "5", 2)).unwrap();
1336 assert_eq!(book.weighted_mid().unwrap(), dec!(101));
1338 }
1339
1340 #[test]
1341 fn test_orderbook_weighted_mid_bid_heavy() {
1342 let mut book = make_book();
1343 book.apply_delta(set_delta(Side::Bid, "100", "9", 1)).unwrap();
1344 book.apply_delta(set_delta(Side::Ask, "110", "1", 2)).unwrap();
1345 assert_eq!(book.weighted_mid().unwrap(), dec!(109));
1347 }
1348
1349 #[test]
1350 fn test_orderbook_weighted_mid_empty_returns_none() {
1351 let book = make_book();
1352 assert!(book.weighted_mid().is_none());
1353 }
1354
1355 #[test]
1356 fn test_orderbook_bid_ask_ratio_equal_volumes() {
1357 let mut book = make_book();
1358 book.apply_delta(set_delta(Side::Bid, "100", "10", 1)).unwrap();
1359 book.apply_delta(set_delta(Side::Ask, "101", "10", 2)).unwrap();
1360 assert_eq!(book.bid_ask_ratio().unwrap(), dec!(1));
1361 }
1362
1363 #[test]
1364 fn test_orderbook_bid_ask_ratio_bid_heavy() {
1365 let mut book = make_book();
1366 book.apply_delta(set_delta(Side::Bid, "100", "20", 1)).unwrap();
1367 book.apply_delta(set_delta(Side::Ask, "101", "10", 2)).unwrap();
1368 assert_eq!(book.bid_ask_ratio().unwrap(), dec!(2));
1369 }
1370
1371 #[test]
1372 fn test_orderbook_bid_ask_ratio_empty_returns_none() {
1373 let book = make_book();
1374 assert!(book.bid_ask_ratio().is_none());
1375 }
1376
1377 #[test]
1378 fn test_orderbook_price_impact_buy_single_level() {
1379 let mut book = make_book();
1380 book.apply_delta(set_delta(Side::Ask, "101", "10", 1)).unwrap();
1381 let qty = Quantity::new(dec!(5)).unwrap();
1382 let avg = book.price_impact(Side::Bid, qty).unwrap();
1383 assert_eq!(avg, dec!(101));
1384 }
1385
1386 #[test]
1387 fn test_orderbook_price_impact_buy_spans_two_levels() {
1388 let mut book = make_book();
1389 book.apply_delta(set_delta(Side::Ask, "100", "5", 1)).unwrap();
1390 book.apply_delta(set_delta(Side::Ask, "102", "5", 2)).unwrap();
1391 let qty = Quantity::new(dec!(10)).unwrap();
1393 let avg = book.price_impact(Side::Bid, qty).unwrap();
1394 assert_eq!(avg, dec!(101));
1395 }
1396
1397 #[test]
1398 fn test_orderbook_price_impact_insufficient_depth_returns_none() {
1399 let mut book = make_book();
1400 book.apply_delta(set_delta(Side::Ask, "101", "3", 1)).unwrap();
1401 let qty = Quantity::new(dec!(10)).unwrap();
1402 assert!(book.price_impact(Side::Bid, qty).is_none());
1403 }
1404
1405 #[test]
1406 fn test_orderbook_price_impact_zero_qty_returns_none() {
1407 let mut book = make_book();
1408 book.apply_delta(set_delta(Side::Ask, "101", "10", 1)).unwrap();
1409 let qty = Quantity::zero();
1410 assert!(book.price_impact(Side::Bid, qty).is_none());
1411 }
1412
1413 #[test]
1414 fn test_orderbook_depth_at_existing_bid_level() {
1415 let mut book = make_book();
1416 book.apply_delta(set_delta(Side::Bid, "99", "5", 1)).unwrap();
1418 let price = Price::new(dec!(99)).unwrap();
1419 assert_eq!(book.depth_at(Side::Bid, price), Some(dec!(5)));
1420 }
1421
1422 #[test]
1423 fn test_orderbook_depth_at_absent_level_returns_none() {
1424 let book = make_book();
1425 let price = Price::new(dec!(50)).unwrap();
1426 assert!(book.depth_at(Side::Bid, price).is_none());
1427 assert!(book.depth_at(Side::Ask, price).is_none());
1428 }
1429
1430 #[test]
1431 fn test_orderbook_bid_depth_returns_top_n_descending() {
1432 let mut book = make_book();
1433 book.apply_delta(set_delta(Side::Bid, "100", "10", 1)).unwrap();
1434 book.apply_delta(set_delta(Side::Bid, "99", "5", 2)).unwrap();
1435 book.apply_delta(set_delta(Side::Bid, "98", "3", 3)).unwrap();
1436 let levels = book.bid_depth(2);
1437 assert_eq!(levels.len(), 2);
1438 assert_eq!(levels[0].price.value(), dec!(100)); assert_eq!(levels[1].price.value(), dec!(99));
1440 }
1441
1442 #[test]
1443 fn test_orderbook_ask_depth_returns_top_n_ascending() {
1444 let mut book = make_book();
1445 book.apply_delta(set_delta(Side::Ask, "101", "10", 1)).unwrap();
1446 book.apply_delta(set_delta(Side::Ask, "102", "5", 2)).unwrap();
1447 book.apply_delta(set_delta(Side::Ask, "103", "3", 3)).unwrap();
1448 let levels = book.ask_depth(2);
1449 assert_eq!(levels.len(), 2);
1450 assert_eq!(levels[0].price.value(), dec!(101)); assert_eq!(levels[1].price.value(), dec!(102));
1452 }
1453
1454 #[test]
1455 fn test_orderbook_bid_depth_fewer_than_n() {
1456 let mut book = make_book();
1457 book.apply_delta(set_delta(Side::Bid, "100", "10", 1)).unwrap();
1458 let levels = book.bid_depth(5);
1459 assert_eq!(levels.len(), 1);
1460 }
1461
1462 #[test]
1463 fn test_orderbook_ask_depth_empty_book() {
1464 let book = make_book();
1465 assert!(book.ask_depth(3).is_empty());
1466 }
1467
1468 #[test]
1469 fn test_orderbook_remove_all_bids_clears_bid_side() {
1470 let mut book = make_book();
1471 book.apply_delta(set_delta(Side::Bid, "100", "10", 1)).unwrap();
1472 book.apply_delta(set_delta(Side::Bid, "99", "5", 2)).unwrap();
1473 book.remove_all(Side::Bid);
1474 assert!(book.best_bid().is_none());
1475 }
1476
1477 #[test]
1478 fn test_orderbook_remove_all_bids_leaves_asks_intact() {
1479 let mut book = make_book();
1480 book.apply_delta(set_delta(Side::Bid, "100", "10", 1)).unwrap();
1481 book.apply_delta(set_delta(Side::Ask, "101", "5", 2)).unwrap();
1482 book.remove_all(Side::Bid);
1483 assert!(book.best_bid().is_none());
1484 assert!(book.best_ask().is_some());
1485 }
1486
1487 #[test]
1488 fn test_orderbook_remove_all_asks_clears_ask_side() {
1489 let mut book = make_book();
1490 book.apply_delta(set_delta(Side::Ask, "101", "5", 1)).unwrap();
1491 book.apply_delta(set_delta(Side::Ask, "102", "3", 2)).unwrap();
1492 book.remove_all(Side::Ask);
1493 assert!(book.best_ask().is_none());
1494 }
1495
1496 #[test]
1497 fn test_orderbook_total_levels_sums_both_sides() {
1498 let mut book = make_book();
1499 book.apply_delta(set_delta(Side::Bid, "100", "10", 1)).unwrap();
1500 book.apply_delta(set_delta(Side::Bid, "99", "5", 2)).unwrap();
1501 book.apply_delta(set_delta(Side::Ask, "101", "8", 3)).unwrap();
1502 assert_eq!(book.total_levels(), 3);
1503 }
1504
1505 #[test]
1506 fn test_orderbook_total_levels_empty_book() {
1507 let book = make_book();
1508 assert_eq!(book.total_levels(), 0);
1509 }
1510}