rust_ob/
orderbook.rs

1use std::{cell::RefCell, collections::HashMap, fmt::Display, hash::Hash, rc::Rc};
2
3use rust_decimal::Decimal;
4
5use crate::{
6    bookside::{BookSide, MaxPricePriority, MinPricePriority},
7    errors,
8    order::{Order, Side},
9};
10
11#[derive(Debug)]
12pub struct OrderBook<OrderID> {
13    // every active order is in: order_index AND (buy_side XOR sell_side)
14    order_index: HashMap<OrderID, Rc<RefCell<Order<OrderID>>>>,
15
16    buy_side: BookSide<MaxPricePriority, OrderID>,
17    sell_side: BookSide<MinPricePriority, OrderID>,
18
19    // increments on each new order added to data structures. Used for order time priority.
20    priority: u64,
21}
22
23impl<OrderID> OrderBook<OrderID>
24where
25    OrderID: Copy + PartialEq + Eq + Hash,
26{
27    /// Create new initialized OrderBook
28    pub fn new() -> Self {
29        OrderBook {
30            order_index: HashMap::new(),
31
32            buy_side: BookSide::new(),
33            sell_side: BookSide::new(),
34
35            priority: u64::MIN,
36        }
37    }
38
39    /// Process new limit order
40    /// ```
41    /// use rust_ob::{
42    ///     OrderBook,
43    ///     Side,
44    ///     OrderMatch,
45    ///     errors,
46    /// };
47    /// use rust_decimal::Decimal;
48    ///
49    /// let mut ob = OrderBook::new();
50    ///
51    /// let res1 = ob.process_limit_order(1, Side::Sell, Decimal::from(4), Decimal::from(4)).unwrap();
52    /// assert_eq!(res1.len(), 0);
53    ///
54    /// let res2 = ob.process_limit_order(2, Side::Sell, Decimal::from(3), Decimal::from(2)).unwrap();
55    /// assert_eq!(res2.len(), 0);
56    ///
57    /// let res3 = ob.process_limit_order(3, Side::Buy, Decimal::from(8), Decimal::from(3)).unwrap();
58    /// assert_eq!(
59    ///     res3,
60    ///     vec![
61    ///         OrderMatch {
62    ///             order: 2,
63    ///             quantity: Decimal::from(2),
64    ///             cost: Decimal::from(-6)
65    ///         },
66    ///         OrderMatch {
67    ///             order: 1,
68    ///             quantity: Decimal::from(1),
69    ///             cost: Decimal::from(-4)
70    ///         },
71    ///         OrderMatch {
72    ///             order: 3,
73    ///             quantity: Decimal::from(3),
74    ///             cost: Decimal::from(10)
75    ///         }
76    ///     ]
77    /// );
78    ///
79    ///
80    /// // all costs sum to zero
81    /// assert_eq!(res3.iter().map(|val| val.cost).sum::<Decimal>(), Decimal::ZERO);
82    ///
83    /// // quantity on sell orders == quantity on buy orders
84    /// // last OrderMatch of Vec (if not empty) is always the order just placed
85    /// assert_eq!(res3.iter().take(res3.len()-1).map(|val| val.quantity).sum::<Decimal>(), res3.last().unwrap().quantity);
86    ///
87    /// // possible errors
88    /// assert_eq!(ob.process_limit_order(4, Side::Buy, Decimal::from(10), Decimal::from(0)).unwrap_err(), errors::ProcessLimitOrder::NonPositiveQuantity);
89    /// assert_eq!(ob.process_limit_order(1, Side::Buy, Decimal::from(10), Decimal::from(25)).unwrap_err(), errors::ProcessLimitOrder::OrderAlreadyExists);
90    /// ```
91    pub fn process_limit_order(
92        &mut self,
93        id: OrderID,
94        side: Side,
95        price: Decimal,
96        mut quantity: Decimal,
97    ) -> Result<Vec<OrderMatch<OrderID>>, errors::ProcessLimitOrder> {
98        // check to ensure order does not already exist
99        if self.order_index.contains_key(&id) {
100            return Err(errors::ProcessLimitOrder::OrderAlreadyExists);
101        }
102        // check to ensure positive quantity
103        if quantity <= Decimal::ZERO {
104            return Err(errors::ProcessLimitOrder::NonPositiveQuantity);
105        }
106
107        // vars
108        let mut order_match_vec = Vec::new();
109        let mut new_order_order_match = OrderMatch::new(id);
110
111        // main matching loop
112        while quantity > Decimal::ZERO {
113            // get highest priority order on opposite side
114            let Some(shared_highest_priority_order) = (match side {
115                Side::Buy => self.sell_side.get_highest_priority(),
116                Side::Sell => self.buy_side.get_highest_priority(),
117            }) else {
118                break;
119            };
120            let mut highest_priority_order = shared_highest_priority_order.borrow_mut();
121
122            // check if orders satisfy each other
123            let satisfied = match side {
124                Side::Buy => price >= highest_priority_order.price,
125                Side::Sell => price <= highest_priority_order.price,
126            };
127            if !satisfied {
128                break;
129            }
130
131            // create order match for highest_priority_order
132            let mut highest_priority_order_order_match = OrderMatch::new(highest_priority_order.id);
133
134            // find satisfied quantity and update vars
135            let satisfied_quantity = quantity.min(highest_priority_order.quantity);
136
137            quantity -= satisfied_quantity;
138            highest_priority_order.quantity -= satisfied_quantity;
139
140            new_order_order_match.quantity += satisfied_quantity;
141            highest_priority_order_order_match.quantity += satisfied_quantity;
142
143            // find cost and update vars
144            let buy_side_cost = highest_priority_order.price * satisfied_quantity;
145            match side {
146                Side::Buy => {
147                    new_order_order_match.cost += buy_side_cost;
148                    highest_priority_order_order_match.cost = -buy_side_cost
149                }
150                Side::Sell => {
151                    new_order_order_match.cost -= buy_side_cost;
152                    highest_priority_order_order_match.cost = buy_side_cost
153                }
154            }
155
156            // remove highest_priority_order from orderbook if completely satisfied
157            if highest_priority_order.quantity == Decimal::ZERO {
158                self.order_index.remove(&highest_priority_order.id);
159
160                drop(highest_priority_order);
161                match side {
162                    Side::Sell => self.buy_side.pop_highest_priority(),
163                    Side::Buy => self.sell_side.pop_highest_priority(),
164                }
165            }
166
167            // add to result vec
168            order_match_vec.push(highest_priority_order_order_match);
169        }
170
171        // add to result vec if not empty
172        if !new_order_order_match.quantity.is_zero() {
173            order_match_vec.push(new_order_order_match);
174        }
175
176        // add order to data structures if any remaining quantity
177        if !quantity.is_zero() {
178            let shared_order = Rc::new(RefCell::new(Order {
179                id,
180                side,
181                price,
182                quantity,
183                priority: self.get_next_priority(),
184            }));
185
186            self.order_index.insert(id, shared_order.clone());
187            match side {
188                Side::Buy => self.buy_side.add(shared_order),
189                Side::Sell => self.sell_side.add(shared_order),
190            }
191        }
192
193        Ok(order_match_vec)
194    }
195
196    /// Cancels order with id
197    /// ```
198    /// use rust_ob::{
199    ///     OrderBook,
200    ///     Side,
201    ///     errors,
202    /// };
203    /// use rust_decimal::Decimal;
204    ///
205    /// let mut ob = OrderBook::new();
206    /// let _ = ob.process_limit_order(884213, Side::Sell, Decimal::from(5), Decimal::from(5));
207    ///
208    /// assert_eq!(ob.cancel_order(884213), Ok(()));
209    ///
210    /// // possible errors
211    /// assert_eq!(ob.cancel_order(884213), Err(errors::CancelOrder::OrderNotFound));
212    /// ```
213    pub fn cancel_order(&mut self, id: OrderID) -> Result<(), errors::CancelOrder> {
214        let Some(shared_order) = self.order_index.remove(&id) else {
215            return Err(errors::CancelOrder::OrderNotFound);
216        };
217
218        let side = shared_order.borrow().side;
219        match side {
220            Side::Buy => self.buy_side.remove(shared_order),
221            Side::Sell => self.sell_side.remove(shared_order),
222        }
223
224        Ok(())
225    }
226
227    /// Calculates cost to buy/sell up to quantity.
228    /// This function does not mutate anything in OrderBook.
229    /// The return tuple is in format (quantity_fulfilled, cost).
230    /// ```
231    /// use rust_ob::{
232    ///     OrderBook,
233    ///     Side,
234    ///     errors,
235    /// };
236    /// use rust_decimal::Decimal;
237    ///
238    /// let mut ob = OrderBook::new();
239    /// let _ = ob.process_limit_order(1, Side::Buy, Decimal::from(5), Decimal::from(5));
240    /// let _ = ob.process_limit_order(2, Side::Buy, Decimal::from(3), Decimal::from(3));
241    ///
242    /// assert_eq!(ob.calculate_market_cost(Side::Sell, Decimal::from(6)).unwrap(), (Decimal::from(6), Decimal::from(-28)));
243    /// assert_eq!(ob.calculate_market_cost(Side::Sell, Decimal::from(12)).unwrap(), (Decimal::from(8), Decimal::from(-34)));
244    ///
245    /// // possible errors
246    /// assert_eq!(ob.calculate_market_cost(Side::Sell, Decimal::from(0)), Err(errors::CalculateMarketCost::NonPositiveQuantity));
247    /// ```
248    pub fn calculate_market_cost(
249        &self,
250        side: Side,
251        mut quantity: Decimal,
252    ) -> Result<(Decimal, Decimal), errors::CalculateMarketCost> {
253        // check to ensure positive quantity
254        if quantity <= Decimal::ZERO {
255            return Err(errors::CalculateMarketCost::NonPositiveQuantity);
256        }
257
258        // inits
259        let mut quantity_fulfilled = Decimal::ZERO;
260        let mut cost = Decimal::ZERO;
261        let mut opposite_side_iter = match side {
262            Side::Buy => self.sell_side.iter(),
263            Side::Sell => self.buy_side.iter(),
264        };
265
266        while !quantity.is_zero() {
267            let shared_order = opposite_side_iter.next();
268            let order = match shared_order {
269                Some(val) => val.borrow(),
270                None => break,
271            };
272
273            let satisfied_quantity = quantity.min(order.quantity);
274            quantity -= satisfied_quantity;
275            quantity_fulfilled += satisfied_quantity;
276
277            let buy_side_cost = order.price * satisfied_quantity;
278            match side {
279                Side::Buy => cost += buy_side_cost,
280                Side::Sell => cost -= buy_side_cost,
281            }
282        }
283
284        Ok((quantity_fulfilled, cost))
285    }
286
287    /// Process new market order
288    /// ```
289    /// use rust_ob::{
290    ///     OrderBook,
291    ///     Side,
292    ///     OrderMatch,
293    ///     errors,
294    /// };
295    /// use rust_decimal::Decimal;
296    ///
297    /// let mut ob = OrderBook::new();
298    /// let _ = ob.process_limit_order(1, Side::Sell, Decimal::from(5), Decimal::from(5));
299    /// let _ = ob.process_limit_order(2, Side::Sell, Decimal::from(3), Decimal::from(3));
300    ///
301    /// assert_eq!(
302    ///     ob.process_market_order(3, Side::Buy, Decimal::from(6)).unwrap(),
303    ///     vec![
304    ///         OrderMatch {
305    ///             order: 2,
306    ///             quantity: Decimal::from(3),
307    ///             cost: Decimal::from(-9)
308    ///         },
309    ///         OrderMatch {
310    ///             order: 1,
311    ///             quantity: Decimal::from(3),
312    ///             cost: Decimal::from(-15)
313    ///         },
314    ///         OrderMatch {
315    ///             order: 3,
316    ///             quantity: Decimal::from(6),
317    ///             cost: Decimal::from(24)
318    ///         }
319    ///     ]
320    /// );
321    ///
322    /// // possible errors
323    /// assert_eq!(ob.process_market_order(4, Side::Buy, Decimal::from(0)), Err(errors::ProcessMarketOrder::NonPositiveQuantity));
324    /// assert_eq!(ob.process_market_order(1, Side::Buy, Decimal::from(3)), Err(errors::ProcessMarketOrder::OrderAlreadyExists));
325    /// ```
326    pub fn process_market_order(
327        &mut self,
328        id: OrderID,
329        side: Side,
330        quantity: Decimal,
331    ) -> Result<Vec<OrderMatch<OrderID>>, errors::ProcessMarketOrder> {
332        // get min or max price based on side
333        let price = match side {
334            Side::Buy => Decimal::MAX,
335            Side::Sell => Decimal::MIN,
336        };
337
338        let result = self
339            .process_limit_order(id, side, price, quantity)
340            .map_err(|e| match e {
341                errors::ProcessLimitOrder::NonPositiveQuantity => {
342                    errors::ProcessMarketOrder::NonPositiveQuantity
343                }
344                errors::ProcessLimitOrder::OrderAlreadyExists => {
345                    errors::ProcessMarketOrder::OrderAlreadyExists
346                }
347            });
348
349        if let Ok(ref order_match_vec) = result {
350            if order_match_vec.len() == 0 || order_match_vec.last().unwrap().quantity != quantity {
351                assert_eq!(self.cancel_order(id), Ok(()));
352            }
353        }
354
355        result
356    }
357
358    /// Returns the `OrderID` of the next to be fulfilled order by side
359    pub fn get_highest_priority_order(&self, side: Side) -> Option<OrderID> {
360        let shared_order = match side {
361            Side::Buy => self.buy_side.get_highest_priority(),
362            Side::Sell => self.sell_side.get_highest_priority(),
363        };
364
365        shared_order.map(|o| o.borrow().id)
366    }
367
368    /// Returns the price of the next to be fulfilled order by side
369    pub fn get_highest_priority_price(&self, side: Side) -> Option<Decimal> {
370        let shared_order = match side {
371            Side::Buy => self.buy_side.get_highest_priority(),
372            Side::Sell => self.sell_side.get_highest_priority(),
373        };
374
375        shared_order.map(|o| o.borrow().price)
376    }
377
378    /// Returns (price, quantity_at_price) of the highest priority price by side
379    pub fn get_highest_priority_price_quantity(&self, side: Side) -> Option<(Decimal, Decimal)> {
380        // return vars
381        let mut price = Decimal::ZERO;
382        let mut quantity_at_price = Decimal::ZERO;
383
384        let side_iter = match side {
385            Side::Buy => self.buy_side.iter(),
386            Side::Sell => self.sell_side.iter(),
387        };
388
389        for (i, order) in side_iter.map(|o| o.borrow()).enumerate() {
390            if i == 0 {
391                price = order.price;
392            } else if price != order.price {
393                break;
394            }
395
396            quantity_at_price += order.quantity
397        }
398
399        if quantity_at_price.is_zero() {
400            return None;
401        }
402
403        Some((price, quantity_at_price))
404    }
405
406    fn get_next_priority(&mut self) -> u64 {
407        self.priority += 1;
408        self.priority
409    }
410}
411
412impl<OrderID: Display> Display for OrderBook<OrderID>
413where
414    OrderID: Copy + PartialEq + Eq + Hash,
415{
416    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417        const PADDING: usize = 18;
418        writeln!(
419            f,
420            "{:->PADDING$}{:->PADDING$}{:->PADDING$}{:->PADDING$}",
421            "ID", "SIDE", "PRICE", "QUANTITY"
422        )?;
423
424        for shared_order in self.sell_side.iter().rev().chain(self.buy_side.iter()) {
425            let order = shared_order.borrow();
426            writeln!(
427                f,
428                "{:>PADDING$}{:>PADDING$}{:>PADDING$}{:>PADDING$}",
429                order.id,
430                order.side.to_string(),
431                order.price,
432                order.quantity
433            )?;
434        }
435
436        write!(f, "")
437    }
438}
439
440unsafe impl<OrderID: Copy + PartialEq + Eq + Hash + Send> Send for OrderBook<OrderID> {}
441
442#[derive(Debug, PartialEq, Clone)]
443pub struct OrderMatch<OrderID> {
444    /// ID of order
445    pub order: OrderID,
446    /// Quantity of order just fulfilled
447    /// - Always positive
448    pub quantity: Decimal,
449    /// Cost to buy/sell quantity
450    /// - Positive priced buys add to cost
451    /// - Positive priced sells subract from cost
452    /// - Negatively priced buys subract from cost
453    /// - Negatively priced sell add to cost
454    pub cost: Decimal,
455}
456
457impl<OrderID> OrderMatch<OrderID> {
458    fn new(order: OrderID) -> Self {
459        OrderMatch {
460            order,
461            quantity: Decimal::ZERO,
462            cost: Decimal::ZERO,
463        }
464    }
465}