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