1use crate::subscription::book::OrderBookEvent;
2use chrono::{DateTime, Utc};
3use derive_more::Display;
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use std::cmp::Ordering;
7use tracing::debug;
8
9pub mod manager;
12
13pub mod map;
15
16#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Deserialize, Serialize)]
26pub struct OrderBookTimes {
27 pub time_engine: Option<DateTime<Utc>>,
31 pub time_exchange: Option<DateTime<Utc>>,
35 pub time_received: DateTime<Utc>,
43}
44
45#[derive(Clone, PartialEq, Eq, Debug, Default, Deserialize, Serialize)]
53pub struct OrderBook {
54 sequence: u64,
55 times: OrderBookTimes,
56 bids: OrderBookSide<Bids>,
57 asks: OrderBookSide<Asks>,
58}
59
60impl OrderBook {
61 pub fn new<IterBids, IterAsks, L>(
65 sequence: u64,
66 times: OrderBookTimes,
67 bids: IterBids,
68 asks: IterAsks,
69 ) -> Self
70 where
71 IterBids: IntoIterator<Item = L>,
72 IterAsks: IntoIterator<Item = L>,
73 L: Into<Level>,
74 {
75 Self {
76 sequence,
77 times,
78 bids: OrderBookSide::bids(bids),
79 asks: OrderBookSide::asks(asks),
80 }
81 }
82
83 pub fn from_sides(
88 sequence: u64,
89 times: OrderBookTimes,
90 bids: OrderBookSide<Bids>,
91 asks: OrderBookSide<Asks>,
92 ) -> Self {
93 debug_assert!(
94 bids.levels().windows(2).all(|w| w[0].price >= w[1].price),
95 "bids must be sorted descending by price"
96 );
97 debug_assert!(
98 asks.levels().windows(2).all(|w| w[0].price <= w[1].price),
99 "asks must be sorted ascending by price"
100 );
101 Self {
102 sequence,
103 times,
104 bids,
105 asks,
106 }
107 }
108
109 pub fn sequence(&self) -> u64 {
111 self.sequence
112 }
113
114 pub fn time_engine(&self) -> Option<DateTime<Utc>> {
121 self.times.time_engine
122 }
123
124 pub fn time_exchange(&self) -> Option<DateTime<Utc>> {
136 self.times.time_exchange
137 }
138
139 pub fn time_received(&self) -> DateTime<Utc> {
150 self.times.time_received
151 }
152
153 pub fn times(&self) -> OrderBookTimes {
160 self.times
161 }
162
163 pub fn snapshot(&self, depth: usize) -> Self {
168 Self {
169 sequence: self.sequence,
170 times: self.times,
171 bids: OrderBookSide::bids(self.bids.levels.iter().take(depth).copied()),
172 asks: OrderBookSide::asks(self.asks.levels.iter().take(depth).copied()),
173 }
174 }
175
176 pub fn update(&mut self, event: &OrderBookEvent) {
182 match event {
183 OrderBookEvent::Snapshot(snapshot) => {
184 *self = snapshot.clone();
185 }
186 OrderBookEvent::Update(update) => {
187 self.sequence = update.sequence;
188 self.times = update.times;
189 self.upsert_bids(&update.bids);
190 self.upsert_asks(&update.asks);
191 }
192 }
193 }
194
195 fn upsert_bids(&mut self, update: &OrderBookSide<Bids>) {
197 self.bids.upsert(&update.levels)
198 }
199
200 fn upsert_asks(&mut self, update: &OrderBookSide<Asks>) {
202 self.asks.upsert(&update.levels)
203 }
204
205 pub fn bids(&self) -> &OrderBookSide<Bids> {
207 &self.bids
208 }
209
210 pub fn asks(&self) -> &OrderBookSide<Asks> {
212 &self.asks
213 }
214
215 pub fn mid_price(&self) -> Option<Decimal> {
219 match (self.bids.best(), self.asks.best()) {
220 (Some(best_bid), Some(best_ask)) => Some(mid_price(best_bid.price, best_ask.price)),
221 (Some(best_bid), None) => Some(best_bid.price),
222 (None, Some(best_ask)) => Some(best_ask.price),
223 (None, None) => None,
224 }
225 }
226
227 pub fn volume_weighed_mid_price(&self) -> Option<Decimal> {
232 match (self.bids.best(), self.asks.best()) {
233 (Some(best_bid), Some(best_ask)) => {
234 Some(volume_weighted_mid_price(*best_bid, *best_ask))
235 }
236 (Some(best_bid), None) => Some(best_bid.price),
237 (None, Some(best_ask)) => Some(best_ask.price),
238 (None, None) => None,
239 }
240 }
241}
242
243#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
245pub struct OrderBookSide<Side> {
246 #[serde(skip_serializing)]
247 pub side: Side,
248 levels: Vec<Level>,
249}
250
251#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Display)]
253pub struct Bids;
254
255#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Display)]
257pub struct Asks;
258
259impl OrderBookSide<Bids> {
260 pub fn bids<Iter, L>(levels: Iter) -> Self
262 where
263 Iter: IntoIterator<Item = L>,
264 L: Into<Level>,
265 {
266 let mut levels = levels.into_iter().map(L::into).collect::<Vec<_>>();
267 levels.sort_unstable_by(|a, b| a.price.cmp(&b.price).reverse());
268
269 Self { side: Bids, levels }
270 }
271
272 pub fn upsert<L>(&mut self, levels: &[L])
274 where
275 L: Into<Level> + Copy,
276 {
277 levels.iter().for_each(|upsert| {
278 let upsert: Level = (*upsert).into();
279 self.upsert_single(upsert, |existing| {
280 existing.price.cmp(&upsert.price).reverse()
281 })
282 })
283 }
284}
285
286impl OrderBookSide<Asks> {
287 pub fn asks<Iter, L>(levels: Iter) -> Self
289 where
290 Iter: IntoIterator<Item = L>,
291 L: Into<Level>,
292 {
293 let mut levels = levels.into_iter().map(L::into).collect::<Vec<_>>();
294 levels.sort_unstable_by_key(|a| a.price);
295
296 Self { side: Asks, levels }
297 }
298
299 pub fn upsert<L>(&mut self, levels: &[L])
301 where
302 L: Into<Level> + Copy,
303 {
304 levels.iter().for_each(|upsert| {
305 let upsert = (*upsert).into();
306 self.upsert_single(upsert, |existing| existing.price.cmp(&upsert.price))
307 })
308 }
309}
310
311impl<Side> OrderBookSide<Side>
312where
313 Side: std::fmt::Display + std::fmt::Debug,
314{
315 pub fn best(&self) -> Option<&Level> {
317 self.levels.first()
318 }
319
320 pub fn levels(&self) -> &[Level] {
322 &self.levels
323 }
324
325 pub fn upsert_single<FnOrd>(&mut self, new_level: Level, fn_ord: FnOrd)
336 where
337 FnOrd: Fn(&Level) -> Ordering,
338 {
339 match (self.levels.binary_search_by(fn_ord), new_level.amount) {
340 (Ok(index), new_amount) => {
341 if new_amount.is_zero() {
342 let _removed = self.levels.remove(index);
344 } else {
345 self.levels[index].amount = new_amount;
347 }
348 }
349 (Err(index), new_amount) => {
350 if new_amount.is_zero() {
351 debug!(
353 ?new_level,
354 side = %self.side,
355 "received upsert Level with zero amount (to remove) that was not found"
356 );
357 } else {
358 self.levels.insert(index, new_level);
360 }
361 }
362 }
363 }
364}
365
366impl Default for OrderBookSide<Bids> {
367 fn default() -> Self {
368 Self {
369 side: Bids,
370 levels: vec![],
371 }
372 }
373}
374
375impl Default for OrderBookSide<Asks> {
376 fn default() -> Self {
377 Self {
378 side: Asks,
379 levels: vec![],
380 }
381 }
382}
383
384#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Hash, Default, Deserialize, Serialize)]
386pub struct Level {
387 pub price: Decimal,
388 pub amount: Decimal,
389}
390
391impl<T> From<(T, T)> for Level
392where
393 T: Into<Decimal>,
394{
395 fn from((price, amount): (T, T)) -> Self {
396 Self::new(price, amount)
397 }
398}
399
400impl Eq for Level {}
401
402impl Level {
403 pub fn new<T>(price: T, amount: T) -> Self
404 where
405 T: Into<Decimal>,
406 {
407 Self {
408 price: price.into(),
409 amount: amount.into(),
410 }
411 }
412}
413
414pub fn mid_price(best_bid_price: Decimal, best_ask_price: Decimal) -> Decimal {
418 (best_bid_price + best_ask_price) / Decimal::TWO
419}
420
421pub fn volume_weighted_mid_price(best_bid: Level, best_ask: Level) -> Decimal {
426 ((best_bid.price * best_ask.amount) + (best_ask.price * best_bid.amount))
427 / (best_bid.amount + best_ask.amount)
428}
429
430#[cfg(test)]
431#[allow(clippy::unwrap_used)] mod tests {
433 use super::*;
434
435 mod order_book_l1 {
436 use super::*;
437 use crate::subscription::book::OrderBookL1;
438 use rust_decimal_macros::dec;
439
440 #[test]
441 fn test_mid_price() {
442 struct TestCase {
443 input: OrderBookL1,
444 expected: Option<Decimal>,
445 }
446
447 let tests = vec![
448 TestCase {
449 input: OrderBookL1 {
451 last_update_time: Default::default(),
452 best_bid: Some(Level::new(100, 999999)),
453 best_ask: Some(Level::new(200, 1)),
454 },
455 expected: Some(dec!(150.0)),
456 },
457 TestCase {
458 input: OrderBookL1 {
460 last_update_time: Default::default(),
461 best_bid: Some(Level::new(50, 1)),
462 best_ask: Some(Level::new(250, 999999)),
463 },
464 expected: Some(dec!(150.0)),
465 },
466 TestCase {
467 input: OrderBookL1 {
469 last_update_time: Default::default(),
470 best_bid: Some(Level::new(10, 999999)),
471 best_ask: Some(Level::new(250, 999999)),
472 },
473 expected: Some(dec!(130.0)),
474 },
475 TestCase {
476 input: OrderBookL1 {
478 last_update_time: Default::default(),
479 best_bid: Some(Level::new(10, 999999)),
480 best_ask: None,
481 },
482 expected: None,
483 },
484 TestCase {
485 input: OrderBookL1 {
487 last_update_time: Default::default(),
488 best_bid: None,
489 best_ask: Some(Level::new(250, 999999)),
490 },
491 expected: None,
492 },
493 ];
494
495 for (index, test) in tests.into_iter().enumerate() {
496 assert_eq!(test.input.mid_price(), test.expected, "TC{index} failed")
497 }
498 }
499
500 #[test]
501 fn test_volume_weighted_mid_price() {
502 struct TestCase {
503 input: OrderBookL1,
504 expected: Option<Decimal>,
505 }
506
507 let tests = vec![
508 TestCase {
509 input: OrderBookL1 {
511 last_update_time: Default::default(),
512 best_bid: Some(Level::new(100, 100)),
513 best_ask: Some(Level::new(200, 100)),
514 },
515 expected: Some(dec!(150.0)),
516 },
517 TestCase {
518 input: OrderBookL1 {
520 last_update_time: Default::default(),
521 best_bid: Some(Level::new(100, 600)),
522 best_ask: Some(Level::new(200, 1000)),
523 },
524 expected: Some(dec!(137.5)),
525 },
526 TestCase {
527 input: OrderBookL1 {
529 last_update_time: Default::default(),
530 best_bid: Some(Level::new(1000, 999999)),
531 best_ask: Some(Level::new(1000, 999999)),
532 },
533 expected: Some(dec!(1000.0)),
534 },
535 TestCase {
536 input: OrderBookL1 {
538 last_update_time: Default::default(),
539 best_bid: Some(Level::new(1000, 999999)),
540 best_ask: None,
541 },
542 expected: None,
543 },
544 TestCase {
545 input: OrderBookL1 {
547 last_update_time: Default::default(),
548 best_bid: None,
549 best_ask: Some(Level::new(1000, 999999)),
550 },
551 expected: None,
552 },
553 ];
554
555 for (index, test) in tests.into_iter().enumerate() {
556 assert_eq!(
557 test.input.volume_weighed_mid_price(),
558 test.expected,
559 "TC{index} failed"
560 )
561 }
562 }
563 }
564
565 mod order_book {
566 use super::*;
567 use rust_decimal_macros::dec;
568
569 fn times(received_ms: i64) -> OrderBookTimes {
570 OrderBookTimes {
571 time_engine: Some(DateTime::from_timestamp_millis(1).unwrap()),
572 time_exchange: Some(DateTime::from_timestamp_millis(2).unwrap()),
573 time_received: DateTime::from_timestamp_millis(received_ms).unwrap(),
574 }
575 }
576
577 #[test]
578 fn test_update_replaces_times_wholesale() {
579 let mut book = OrderBook::new(1, times(100), vec![Level::new(50, 1)], vec![]);
580
581 let newer = OrderBookTimes {
582 time_engine: None,
583 time_exchange: Some(DateTime::from_timestamp_millis(999).unwrap()),
584 time_received: DateTime::from_timestamp_millis(1000).unwrap(),
585 };
586 let update = OrderBook::new(2, newer, vec![Level::new(50, 1)], vec![]);
587 book.update(&OrderBookEvent::Update(update));
588
589 assert_eq!(book.time_engine(), newer.time_engine);
590 assert_eq!(book.time_exchange(), newer.time_exchange);
591 assert_eq!(book.time_received(), newer.time_received);
592 }
593
594 #[test]
595 fn test_update_snapshot_propagates_times_wholesale() {
596 let mut book = OrderBook::new(1, times(100), vec![Level::new(50, 1)], vec![]);
599
600 let newer = OrderBookTimes {
601 time_engine: None,
602 time_exchange: Some(DateTime::from_timestamp_millis(999).unwrap()),
603 time_received: DateTime::from_timestamp_millis(1000).unwrap(),
604 };
605 let snapshot = OrderBook::new(2, newer, vec![Level::new(60, 1)], vec![]);
606 book.update(&OrderBookEvent::Snapshot(snapshot));
607
608 assert_eq!(book.time_engine(), newer.time_engine);
609 assert_eq!(book.time_exchange(), newer.time_exchange);
610 assert_eq!(book.time_received(), newer.time_received);
611 }
612
613 #[test]
614 fn test_snapshot_copies_times() {
615 let book = OrderBook::new(1, times(100), vec![Level::new(50, 1)], vec![]);
616 let snap = book.snapshot(10);
617 assert_eq!(snap.time_engine(), book.time_engine());
618 assert_eq!(snap.time_exchange(), book.time_exchange());
619 assert_eq!(snap.time_received(), book.time_received());
620 }
621
622 #[test]
623 fn test_equality_reflects_timestamps() {
624 let a = OrderBook::new(1, times(100), vec![Level::new(50, 1)], vec![]);
626 let b = OrderBook::new(1, times(200), vec![Level::new(50, 1)], vec![]);
627 assert_ne!(a, b);
628 }
629
630 #[test]
631 fn test_mid_price() {
632 struct TestCase {
633 input: OrderBook,
634 expected: Option<Decimal>,
635 }
636
637 let tests = vec![
638 TestCase {
639 input: OrderBook::new::<Vec<_>, Vec<_>, Level>(
641 0,
642 Default::default(),
643 vec![],
644 vec![],
645 ),
646 expected: None,
647 },
648 TestCase {
649 input: OrderBook::new(
651 0,
652 Default::default(),
653 vec![
654 Level::new(dec!(100.0), dec!(100.0)),
655 Level::new(dec!(50.0), dec!(100.0)),
656 ],
657 vec![],
658 ),
659 expected: Some(dec!(100.0)),
660 },
661 TestCase {
662 input: OrderBook::new(
664 0,
665 Default::default(),
666 vec![],
667 vec![
668 Level::new(dec!(50.0), dec!(100.0)),
669 Level::new(dec!(100.0), dec!(100.0)),
670 ],
671 ),
672 expected: Some(dec!(50.0)),
673 },
674 TestCase {
675 input: OrderBook::new(
677 0,
678 Default::default(),
679 vec![
680 Level::new(dec!(100.0), dec!(100.0)),
681 Level::new(dec!(50.0), dec!(100.0)),
682 ],
683 vec![
684 Level::new(dec!(200.0), dec!(100.0)),
685 Level::new(dec!(300.0), dec!(100.0)),
686 ],
687 ),
688 expected: Some(dec!(150.0)),
689 },
690 ];
691
692 for (index, test) in tests.into_iter().enumerate() {
693 assert_eq!(test.input.mid_price(), test.expected, "TC{index} failed")
694 }
695 }
696
697 #[test]
698 fn test_volume_weighted_mid_price() {
699 struct TestCase {
700 input: OrderBook,
701 expected: Option<Decimal>,
702 }
703
704 let tests = vec![
705 TestCase {
706 input: OrderBook::new::<Vec<_>, Vec<_>, Level>(
708 0,
709 Default::default(),
710 vec![],
711 vec![],
712 ),
713 expected: None,
714 },
715 TestCase {
716 input: OrderBook::new(
718 0,
719 Default::default(),
720 vec![
721 Level::new(dec!(100.0), dec!(100.0)),
722 Level::new(dec!(50.0), dec!(100.0)),
723 ],
724 vec![],
725 ),
726 expected: Some(dec!(100.0)),
727 },
728 TestCase {
729 input: OrderBook::new(
731 0,
732 Default::default(),
733 vec![],
734 vec![
735 Level::new(dec!(50.0), dec!(100.0)),
736 Level::new(dec!(100.0), dec!(100.0)),
737 ],
738 ),
739 expected: Some(dec!(50.0)),
740 },
741 TestCase {
742 input: OrderBook::new(
744 0,
745 Default::default(),
746 vec![
747 Level::new(dec!(100.0), dec!(100.0)),
748 Level::new(dec!(50.0), dec!(100.0)),
749 ],
750 vec![
751 Level::new(dec!(200.0), dec!(100.0)),
752 Level::new(dec!(300.0), dec!(100.0)),
753 ],
754 ),
755 expected: Some(dec!(150.0)),
756 },
757 TestCase {
758 input: OrderBook::new(
760 0,
761 Default::default(),
762 vec![
763 Level::new(dec!(100.0), dec!(3000.0)),
764 Level::new(dec!(50.0), dec!(100.0)),
765 ],
766 vec![
767 Level::new(dec!(200.0), dec!(1000.0)),
768 Level::new(dec!(300.0), dec!(100.0)),
769 ],
770 ),
771 expected: Some(dec!(175.0)),
772 },
773 ];
774
775 for (index, test) in tests.into_iter().enumerate() {
776 assert_eq!(
777 test.input.volume_weighed_mid_price(),
778 test.expected,
779 "TC{index} failed"
780 )
781 }
782 }
783 }
784
785 mod order_book_side {
786 use super::*;
787 use rust_decimal_macros::dec;
788
789 #[test]
790 fn test_upsert_single() {
791 struct TestCase {
792 book_side: OrderBookSide<Bids>,
793 new_level: Level,
794 expected: OrderBookSide<Bids>,
795 }
796
797 let tests = vec![
798 TestCase {
799 book_side: OrderBookSide::bids(vec![
801 Level::new(dec!(80), dec!(1)),
802 Level::new(dec!(90), dec!(1)),
803 Level::new(dec!(100), dec!(1)),
804 ]),
805 new_level: Level::new(dec!(100), dec!(0)),
806 expected: OrderBookSide::bids(vec![
807 Level::new(dec!(80), dec!(1)),
808 Level::new(dec!(90), dec!(1)),
809 ]),
810 },
811 TestCase {
812 book_side: OrderBookSide::bids(vec![
814 Level::new(dec!(80), dec!(1)),
815 Level::new(dec!(90), dec!(1)),
816 Level::new(dec!(100), dec!(1)),
817 ]),
818 new_level: Level::new(dec!(100), dec!(10)),
819 expected: OrderBookSide::bids(vec![
820 Level::new(dec!(80), dec!(1)),
821 Level::new(dec!(90), dec!(1)),
822 Level::new(dec!(100), dec!(10)),
823 ]),
824 },
825 TestCase {
826 book_side: OrderBookSide::bids(vec![
828 Level::new(dec!(80), dec!(1)),
829 Level::new(dec!(90), dec!(1)),
830 Level::new(dec!(100), dec!(1)),
831 ]),
832 new_level: Level::new(dec!(110), dec!(1)),
833 expected: OrderBookSide::bids(vec![
834 Level::new(dec!(80), dec!(1)),
835 Level::new(dec!(90), dec!(1)),
836 Level::new(dec!(100), dec!(1)),
837 Level::new(dec!(110), dec!(1)),
838 ]),
839 },
840 TestCase {
841 book_side: OrderBookSide::bids(vec![
843 Level::new(dec!(80), dec!(1)),
844 Level::new(dec!(90), dec!(1)),
845 Level::new(dec!(100), dec!(1)),
846 ]),
847 new_level: Level::new(dec!(110), dec!(0)),
848 expected: OrderBookSide::bids(vec![
849 Level::new(dec!(80), dec!(1)),
850 Level::new(dec!(90), dec!(1)),
851 Level::new(dec!(100), dec!(1)),
852 ]),
853 },
854 ];
855
856 for (index, mut test) in tests.into_iter().enumerate() {
857 test.book_side.upsert_single(test.new_level, |existing| {
858 existing.price.cmp(&test.new_level.price).reverse()
859 });
860 assert_eq!(test.book_side, test.expected, "TC{} failed", index);
861 }
862 }
863 }
864}