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}