Skip to main content

managed_order_book/
lib.rs

1use prettytable::{row, table, Cell, Row, Table};
2use quoter::Quoter;
3use rust_decimal::{prelude::Zero, Decimal};
4use serde::Deserialize;
5use std::collections::BTreeMap;
6
7pub mod managed;
8pub mod quoter;
9
10pub trait OrderBook {
11    /// Returns the price decimal points
12    fn price_decimal_points(&self) -> u32;
13    /// Returns the quantity decimal points
14    fn quantity_decimal_points(&self) -> u32;
15    /// Returns the price precision
16    fn tick_size(&self) -> Decimal;
17    /// Returns the quantity precision
18    fn step_size(&self) -> Decimal;
19    //
20    fn bids(&self) -> &BTreeMap<Decimal, Decimal>;
21    fn asks(&self) -> &BTreeMap<Decimal, Decimal>;
22    fn bids_mut(&mut self) -> &mut BTreeMap<Decimal, Decimal>;
23    fn asks_mut(&mut self) -> &mut BTreeMap<Decimal, Decimal>;
24    /// Returns (price, quantity)
25    fn best_bid(&self) -> (&Decimal, &Decimal) {
26        let bids = self.bids();
27        bids.last_key_value().unwrap()
28    }
29    /// Returns (price, quantity)
30    fn best_ask(&self) -> (&Decimal, &Decimal) {
31        let asks = self.asks();
32        asks.first_key_value().unwrap()
33    }
34
35    fn update_book(book: &mut BTreeMap<Decimal, Decimal>, update: Vec<(Decimal, Decimal)>) {
36        for (price, quantity) in update {
37            if quantity.is_zero() {
38                book.remove(&price);
39            } else {
40                book.insert(price, quantity);
41            }
42        }
43    }
44
45    fn update_asks(&mut self, asks: Vec<(Decimal, Decimal)>) {
46        let mut book = self.asks_mut();
47        Self::update_book(&mut book, asks);
48    }
49
50    fn update_bids(&mut self, bids: Vec<(Decimal, Decimal)>) {
51        let mut book = self.bids_mut();
52        Self::update_book(&mut book, bids);
53    }
54}
55
56#[derive(Debug)]
57pub struct BinanceOrderBook {
58    symbol: String,
59    last_update_id: u128,
60    processed_events_count: u128,
61    bids: BTreeMap<Decimal, Decimal>,
62    asks: BTreeMap<Decimal, Decimal>,
63}
64
65impl BinanceOrderBook {
66    pub fn new(symbol: String, depth: Depth) -> Self {
67        let Depth {
68            bids,
69            asks,
70            last_update_id,
71            ..
72        } = depth;
73        let bids = BTreeMap::from_iter(bids.into_iter());
74        let asks = BTreeMap::from_iter(asks.into_iter());
75        Self {
76            symbol,
77            bids,
78            asks,
79            last_update_id,
80            processed_events_count: 0,
81        }
82    }
83
84    pub fn update(&mut self, depth_update: DepthUpdate) -> anyhow::Result<()> {
85        let DepthUpdate {
86            event_type,
87            event_time,
88            transaction_time,
89            symbol,
90            first_update_id,
91            final_update_id,
92            last_stream_final_update_id,
93            bids,
94            asks,
95        } = depth_update;
96
97        if self.processed_events_count == 0 {
98            if self.last_update_id < first_update_id || final_update_id < self.last_update_id {
99                return Err(anyhow::anyhow!("First event out of sync"));
100            }
101        } else {
102            if self.last_update_id != last_stream_final_update_id {
103                return Err(anyhow::anyhow!("Event out of sync"));
104            }
105        }
106
107        self.update_asks(asks);
108        self.update_bids(bids);
109
110        self.last_update_id = final_update_id;
111        self.processed_events_count += 1;
112
113        Ok(())
114    }
115
116    pub fn print(&self, levels: usize) {
117        let mut bid_table = Table::new();
118
119        bid_table.add_row(row!["Size", "Price"]);
120
121        for (price, quantity) in self.bids().iter().rev().take(levels) {
122            bid_table.add_row(Row::new(vec![
123                Cell::new(&quantity.to_string()),
124                Cell::new(&price.to_string()),
125            ]));
126        }
127
128        let mut ask_table = Table::new();
129
130        ask_table.add_row(row!["Price", "Size"]);
131
132        for (price, quantity) in self.asks().iter().take(levels) {
133            ask_table.add_row(Row::new(vec![
134                Cell::new(&price.to_string()),
135                Cell::new(&quantity.to_string()),
136            ]));
137        }
138
139        let table = table!(["Bids", "Asks"], [bid_table, ask_table]);
140
141        table.printstd();
142    }
143}
144
145impl OrderBook for BinanceOrderBook {
146    fn price_decimal_points(&self) -> u32 {
147        todo!()
148    }
149
150    fn quantity_decimal_points(&self) -> u32 {
151        todo!()
152    }
153
154    fn tick_size(&self) -> Decimal {
155        todo!()
156    }
157
158    fn step_size(&self) -> Decimal {
159        todo!()
160    }
161
162    fn bids(&self) -> &BTreeMap<Decimal, Decimal> {
163        &self.bids
164    }
165
166    fn asks(&self) -> &BTreeMap<Decimal, Decimal> {
167        &self.asks
168    }
169
170    fn bids_mut(&mut self) -> &mut BTreeMap<Decimal, Decimal> {
171        &mut self.bids
172    }
173
174    fn asks_mut(&mut self) -> &mut BTreeMap<Decimal, Decimal> {
175        &mut self.asks
176    }
177}
178
179impl Quoter for BinanceOrderBook {
180    fn buy(&self, size: &Decimal) -> Decimal {
181        let book = self.asks().iter();
182        <BinanceOrderBook as Quoter>::quote(size, book)
183    }
184
185    fn sell(&self, size: &Decimal) -> Decimal {
186        let book = self.bids().iter().rev();
187        <BinanceOrderBook as Quoter>::quote(size, book)
188    }
189}
190
191#[derive(Debug, Deserialize, Clone)]
192#[serde(rename_all = "camelCase")]
193pub struct Depth {
194    last_update_id: u128,
195    #[serde(rename = "E")]
196    event_time: u64,
197    #[serde(rename = "T")]
198    transaction_time: u64,
199    bids: Vec<(Decimal, Decimal)>,
200    asks: Vec<(Decimal, Decimal)>,
201}
202
203#[derive(Debug, Deserialize, Clone)]
204#[serde(rename_all = "camelCase")]
205pub struct DepthUpdate {
206    #[serde(rename = "e")]
207    event_type: String,
208    #[serde(rename = "E")]
209    event_time: u64,
210    #[serde(rename = "T")]
211    transaction_time: u64,
212    #[serde(rename = "s")]
213    symbol: String,
214    #[serde(rename = "U")]
215    first_update_id: u128,
216    #[serde(rename = "u")]
217    final_update_id: u128,
218    #[serde(rename = "pu")]
219    last_stream_final_update_id: u128,
220    #[serde(rename = "b")]
221    bids: Vec<(Decimal, Decimal)>,
222    #[serde(rename = "a")]
223    asks: Vec<(Decimal, Decimal)>,
224}
225
226#[cfg(test)]
227mod tests {
228
229    use super::*;
230
231    #[test]
232    fn it_works() {
233        let symbol = "RONINUSDT";
234        let data = r#"{"lastUpdateId":5573970560080,"E":1729534992908,"T":1729534992900,"bids":[["1.663900","31.0"],["1.663800","71.3"],["1.663700","30.6"],["1.663600","110.2"],["1.663500","76.7"]],"asks":[["1.664500","40.3"],["1.664700","10.0"],["1.664800","133.9"],["1.664900","214.9"],["1.665000","110.2"]]}"#;
235        let depth: Depth = serde_json::from_str(&data).unwrap();
236        let mut binance_order_book = BinanceOrderBook::new(symbol.to_string(), depth);
237        let best_bid = binance_order_book.best_bid();
238        let best_ask = binance_order_book.best_ask();
239        // dbg!(best_bid, best_ask);
240        // First update
241        let data = r#"{"e":"depthUpdate","E":1729534992913,"T":1729534992900,"s":"RONINUSDT","U":5573970557923,"u":5573970560080,"pu":5573970557478,"b":[["1.331200","45.0"],["1.658900","1013.1"]],"a":[["1.669200","2277.2"],["1.670000","783.4"],["1.670100","1136.5"],["1.670900","739.9"],["1.675100","1103.4"],["1.676600","120.6"],["1.677800","2083.7"]]}"#;
242        let depth_update: DepthUpdate = serde_json::from_str(&data).unwrap();
243        // dbg!(&depth_update);
244        let result = binance_order_book.update(depth_update);
245        dbg!(result);
246        // Second update
247        let data = r#"{"e":"depthUpdate","E":1729534993015,"T":1729534993006,"s":"RONINUSDT","U":5573970560466,"u":5573970562617,"pu":5573970560080,"b":[["1.331200","0.0"],["1.655500","372.0"],["1.660700","2010.4"],["1.660800","2022.0"],["1.660900","759.9"],["1.662000","698.6"],["1.662200","139.2"],["1.662900","448.4"],["1.663000","143.0"],["1.663700","40.6"],["1.664000","30.3"]],"a":[["1.665500","72.5"],["1.666000","47.3"],["1.667700","795.3"],["1.669200","2081.3"],["1.669300","203.8"],["1.669600","757.7"],["1.670000","298.1"],["1.670800","1890.1"]]}"#;
248        let depth_update: DepthUpdate = serde_json::from_str(&data).unwrap();
249        //  dbg!(&depth_update);
250        let result = binance_order_book.update(depth_update);
251        dbg!(result);
252        dbg!(binance_order_book);
253    }
254}