barter_data/books/
mod.rs

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
9/// Provides a [`OrderBookL2Manager`](manager::OrderBookL2Manager) for maintaining a set of local
10/// L2 [`OrderBook`]s.
11pub mod manager;
12
13/// Provides an abstract collection of cheaply cloneable shared-state [`OrderBook`].
14pub mod map;
15
16/// Normalised Barter [`OrderBook`] snapshot.
17#[derive(Clone, PartialEq, Eq, Debug, Default, Deserialize, Serialize)]
18pub struct OrderBook {
19    sequence: u64,
20    time_engine: Option<DateTime<Utc>>,
21    bids: OrderBookSide<Bids>,
22    asks: OrderBookSide<Asks>,
23}
24
25impl OrderBook {
26    /// Construct a new sorted [`OrderBook`].
27    ///
28    /// Note that the passed bid and asks levels do not need to be pre-sorted.
29    pub fn new<IterBids, IterAsks, L>(
30        sequence: u64,
31        time_engine: Option<DateTime<Utc>>,
32        bids: IterBids,
33        asks: IterAsks,
34    ) -> Self
35    where
36        IterBids: IntoIterator<Item = L>,
37        IterAsks: IntoIterator<Item = L>,
38        L: Into<Level>,
39    {
40        Self {
41            sequence,
42            time_engine,
43            bids: OrderBookSide::bids(bids),
44            asks: OrderBookSide::asks(asks),
45        }
46    }
47
48    /// Current `u64` sequence number associated with the [`OrderBook`].
49    pub fn sequence(&self) -> u64 {
50        self.sequence
51    }
52
53    /// Current engine time associated with the [`OrderBook`].
54    pub fn time_engine(&self) -> Option<DateTime<Utc>> {
55        self.time_engine
56    }
57
58    /// Generate a sorted [`OrderBook`] snapshot with a maximum depth.
59    pub fn snapshot(&self, depth: usize) -> Self {
60        Self {
61            sequence: self.sequence,
62            time_engine: self.time_engine,
63            bids: OrderBookSide::bids(self.bids.levels.iter().take(depth).copied()),
64            asks: OrderBookSide::asks(self.asks.levels.iter().take(depth).copied()),
65        }
66    }
67
68    /// Update the local [`OrderBook`] from a new [`OrderBookEvent`].
69    pub fn update(&mut self, event: &OrderBookEvent) {
70        match event {
71            OrderBookEvent::Snapshot(snapshot) => {
72                *self = snapshot.clone();
73            }
74            OrderBookEvent::Update(update) => {
75                self.sequence = update.sequence;
76                self.time_engine = update.time_engine;
77                self.upsert_bids(&update.bids);
78                self.upsert_asks(&update.asks);
79            }
80        }
81    }
82
83    /// Update the local [`OrderBook`] by upserting the levels in an [`OrderBookSide`].
84    fn upsert_bids(&mut self, update: &OrderBookSide<Bids>) {
85        self.bids.upsert(&update.levels)
86    }
87
88    /// Update the local [`OrderBook`] by upserting the levels in an [`OrderBookSide`].
89    fn upsert_asks(&mut self, update: &OrderBookSide<Asks>) {
90        self.asks.upsert(&update.levels)
91    }
92
93    /// Return a reference to this [`OrderBook`]s bids.
94    pub fn bids(&self) -> &OrderBookSide<Bids> {
95        &self.bids
96    }
97
98    /// Return a reference to this [`OrderBook`]s asks.
99    pub fn asks(&self) -> &OrderBookSide<Asks> {
100        &self.asks
101    }
102
103    /// Calculate the mid-price by taking the average of the best bid and ask prices.
104    ///
105    /// See Docs: <https://www.quantstart.com/articles/high-frequency-trading-ii-limit-order-book>
106    pub fn mid_price(&self) -> Option<Decimal> {
107        match (self.bids.best(), self.asks.best()) {
108            (Some(best_bid), Some(best_ask)) => Some(mid_price(best_bid.price, best_ask.price)),
109            (Some(best_bid), None) => Some(best_bid.price),
110            (None, Some(best_ask)) => Some(best_ask.price),
111            (None, None) => None,
112        }
113    }
114
115    /// Calculate the volume weighted mid-price (micro-price), weighing the best bid and ask prices
116    /// with their associated amount.
117    ///
118    /// See Docs: <https://www.quantstart.com/articles/high-frequency-trading-ii-limit-order-book>
119    pub fn volume_weighed_mid_price(&self) -> Option<Decimal> {
120        match (self.bids.best(), self.asks.best()) {
121            (Some(best_bid), Some(best_ask)) => {
122                Some(volume_weighted_mid_price(*best_bid, *best_ask))
123            }
124            (Some(best_bid), None) => Some(best_bid.price),
125            (None, Some(best_ask)) => Some(best_ask.price),
126            (None, None) => None,
127        }
128    }
129}
130
131/// Normalised Barter [`Level`]s for one `Side` of the [`OrderBook`].
132#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
133pub struct OrderBookSide<Side> {
134    #[serde(skip_serializing)]
135    pub side: Side,
136    levels: Vec<Level>,
137}
138
139/// Unit type to tag an [`OrderBookSide`] as the bid Side (ie/ buyers) of an [`OrderBook`].
140#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Display)]
141pub struct Bids;
142
143/// Unit type to tag an [`OrderBookSide`] as the ask Side (ie/ sellers) of an [`OrderBook`].
144#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Display)]
145pub struct Asks;
146
147impl OrderBookSide<Bids> {
148    /// Construct a new [`OrderBookSide<Bids>`] from the provided [`Level`]s.
149    pub fn bids<Iter, L>(levels: Iter) -> Self
150    where
151        Iter: IntoIterator<Item = L>,
152        L: Into<Level>,
153    {
154        let mut levels = levels.into_iter().map(L::into).collect::<Vec<_>>();
155        levels.sort_unstable_by(|a, b| a.price.cmp(&b.price).reverse());
156
157        Self { side: Bids, levels }
158    }
159
160    /// Upsert bid [`Level`]s into this [`OrderBookSide<Bids>`].
161    pub fn upsert<L>(&mut self, levels: &[L])
162    where
163        L: Into<Level> + Copy,
164    {
165        levels.iter().for_each(|upsert| {
166            let upsert: Level = (*upsert).into();
167            self.upsert_single(upsert, |existing| {
168                existing.price.cmp(&upsert.price).reverse()
169            })
170        })
171    }
172}
173
174impl OrderBookSide<Asks> {
175    /// Construct a new [`OrderBookSide<Asks>`] from the provided [`Level`]s.
176    pub fn asks<Iter, L>(levels: Iter) -> Self
177    where
178        Iter: IntoIterator<Item = L>,
179        L: Into<Level>,
180    {
181        let mut levels = levels.into_iter().map(L::into).collect::<Vec<_>>();
182        levels.sort_unstable_by(|a, b| a.price.cmp(&b.price));
183
184        Self { side: Asks, levels }
185    }
186
187    /// Upsert ask [`Level`]s into this [`OrderBookSide<Asks>`].
188    pub fn upsert<L>(&mut self, levels: &[L])
189    where
190        L: Into<Level> + Copy,
191    {
192        levels.into_iter().for_each(|upsert| {
193            let upsert = (*upsert).into();
194            self.upsert_single(upsert, |existing| existing.price.cmp(&upsert.price))
195        })
196    }
197}
198
199impl<Side> OrderBookSide<Side>
200where
201    Side: std::fmt::Display + std::fmt::Debug,
202{
203    /// Get best [`Level`] on the [`OrderBookSide`].
204    pub fn best(&self) -> Option<&Level> {
205        self.levels.first()
206    }
207
208    /// Return a reference to the [`OrderBookSide`] levels.
209    pub fn levels(&self) -> &[Level] {
210        &self.levels
211    }
212
213    /// Upsert a single [`Level`] into this [`OrderBookSide`].
214    ///
215    /// ### Upsert Scenarios
216    /// #### 1 Level Already Exists
217    /// 1a) New value is 0, remove the level
218    /// 1b) New value is > 0, replace the level
219    ///
220    /// #### 2 Level Does Not Exist
221    /// 2a) New value is 0, log warn and continue
222    /// 2b) New value is > 0, insert new level
223    pub fn upsert_single<FnOrd>(&mut self, new_level: Level, fn_ord: FnOrd)
224    where
225        FnOrd: Fn(&Level) -> Ordering,
226    {
227        match (self.levels.binary_search_by(fn_ord), new_level.amount) {
228            (Ok(index), new_amount) => {
229                if new_amount.is_zero() {
230                    // Scenario 1a: Level exists & new value is 0 => remove level
231                    let _removed = self.levels.remove(index);
232                } else {
233                    // Scenario 1b: Level exists & new value is > 0 => replace level
234                    self.levels[index].amount = new_amount;
235                }
236            }
237            (Err(index), new_amount) => {
238                if new_amount.is_zero() {
239                    // Scenario 2a: Level does not exist & new value is 0 => log & continue
240                    debug!(
241                        ?new_level,
242                        side = %self.side,
243                        "received upsert Level with zero amount (to remove) that was not found"
244                    );
245                } else {
246                    // Scenario 2b: Level does not exist & new value > 0 => insert new level
247                    self.levels.insert(index, new_level);
248                }
249            }
250        }
251    }
252}
253
254impl Default for OrderBookSide<Bids> {
255    fn default() -> Self {
256        Self {
257            side: Bids,
258            levels: vec![],
259        }
260    }
261}
262
263impl Default for OrderBookSide<Asks> {
264    fn default() -> Self {
265        Self {
266            side: Asks,
267            levels: vec![],
268        }
269    }
270}
271
272/// Normalised Barter OrderBook [`Level`].
273#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Hash, Default, Deserialize, Serialize)]
274pub struct Level {
275    pub price: Decimal,
276    pub amount: Decimal,
277}
278
279impl<T> From<(T, T)> for Level
280where
281    T: Into<Decimal>,
282{
283    fn from((price, amount): (T, T)) -> Self {
284        Self::new(price, amount)
285    }
286}
287
288impl Eq for Level {}
289
290impl Level {
291    pub fn new<T>(price: T, amount: T) -> Self
292    where
293        T: Into<Decimal>,
294    {
295        Self {
296            price: price.into(),
297            amount: amount.into(),
298        }
299    }
300}
301
302/// Calculate the mid-price by taking the average of the best bid and ask prices.
303///
304/// See Docs: <https://www.quantstart.com/articles/high-frequency-trading-ii-limit-order-book>
305pub fn mid_price(best_bid_price: Decimal, best_ask_price: Decimal) -> Decimal {
306    (best_bid_price + best_ask_price) / Decimal::TWO
307}
308
309/// Calculate the volume weighted mid-price (micro-price), weighing the best bid and ask prices
310/// with their associated amount.
311///
312/// See Docs: <https://www.quantstart.com/articles/high-frequency-trading-ii-limit-order-book>
313pub fn volume_weighted_mid_price(best_bid: Level, best_ask: Level) -> Decimal {
314    ((best_bid.price * best_ask.amount) + (best_ask.price * best_bid.amount))
315        / (best_bid.amount + best_ask.amount)
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321
322    mod order_book_l1 {
323        use super::*;
324        use crate::subscription::book::OrderBookL1;
325        use rust_decimal_macros::dec;
326
327        #[test]
328        fn test_mid_price() {
329            struct TestCase {
330                input: OrderBookL1,
331                expected: Option<Decimal>,
332            }
333
334            let tests = vec![
335                TestCase {
336                    // TC0
337                    input: OrderBookL1 {
338                        last_update_time: Default::default(),
339                        best_bid: Some(Level::new(100, 999999)),
340                        best_ask: Some(Level::new(200, 1)),
341                    },
342                    expected: Some(dec!(150.0)),
343                },
344                TestCase {
345                    // TC1
346                    input: OrderBookL1 {
347                        last_update_time: Default::default(),
348                        best_bid: Some(Level::new(50, 1)),
349                        best_ask: Some(Level::new(250, 999999)),
350                    },
351                    expected: Some(dec!(150.0)),
352                },
353                TestCase {
354                    // TC2
355                    input: OrderBookL1 {
356                        last_update_time: Default::default(),
357                        best_bid: Some(Level::new(10, 999999)),
358                        best_ask: Some(Level::new(250, 999999)),
359                    },
360                    expected: Some(dec!(130.0)),
361                },
362                TestCase {
363                    // TC3
364                    input: OrderBookL1 {
365                        last_update_time: Default::default(),
366                        best_bid: Some(Level::new(10, 999999)),
367                        best_ask: None,
368                    },
369                    expected: None,
370                },
371                TestCase {
372                    // TC4
373                    input: OrderBookL1 {
374                        last_update_time: Default::default(),
375                        best_bid: None,
376                        best_ask: Some(Level::new(250, 999999)),
377                    },
378                    expected: None,
379                },
380            ];
381
382            for (index, test) in tests.into_iter().enumerate() {
383                assert_eq!(test.input.mid_price(), test.expected, "TC{index} failed")
384            }
385        }
386
387        #[test]
388        fn test_volume_weighted_mid_price() {
389            struct TestCase {
390                input: OrderBookL1,
391                expected: Option<Decimal>,
392            }
393
394            let tests = vec![
395                TestCase {
396                    // TC0: volume the same so should be equal to non-weighted mid price
397                    input: OrderBookL1 {
398                        last_update_time: Default::default(),
399                        best_bid: Some(Level::new(100, 100)),
400                        best_ask: Some(Level::new(200, 100)),
401                    },
402                    expected: Some(dec!(150.0)),
403                },
404                TestCase {
405                    // TC1: volume affects mid-price
406                    input: OrderBookL1 {
407                        last_update_time: Default::default(),
408                        best_bid: Some(Level::new(100, 600)),
409                        best_ask: Some(Level::new(200, 1000)),
410                    },
411                    expected: Some(dec!(137.5)),
412                },
413                TestCase {
414                    // TC2: volume the same and price the same
415                    input: OrderBookL1 {
416                        last_update_time: Default::default(),
417                        best_bid: Some(Level::new(1000, 999999)),
418                        best_ask: Some(Level::new(1000, 999999)),
419                    },
420                    expected: Some(dec!(1000.0)),
421                },
422                TestCase {
423                    // TC3: best ask is None
424                    input: OrderBookL1 {
425                        last_update_time: Default::default(),
426                        best_bid: Some(Level::new(1000, 999999)),
427                        best_ask: None,
428                    },
429                    expected: None,
430                },
431                TestCase {
432                    // TC4: best bid is None
433                    input: OrderBookL1 {
434                        last_update_time: Default::default(),
435                        best_bid: None,
436                        best_ask: Some(Level::new(1000, 999999)),
437                    },
438                    expected: None,
439                },
440            ];
441
442            for (index, test) in tests.into_iter().enumerate() {
443                assert_eq!(
444                    test.input.volume_weighed_mid_price(),
445                    test.expected,
446                    "TC{index} failed"
447                )
448            }
449        }
450    }
451
452    mod order_book {
453        use super::*;
454        use rust_decimal_macros::dec;
455
456        #[test]
457        fn test_mid_price() {
458            struct TestCase {
459                input: OrderBook,
460                expected: Option<Decimal>,
461            }
462
463            let tests = vec![
464                TestCase {
465                    // TC0: no levels so 0.0 mid-price
466                    input: OrderBook::new::<Vec<_>, Vec<_>, Level>(
467                        0,
468                        Default::default(),
469                        vec![],
470                        vec![],
471                    ),
472                    expected: None,
473                },
474                TestCase {
475                    // TC1: no asks in the books so take best bid price
476                    input: OrderBook::new(
477                        0,
478                        Default::default(),
479                        vec![
480                            Level::new(dec!(100.0), dec!(100.0)),
481                            Level::new(dec!(50.0), dec!(100.0)),
482                        ],
483                        vec![],
484                    ),
485                    expected: Some(dec!(100.0)),
486                },
487                TestCase {
488                    // TC2: no bids in the books so take ask price
489                    input: OrderBook::new(
490                        0,
491                        Default::default(),
492                        vec![],
493                        vec![
494                            Level::new(dec!(50.0), dec!(100.0)),
495                            Level::new(dec!(100.0), dec!(100.0)),
496                        ],
497                    ),
498                    expected: Some(dec!(50.0)),
499                },
500                TestCase {
501                    // TC3: best bid and ask amount is the same, so regular mid-price
502                    input: OrderBook::new(
503                        0,
504                        Default::default(),
505                        vec![
506                            Level::new(dec!(100.0), dec!(100.0)),
507                            Level::new(dec!(50.0), dec!(100.0)),
508                        ],
509                        vec![
510                            Level::new(dec!(200.0), dec!(100.0)),
511                            Level::new(dec!(300.0), dec!(100.0)),
512                        ],
513                    ),
514                    expected: Some(dec!(150.0)),
515                },
516            ];
517
518            for (index, test) in tests.into_iter().enumerate() {
519                assert_eq!(test.input.mid_price(), test.expected, "TC{index} failed")
520            }
521        }
522
523        #[test]
524        fn test_volume_weighted_mid_price() {
525            struct TestCase {
526                input: OrderBook,
527                expected: Option<Decimal>,
528            }
529
530            let tests = vec![
531                TestCase {
532                    // TC0: no levels so 0.0 mid-price
533                    input: OrderBook::new::<Vec<_>, Vec<_>, Level>(
534                        0,
535                        Default::default(),
536                        vec![],
537                        vec![],
538                    ),
539                    expected: None,
540                },
541                TestCase {
542                    // TC1: no asks in the books so take best bid price
543                    input: OrderBook::new(
544                        0,
545                        Default::default(),
546                        vec![
547                            Level::new(dec!(100.0), dec!(100.0)),
548                            Level::new(dec!(50.0), dec!(100.0)),
549                        ],
550                        vec![],
551                    ),
552                    expected: Some(dec!(100.0)),
553                },
554                TestCase {
555                    // TC2: no bids in the books so take ask price
556                    input: OrderBook::new(
557                        0,
558                        Default::default(),
559                        vec![],
560                        vec![
561                            Level::new(dec!(50.0), dec!(100.0)),
562                            Level::new(dec!(100.0), dec!(100.0)),
563                        ],
564                    ),
565                    expected: Some(dec!(50.0)),
566                },
567                TestCase {
568                    // TC3: best bid and ask amount is the same, so regular mid-price
569                    input: OrderBook::new(
570                        0,
571                        Default::default(),
572                        vec![
573                            Level::new(dec!(100.0), dec!(100.0)),
574                            Level::new(dec!(50.0), dec!(100.0)),
575                        ],
576                        vec![
577                            Level::new(dec!(200.0), dec!(100.0)),
578                            Level::new(dec!(300.0), dec!(100.0)),
579                        ],
580                    ),
581                    expected: Some(dec!(150.0)),
582                },
583                TestCase {
584                    // TC4: valid volume weighted mid-price
585                    input: OrderBook::new(
586                        0,
587                        Default::default(),
588                        vec![
589                            Level::new(dec!(100.0), dec!(3000.0)),
590                            Level::new(dec!(50.0), dec!(100.0)),
591                        ],
592                        vec![
593                            Level::new(dec!(200.0), dec!(1000.0)),
594                            Level::new(dec!(300.0), dec!(100.0)),
595                        ],
596                    ),
597                    expected: Some(dec!(175.0)),
598                },
599            ];
600
601            for (index, test) in tests.into_iter().enumerate() {
602                assert_eq!(
603                    test.input.volume_weighed_mid_price(),
604                    test.expected,
605                    "TC{index} failed"
606                )
607            }
608        }
609    }
610
611    mod order_book_side {
612        use super::*;
613        use rust_decimal_macros::dec;
614
615        #[test]
616        fn test_upsert_single() {
617            struct TestCase {
618                book_side: OrderBookSide<Bids>,
619                new_level: Level,
620                expected: OrderBookSide<Bids>,
621            }
622
623            let tests = vec![
624                TestCase {
625                    // TC0: Level exists & new value is 0 => remove Level
626                    book_side: OrderBookSide::bids(vec![
627                        Level::new(dec!(80), dec!(1)),
628                        Level::new(dec!(90), dec!(1)),
629                        Level::new(dec!(100), dec!(1)),
630                    ]),
631                    new_level: Level::new(dec!(100), dec!(0)),
632                    expected: OrderBookSide::bids(vec![
633                        Level::new(dec!(80), dec!(1)),
634                        Level::new(dec!(90), dec!(1)),
635                    ]),
636                },
637                TestCase {
638                    // TC1: Level exists & new value is > 0 => replace Level
639                    book_side: OrderBookSide::bids(vec![
640                        Level::new(dec!(80), dec!(1)),
641                        Level::new(dec!(90), dec!(1)),
642                        Level::new(dec!(100), dec!(1)),
643                    ]),
644                    new_level: Level::new(dec!(100), dec!(10)),
645                    expected: OrderBookSide::bids(vec![
646                        Level::new(dec!(80), dec!(1)),
647                        Level::new(dec!(90), dec!(1)),
648                        Level::new(dec!(100), dec!(10)),
649                    ]),
650                },
651                TestCase {
652                    // TC2: Level does not exist & new value > 0 => insert new Level
653                    book_side: OrderBookSide::bids(vec![
654                        Level::new(dec!(80), dec!(1)),
655                        Level::new(dec!(90), dec!(1)),
656                        Level::new(dec!(100), dec!(1)),
657                    ]),
658                    new_level: Level::new(dec!(110), dec!(1)),
659                    expected: OrderBookSide::bids(vec![
660                        Level::new(dec!(80), dec!(1)),
661                        Level::new(dec!(90), dec!(1)),
662                        Level::new(dec!(100), dec!(1)),
663                        Level::new(dec!(110), dec!(1)),
664                    ]),
665                },
666                TestCase {
667                    // TC3: Level does not exist & new value is 0 => no change
668                    book_side: OrderBookSide::bids(vec![
669                        Level::new(dec!(80), dec!(1)),
670                        Level::new(dec!(90), dec!(1)),
671                        Level::new(dec!(100), dec!(1)),
672                    ]),
673                    new_level: Level::new(dec!(110), dec!(0)),
674                    expected: OrderBookSide::bids(vec![
675                        Level::new(dec!(80), dec!(1)),
676                        Level::new(dec!(90), dec!(1)),
677                        Level::new(dec!(100), dec!(1)),
678                    ]),
679                },
680            ];
681
682            for (index, mut test) in tests.into_iter().enumerate() {
683                test.book_side.upsert_single(test.new_level, |existing| {
684                    existing.price.cmp(&test.new_level.price).reverse()
685                });
686                assert_eq!(test.book_side, test.expected, "TC{} failed", index);
687            }
688        }
689    }
690}