Skip to main content

clob_engine/order_book/
matching_engine.rs

1use crate::order_book::{
2    orderbook::OrderBook,
3    types::{
4        CancelOrder, CancelOutcome, GlobalOrderRegistry, ModifyOrder, ModifyOutcome, NewOrder, OrderLocation, OrderNode, OrderType
5    },
6};
7use anyhow::{Context};
8use std::collections::HashMap;
9use tracing::{Span, instrument};
10use uuid::Uuid;
11
12#[derive(Debug)]
13pub struct MatchingEngine {
14    _book: HashMap<Uuid, OrderBook>,
15    _global_registry: GlobalOrderRegistry,
16}
17
18impl MatchingEngine {
19
20    pub fn new() -> Self{
21        Self { _book: HashMap::new(), _global_registry: GlobalOrderRegistry::new() }
22    }
23
24    #[instrument(
25        name = "get_orderbook",
26        skip(self),
27        fields(
28            order_id = %global_order_id,
29        ),
30    )]
31    fn get_orderbook(
32        &mut self,
33        global_order_id: Uuid,
34        span: &Span
35    ) -> Option<(usize, bool,Uuid, &mut OrderBook)> {
36        let order_location = match self._global_registry.get_details(&global_order_id){
37            Some(location) => {
38                location
39            }
40            None => {
41                span.record("reason", "order not found in global registry");
42                return None;
43            }
44        };
45        let Some(book) = self._book.get_mut(&order_location.security_id) else {
46            span.record("reason", "orderbook doesn't exist");
47            return None;
48        };
49        Some((order_location.order_index, order_location.is_buy_side,order_location.security_id, book))
50    }
51
52    pub fn modify(
53        &mut self,
54        global_order_id: Uuid,
55        new_price: Option<u32>,
56        new_qty: Option<u32>,
57        span: &Span,
58    ) -> Result<(), anyhow::Error> {
59        let (order_index, is_buy_side,security_id, orderbook) = self
60            .get_orderbook(global_order_id, span)
61            .context("Could not find the orderbook")?;
62        if let Ok(potential_modfication) = orderbook.modify_order(
63            global_order_id,
64            ModifyOrder {
65                new_price,
66                order_index,
67                is_buy_side,
68                new_quantity: new_qty,
69            },
70        ) {
71            if let Some(modification_result) = potential_modfication {
72                match modification_result {
73                    ModifyOutcome::Both {
74                        new_price,
75                        new_initial_qty,
76                        old_current_qty,
77                    } => {
78                        span.record("modify_outcome", "price & qty");
79                        if let Some(_) = self._global_registry.delete(&global_order_id){
80                            let _ = self.match_order(
81                            NewOrder {
82                                engine_order_id: global_order_id,
83                                price: Some(new_price),
84                                initial_quantity: new_initial_qty,
85                                current_quantity : old_current_qty,
86                                is_buy_side,
87                                security_id,
88                                order_type: OrderType::Limit,
89                            },
90                            span);
91                            return Ok(());
92                        }
93                        span.record("intermediate_error", "Failed to delete from global registry");
94                    },
95                    ModifyOutcome::Repriced { new_price, old_initial_qty, old_current_qty } => 
96                        {
97                        span.record("modify_outcome", "price");
98                        if let Some(_) = self._global_registry.delete(&global_order_id){
99                            let _ = self.match_order(
100                            NewOrder {
101                                engine_order_id: global_order_id,
102                                price: Some(new_price),
103                                initial_quantity: old_initial_qty,
104                                current_quantity : old_current_qty,
105                                is_buy_side,
106                                security_id,
107                                order_type: OrderType::Limit,
108                            },
109                            span);
110                            return Ok(());
111                        }
112                        span.record("intermediate_error", "Failed to delete from global registry");
113                    },
114                    ModifyOutcome::Requantized { old_price, new_initial_qty, old_current_qty } => {
115                        span.record("modify_outcome", "qty");
116                        if let Some(_) = self._global_registry.delete(&global_order_id){
117                            let _ = self.match_order(
118                            NewOrder {
119                                engine_order_id: global_order_id,
120                                price: Some(old_price),
121                                initial_quantity: new_initial_qty,
122                                current_quantity : old_current_qty,
123                                is_buy_side,
124                                security_id,
125                                order_type: OrderType::Limit,
126                            }, span);
127                            return Ok(());
128                        }
129                        span.record("intermediate_error", "Failed to delete from global registry");
130                    },
131                    ModifyOutcome::Inplace => {
132                        span.record("modify_outcome", "qty reduction");
133                        return Ok(());
134                    }
135                }
136            }
137        } else {
138            return Ok(());
139        }
140        Ok(())
141    }
142
143    pub fn cancel(&mut self, global_order_id: Uuid, span: &Span) -> Result<CancelOutcome, anyhow::Error>{
144        let (order_index, is_buy_side,_, orderbook) = self
145            .get_orderbook(global_order_id, span)
146            .context("Could not find the orderbook")?;
147        if let Err(_) = orderbook.cancel_order(global_order_id, CancelOrder{is_buy_side, order_index}){
148            span.record("reason", "orderbook cancellation failed");
149            span.record("success_status", false);
150            return Ok(CancelOutcome::Failed);
151        }; 
152        if let Some(_) = self._global_registry.delete(&global_order_id){
153            span.record("success_status", true);
154            return Ok(CancelOutcome::Success)
155        };
156        span.record("reason", "Registry cancellation failed");
157        span.record("success_status", false);
158        Ok(CancelOutcome::Failed)
159    }
160
161    pub fn match_order(&mut self, order: NewOrder, span: &Span) -> Result<(), anyhow::Error> {
162        
163        let _gaurd = span.enter();
164
165        let orderbook = match self._book.get_mut(&order.security_id){
166            Some(orderbook) => {
167                orderbook
168            }
169            None => {
170                self._book.entry(order.security_id).or_insert(OrderBook::new(1))
171            }
172        };
173
174        if !order.is_buy_side {
175            // for ASK order
176            match order.order_type {
177                OrderType::Market(None) => {
178                    // need to immediatly execute the order on the best of other half
179                    let mut fill_quantity = order.initial_quantity;
180                    let mut levels_touched = 0;
181                    let mut orders_consumed = 0;
182                    while fill_quantity > 0 {
183                        let remove_node: bool;
184                        {
185                            let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
186                                break;
187                            };
188                            let price_level = price_node.get_mut();
189                            while price_level.total_quantity > 0 && fill_quantity > 0 {
190                                let head_idx = price_level.head;
191                                let first_order_node =
192                                    orderbook.bid.order_pool[head_idx].as_mut().unwrap();
193                                if fill_quantity >= first_order_node.current_quantity {
194                                    fill_quantity -= first_order_node.current_quantity;
195                                    price_level.total_quantity -= first_order_node.current_quantity;
196                                    let next = first_order_node.next;
197                                    orderbook.bid.order_pool[head_idx] = None;
198                                    orderbook.bid.free_list.push(head_idx);
199                                    orders_consumed += 1;
200                                    if let Some(next_order_idx) = next {
201                                        price_level.head = next_order_idx;
202                                    } else {
203                                        span.record("reason", "exhausted");
204                                        break;
205                                    }
206                                } else {
207                                    first_order_node.current_quantity -= fill_quantity;
208                                    price_level.total_quantity -= fill_quantity;
209                                    fill_quantity = 0;
210                                    span.record("filled", true);
211                                }
212                            }
213                            span.record("reason", "orderbook is empty");
214                            span.record("filled", false);
215                            remove_node = price_level.total_quantity == 0;
216                        }
217                        if remove_node {
218                            orderbook.bid.price_map.pop_first();
219                            levels_touched += 1;
220                        }
221                    }
222                    span.record("order_type", "market");
223                    span.record("is_buy_side", false);
224                    span.record("levels_touched", levels_touched);
225                    span.record("orders_consumed", orders_consumed);
226                }
227                OrderType::Market(market_limit) => {
228                    let mut fill_quantity = order.initial_quantity;
229                    let mut levels_touched = 0;
230                    let mut orders_consumed = 0;
231                    while fill_quantity > 0 {
232                        let remove_node: bool;
233                        {
234                            let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
235                                break;
236                            };
237                            if market_limit.unwrap() >= *price_node.key() {
238                                break;
239                            }
240                            let price_level = price_node.get_mut();
241                            while price_level.total_quantity > 0 && fill_quantity > 0 {
242                                let head_idx = price_level.head;
243                                let first_order_node =
244                                    orderbook.bid.order_pool[head_idx].as_mut().unwrap();
245                                if fill_quantity >= first_order_node.current_quantity {
246                                    fill_quantity -= first_order_node.current_quantity;
247                                    price_level.total_quantity -= first_order_node.current_quantity;
248                                    let next = first_order_node.next;
249                                    orderbook.bid.order_pool[head_idx] = None;
250                                    orderbook.bid.free_list.push(head_idx);
251                                    orders_consumed += 1;
252                                    if let Some(next_order_idx) = next {
253                                        price_level.head = next_order_idx;
254                                    } else {
255                                        span.record("reason", "exhausted");
256                                        break;
257                                    }
258                                } else {
259                                    first_order_node.current_quantity -= fill_quantity;
260                                    price_level.total_quantity -= fill_quantity;
261                                    fill_quantity = 0;
262                                    span.record("filled", true);
263                                }
264                            }
265                            span.record("reason", "orderbook is empty");
266                            span.record("filled", false);
267                            remove_node = price_level.total_quantity == 0;
268                        }
269                        if remove_node {
270                            orderbook.bid.price_map.pop_first();
271                            levels_touched += 1;
272                        }
273                    }
274                    span.record("order_type", "market");
275                    span.record("is_buy_side", false);
276                    span.record("levels_touched", levels_touched);
277                    span.record("orders_consumed", orders_consumed);
278                }
279                OrderType::Limit => {
280                    let mut fill_quantity = order.initial_quantity;
281                    let mut levels_touched = 0;
282                    let mut orders_consumed = 0;
283                    while fill_quantity > 0 {
284                        let remove_node: bool;
285                        {
286                            let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
287                                break;
288                            };
289                            if order.price >= Some(*price_node.key()) {
290                                break;
291                            }
292                            let price_level = price_node.get_mut();
293                            while price_level.total_quantity > 0 && fill_quantity > 0 {
294                                let head_idx = price_level.head;
295                                let first_order_node =
296                                    orderbook.bid.order_pool[head_idx].as_mut().unwrap();
297                                if fill_quantity >= first_order_node.current_quantity {
298                                    fill_quantity -= first_order_node.current_quantity;
299                                    price_level.total_quantity -= first_order_node.current_quantity;
300                                    let next = first_order_node.next;
301                                    orderbook.bid.order_pool[head_idx] = None;
302                                    orderbook.bid.free_list.push(head_idx);
303                                    orders_consumed += 1;
304                                    if let Some(next_order_idx) = next {
305                                        price_level.head = next_order_idx;
306                                    } else {
307                                        span.record("reason", "partially_filled");
308                                        break;
309                                    }
310                                } else {
311                                    first_order_node.current_quantity -= fill_quantity;
312                                    price_level.total_quantity -= fill_quantity;
313                                    fill_quantity = 0;
314                                }
315                            }
316                            span.record("reason", "orderbook is empty");
317                            span.record("filled", false);
318                            remove_node = price_level.total_quantity == 0;
319                        }
320                        if remove_node {
321                            orderbook.bid.price_map.pop_first();
322                            levels_touched += 1;
323                        }
324                    }
325                    let alloted_index = orderbook.create_sell_order(
326                        order.engine_order_id,
327                        OrderNode {
328                            initial_quantity: order.initial_quantity,
329                            current_quantity: fill_quantity,
330                            market_limit: order.price.unwrap(),
331                            next: None,
332                            prev: None,
333                        },
334                    )?;
335                    let order_location = OrderLocation {
336                        security_id : order.security_id,
337                        is_buy_side : order.is_buy_side,
338                        order_index : alloted_index
339                    };
340                    self._global_registry.insert(order.engine_order_id, order_location);
341                    span.record("order_type", "limit");
342                    span.record("is_buy_side", false);
343                    span.record("levels_touched", levels_touched);
344                    span.record("orders_consumed", orders_consumed);
345                }
346            }
347        } else {
348            match order.order_type {
349                OrderType::Market(None) => {
350                    // need to immediatly execute the order on the best of other half
351                    let mut fill_quantity = order.initial_quantity;
352                    let mut levels_touched = 0;
353                    let mut orders_consumed = 0;
354                    while fill_quantity > 0 {
355                        let remove_node: bool;
356                        {
357                            let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
358                                break;
359                            };
360                            let price_level = price_node.get_mut();
361                            while price_level.total_quantity > 0 && fill_quantity > 0 {
362                                let head_idx = price_level.head;
363                                let first_order_node =
364                                    orderbook.ask.order_pool[head_idx].as_mut().unwrap();
365                                if fill_quantity >= first_order_node.current_quantity {
366                                    fill_quantity -= first_order_node.current_quantity;
367                                    price_level.total_quantity -= first_order_node.current_quantity;
368                                    let next = first_order_node.next;
369                                    orderbook.ask.order_pool[head_idx] = None;
370                                    orderbook.ask.free_list.push(head_idx);
371                                    orders_consumed += 1;
372                                    if let Some(next_order_idx) = next {
373                                        price_level.head = next_order_idx;
374                                    } else {
375                                        span.record("reason", "exhausted");
376                                        break;
377                                    }
378                                } else {
379                                    first_order_node.current_quantity -= fill_quantity;
380                                    price_level.total_quantity -= fill_quantity;
381                                    fill_quantity = 0;
382                                    span.record("filled", true);
383                                }
384                            }
385                            span.record("reason", "orderbook is empty");
386                            span.record("filled", false);
387                            remove_node = price_level.total_quantity == 0;
388                        }
389                        if remove_node {
390                            orderbook.bid.price_map.pop_last();
391                            levels_touched += 1;
392                        }
393                    }
394                    span.record("order_type", "market");
395                    span.record("is_buy_side", true);
396                    span.record("levels_touched", levels_touched);
397                    span.record("orders_consumed", orders_consumed);
398                }
399                OrderType::Market(market_limit) => {
400                    let mut fill_quantity = order.initial_quantity;
401                    let mut levels_touched = 0;
402                    let mut orders_consumed = 0;
403                    while fill_quantity > 0 {
404                        let remove_node: bool;
405                        {
406                            let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
407                                break;
408                            };
409                            if market_limit.unwrap() <= *price_node.key() {
410                                break;
411                            }
412                            let price_level = price_node.get_mut();
413                            while price_level.total_quantity > 0 && fill_quantity > 0 {
414                                let head_idx = price_level.head;
415                                let first_order_node =
416                                    orderbook.ask.order_pool[head_idx].as_mut().unwrap();
417                                if fill_quantity >= first_order_node.current_quantity {
418                                    fill_quantity -= first_order_node.current_quantity;
419                                    price_level.total_quantity -= first_order_node.current_quantity;
420                                    let next = first_order_node.next;
421                                    orderbook.ask.order_pool[head_idx] = None;
422                                    orderbook.ask.free_list.push(head_idx);
423                                    orders_consumed += 1;
424                                    if let Some(next_order_idx) = next {
425                                        price_level.head = next_order_idx;
426                                    } else {
427                                        span.record("reason", "exhausted");
428                                        break;
429                                    }
430                                } else {
431                                    first_order_node.current_quantity -= fill_quantity;
432                                    price_level.total_quantity -= fill_quantity;
433                                    fill_quantity = 0;
434                                    span.record("filled", true);
435                                }
436                            }
437                            span.record("reason", "orderbook is empty");
438                            span.record("filled", false);
439                            remove_node = price_level.total_quantity == 0;
440                        }
441                        if remove_node {
442                            orderbook.bid.price_map.pop_last();
443                            levels_touched += 1;
444                        }
445                    }
446                    span.record("order_type", "market");
447                    span.record("is_buy_side", true);
448                    span.record("levels_touched", levels_touched);
449                    span.record("orders_consumed", orders_consumed);
450                }
451                OrderType::Limit => {
452                    let mut fill_quantity = order.initial_quantity;
453                    let mut levels_touched = 0;
454                    let mut orders_consumed = 0;
455                    while fill_quantity > 0 {
456                        let remove_node: bool;
457                        {
458                            let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
459                                break;
460                            };
461                            if order.price <= Some(*price_node.key()) {
462                                break;
463                            }
464                            let price_level = price_node.get_mut();
465                            while price_level.total_quantity > 0 && fill_quantity > 0 {
466                                let head_idx = price_level.head;
467                                let first_order_node =
468                                    orderbook.ask.order_pool[head_idx].as_mut().unwrap();
469                                if fill_quantity >= first_order_node.current_quantity {
470                                    fill_quantity -= first_order_node.current_quantity;
471                                    price_level.total_quantity -= first_order_node.current_quantity;
472                                    let next = first_order_node.next;
473                                    orderbook.ask.order_pool[head_idx] = None;
474                                    orderbook.ask.free_list.push(head_idx);
475                                    orders_consumed += 1;
476                                    if let Some(next_order_idx) = next {
477                                        price_level.head = next_order_idx;
478                                    } else {
479                                        span.record("reason", "partially_filled");
480                                        break;
481                                    }
482                                } else {
483                                    first_order_node.current_quantity -= fill_quantity;
484                                    price_level.total_quantity -= fill_quantity;
485                                    fill_quantity = 0;
486                                    span.record("filled", false);
487                                }
488                            }
489                            span.record("reason", "orderbook is empty");
490                            span.record("filled", false);
491                            remove_node = price_level.total_quantity == 0;
492                        }
493                        if remove_node {
494                            orderbook.bid.price_map.pop_last();
495                            levels_touched += 1;
496                        }
497                    }
498                    let alloted_index = orderbook.create_buy_order(
499                        order.engine_order_id,
500                        OrderNode {
501                            initial_quantity: order.initial_quantity,
502                            current_quantity: fill_quantity,
503                            market_limit: order.price.unwrap(),
504                            next: None,
505                            prev: None,
506                        },
507                    )?;
508                    let order_location = OrderLocation {
509                        security_id : order.security_id,
510                        is_buy_side : order.is_buy_side,
511                        order_index : alloted_index
512                    };
513                    self._global_registry.insert(order.engine_order_id, order_location);
514                    span.record("order_type", "limit");
515                    span.record("is_buy_side", true);
516                    span.record("levels_touched", levels_touched);
517                    span.record("orders_consumed", orders_consumed);
518                }
519            }
520        }
521        Ok(())
522    }
523}