Skip to main content

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