Skip to main content

clob_sync/order_book_side/
mod.rs

1mod in_memory_order_book_side;
2
3use derive_more::derive::Display;
4use std::cmp::Ordering;
5
6use crate::{
7    __private::Sealed,
8    order::{Order, OrderSide, OrderType, Quantity},
9    prelude::*,
10};
11
12pub use in_memory_order_book_side::InMemoryOrderBookSide;
13
14/// Represents either the bids (buy orders) or asks (sell orders) side of an order book.
15#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone, Display)]
16pub enum BookSide {
17    /// The buy orders (bids) side of the book
18    Bids,
19    /// The sell orders (asks) side of the book
20    Asks,
21}
22
23/// A trait for order book side implementations.
24///
25/// Implementors provide logic for crossing orders against the book
26/// and allocating orders to the book.
27pub trait OrderBookSide: Sealed {
28    /// Attempts to cross an order against existing orders on this side of the book.
29    fn try_cross(&mut self, order: &Order) -> Result<Matches>;
30
31    /// Allocates an order to this side of the book (after no-cross or partial cross).
32    fn allocate(&mut self, order: Order);
33}
34
35/// Represents a match between an existing order and an incoming order.
36///
37/// # Variants
38///
39/// - `FullPartial`: Existing order fully filled, incoming order partially filled
40/// - `PartialFull`: Existing order partially filled, incoming order fully filled
41/// - `FullFull`: Both orders fully filled
42#[derive(Debug)]
43pub enum Match {
44    FullPartial {
45        existing: FullFill,
46        incoming: PartialFill,
47    },
48    PartialFull {
49        existing: PartialFill,
50        incoming: FullFill,
51    },
52    FullFull {
53        existing: FullFill,
54        incoming: FullFill,
55    },
56}
57
58/// Represents a partial fill of an order, with remaining quantity.
59#[derive(Debug, Copy, Clone)]
60pub struct PartialFill(pub OrderId, Quantity);
61impl PartialFill {
62    /// Returns the remaining (leaves) quantity for this partial fill.
63    pub fn leaves(&self) -> Quantity {
64        self.1
65    }
66}
67
68/// Represents a full fill of an order (no remaining quantity).
69#[derive(Debug)]
70pub struct FullFill(pub OrderId);
71
72/// The result of attempting to cross an order against the book.
73#[derive(Debug)]
74pub struct Matches {
75    /// All matches that occurred during the cross
76    pub matches: Vec<Match>,
77    /// If not None, the remaining quantity of the incoming order
78    /// (i.e., quantity that couldn't be matched)
79    pub remaining_incoming_order: Option<Quantity>,
80}
81
82impl Order {
83    pub fn with_quantity(&self, quantity: Quantity) -> Order {
84        let mut result = self.clone();
85        result.quantity = quantity;
86        result
87    }
88}
89
90impl PartialOrd for Order {
91    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92        Some(self.cmp(other))
93    }
94}
95
96impl Ord for Order {
97    fn cmp(&self, other: &Self) -> Ordering {
98        use OrderType::*;
99        use Ordering::*;
100
101        match (&self.order_type, &other.order_type) {
102            (Limit(a), Limit(b)) => match self.side {
103                OrderSide::Buy => a.cmp(b),
104                OrderSide::Sell => a.cmp(b).reverse(),
105            }
106            .then_with(|| self.id.cmp(&other.id).reverse()),
107            (Limit(_), Market) => match self.side {
108                OrderSide::Buy => Less,
109                OrderSide::Sell => Greater,
110            },
111            (Market, Limit(_)) => match self.side {
112                OrderSide::Buy => Greater,
113                OrderSide::Sell => Less,
114            },
115            (Market, Market) => self.id.cmp(&other.id).reverse(),
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::order::{OrderId, OrderSide, Symbol};
124    use std::{cmp::Ordering, collections::BinaryHeap, rc::Rc};
125
126    #[test]
127    fn limit_vs_limit_different_prices_orders_by_price() {
128        let low = {
129            let mut o = Order::new(
130                OrderType::Limit(Price::from(100)),
131                Quantity::from(100u64),
132                OrderSide::Buy,
133                Symbol::from("TEST"),
134            );
135            o.id = None;
136            o
137        };
138        let high = {
139            let mut o = Order::new(
140                OrderType::Limit(Price::from(200)),
141                Quantity::from(100u64),
142                OrderSide::Buy,
143                Symbol::from("TEST"),
144            );
145            o.id = None;
146            o
147        };
148
149        assert_eq!(low.cmp(&high), Ordering::Less);
150        assert_eq!(high.cmp(&low), Ordering::Greater);
151    }
152
153    #[test]
154    fn limit_vs_limit_same_price_orders_by_id() {
155        let first = Order::new(
156            OrderType::Limit(Price::from(100)),
157            Quantity::from(100u64),
158            OrderSide::Buy,
159            Symbol::from("TEST"),
160        )
161        .with_id();
162
163        let second = Order::new(
164            OrderType::Limit(Price::from(100)),
165            Quantity::from(100u64),
166            OrderSide::Buy,
167            Symbol::from("TEST"),
168        )
169        .with_id();
170
171        assert_eq!(first.cmp(&second), Ordering::Greater);
172    }
173
174    #[test]
175    fn limit_vs_market_limit_comes_first_on_buy_last_on_sell() {
176        {
177            let limit = Order::new(
178                OrderType::Limit(Price::from(100)),
179                Quantity::from(100u64),
180                OrderSide::Buy,
181                Symbol::from("TEST"),
182            )
183            .with_id();
184            let market = Order::new(
185                OrderType::Market,
186                Quantity::from(100u64),
187                OrderSide::Buy,
188                Symbol::from("TEST"),
189            )
190            .with_id();
191
192            assert_eq!(limit.cmp(&market), Ordering::Less);
193            assert_eq!(market.cmp(&limit), Ordering::Greater);
194        }
195
196        {
197            let limit = Order::new(
198                OrderType::Limit(Price::from(100)),
199                Quantity::from(100u64),
200                OrderSide::Sell,
201                Symbol::from("TEST"),
202            )
203            .with_id();
204            let market = Order::new(
205                OrderType::Market,
206                Quantity::from(100u64),
207                OrderSide::Sell,
208                Symbol::from("TEST"),
209            )
210            .with_id();
211
212            assert_eq!(limit.cmp(&market), Ordering::Greater);
213            assert_eq!(market.cmp(&limit), Ordering::Less);
214        }
215    }
216
217    #[test]
218    fn market_vs_market_orders_by_id_any_side() {
219        {
220            let first = Order::new(
221                OrderType::Market,
222                Quantity::from(100u64),
223                OrderSide::Buy,
224                Symbol::from("TEST"),
225            )
226            .with_id();
227            let second = Order::new(
228                OrderType::Market,
229                Quantity::from(100u64),
230                OrderSide::Buy,
231                Symbol::from("TEST"),
232            )
233            .with_id();
234
235            assert_eq!(first.cmp(&second), Ordering::Greater);
236            assert_eq!(second.cmp(&first), Ordering::Less);
237        }
238        {
239            let first = Order::new(
240                OrderType::Market,
241                Quantity::from(100u64),
242                OrderSide::Sell,
243                Symbol::from("TEST"),
244            )
245            .with_id();
246            let second = Order::new(
247                OrderType::Market,
248                Quantity::from(100u64),
249                OrderSide::Sell,
250                Symbol::from("TEST"),
251            )
252            .with_id();
253
254            assert_eq!(first.cmp(&second), Ordering::Greater);
255            assert_eq!(second.cmp(&first), Ordering::Less);
256        }
257    }
258
259    #[test]
260    fn identical_limit_orders_are_ordered_by_creation_time() {
261        let first = {
262            let mut o = Order::new(
263                OrderType::Limit(Price::from(100)),
264                Quantity::from(100u64),
265                OrderSide::Buy,
266                Symbol::from("TEST"),
267            );
268            o.id = Some(OrderId::default());
269            o
270        }
271        .with_id();
272        let second = {
273            let mut o = Order::new(
274                OrderType::Limit(Price::from(100)),
275                Quantity::from(100u64),
276                OrderSide::Buy,
277                Symbol::from("TEST"),
278            );
279            o.id = Some(OrderId::default());
280            o
281        }
282        .with_id();
283
284        let mut heap = BinaryHeap::new();
285        heap.push(Rc::new(second.clone()));
286        heap.push(Rc::new(first.clone()));
287
288        // creation time, not insertion time
289        assert_eq!(first.get_id(), heap.pop().unwrap().get_id());
290        assert_eq!(second.get_id(), heap.pop().unwrap().get_id());
291
292        // in rust, greater is earlier, regardless of buy or sell orders
293        assert_eq!(first.cmp(&second), Ordering::Greater);
294    }
295
296    #[test]
297    fn identical_market_orders_are_ordered_by_id_creation_time() {
298        {
299            let first = Order::new(
300                OrderType::Market,
301                Quantity::from(100u64),
302                OrderSide::Buy,
303                Symbol::from("TEST"),
304            )
305            .with_id();
306            let second = Order::new(
307                OrderType::Market,
308                Quantity::from(100u64),
309                OrderSide::Buy,
310                Symbol::from("TEST"),
311            )
312            .with_id();
313
314            assert_eq!(first.cmp(&second), Ordering::Greater);
315        }
316
317        {
318            let first = Order::new(
319                OrderType::Market,
320                Quantity::from(100u64),
321                OrderSide::Sell,
322                Symbol::from("TEST"),
323            )
324            .with_id();
325            let second = Order::new(
326                OrderType::Market,
327                Quantity::from(100u64),
328                OrderSide::Sell,
329                Symbol::from("TEST"),
330            )
331            .with_id();
332
333            assert_eq!(first.cmp(&second), Ordering::Greater);
334        }
335    }
336
337    #[test]
338    fn partial_cmp_delegates_to_cmp() {
339        let limit = {
340            let mut o = Order::new(
341                OrderType::Limit(Price::from(100)),
342                Quantity::from(100u64),
343                OrderSide::Buy,
344                Symbol::from("TEST"),
345            );
346            o.id = None;
347            o
348        };
349        let market = {
350            let mut o = Order::new(
351                OrderType::Market,
352                Quantity::from(100u64),
353                OrderSide::Buy,
354                Symbol::from("TEST"),
355            );
356            o.id = None;
357            o
358        };
359
360        assert_eq!(limit.partial_cmp(&market), Some(Ordering::Less));
361        assert_eq!(market.partial_cmp(&limit), Some(Ordering::Greater));
362    }
363
364    #[test]
365    fn partial_cmp_transitivity_property() {
366        // If a < b and b < c, then a < c
367        let order_a = {
368            let mut o = Order::new(
369                OrderType::Limit(Price::from(50)),
370                Quantity::from(100u64),
371                OrderSide::Buy,
372                Symbol::from("TEST"),
373            );
374            o.id = None;
375            o
376        };
377        let order_b = {
378            let mut o = Order::new(
379                OrderType::Limit(Price::from(100)),
380                Quantity::from(100u64),
381                OrderSide::Buy,
382                Symbol::from("TEST"),
383            );
384            o.id = None;
385            o
386        };
387        let order_c = {
388            let mut o = Order::new(
389                OrderType::Limit(Price::from(150)),
390                Quantity::from(100u64),
391                OrderSide::Buy,
392                Symbol::from("TEST"),
393            );
394            o.id = None;
395            o
396        };
397
398        assert!(order_a < order_b);
399        assert!(order_b < order_c);
400        assert!(order_a < order_c);
401    }
402
403    #[test]
404    fn partial_cmp_sorts_mixed_orders_correctly() {
405        let limit_low = {
406            let mut o = Order::new(
407                OrderType::Limit(Price::from(100)),
408                Quantity::from(100u64),
409                OrderSide::Buy,
410                Symbol::from("TEST"),
411            );
412            o.id = None;
413            o
414        };
415        let limit_high = {
416            let mut o = Order::new(
417                OrderType::Limit(Price::from(200)),
418                Quantity::from(100u64),
419                OrderSide::Buy,
420                Symbol::from("TEST"),
421            );
422            o.id = None;
423            o
424        };
425        let market = {
426            let mut o = Order::new(
427                OrderType::Market,
428                Quantity::from(100u64),
429                OrderSide::Buy,
430                Symbol::from("TEST"),
431            );
432            o.id = None;
433            o
434        };
435
436        let mut orders =
437            [market.clone(), limit_high.clone(), limit_low.clone()];
438        orders.sort_by(|a, b| a.partial_cmp(b).unwrap());
439
440        assert_eq!(orders[0], limit_low);
441        assert_eq!(orders[1], limit_high);
442        assert_eq!(orders[2], market);
443    }
444
445    // Matcher trait tests
446
447    #[test]
448    fn with_reduced_quantity_basic() {
449        let existing = Order::new(
450            OrderType::Limit(Price::from(100)),
451            Quantity::from(100u64),
452            OrderSide::Buy,
453            Symbol::from("TEST"),
454        );
455        let incoming = Order::new(
456            OrderType::Limit(Price::from(100)),
457            Quantity::from(30u64),
458            OrderSide::Sell,
459            Symbol::from("TEST"),
460        );
461
462        let result = existing.with_reduced_quantity(&incoming.quantity);
463
464        assert_eq!(result.quantity, Quantity::from(70u64));
465        assert_eq!(result.order_type, existing.order_type);
466        assert_eq!(result.side, existing.side);
467        assert_eq!(result.symbol, existing.symbol);
468    }
469}