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                let _ = self._book.insert(order.security_id, OrderBook::new(1)).unwrap();
171                self._book.get_mut(&order.security_id).unwrap()
172            }
173        };
174
175        if !order.is_buy_side {
176            // for ASK order
177            match order.order_type {
178                OrderType::Market(None) => {
179                    // need to immediatly execute the order on the best of other half
180                    let mut fill_quantity = order.initial_quantity;
181                    let mut levels_touched = 0;
182                    let mut orders_consumed = 0;
183                    while fill_quantity > 0 {
184                        let remove_node: bool;
185                        {
186                            let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
187                                break;
188                            };
189                            let price_level = price_node.get_mut();
190                            while price_level.total_quantity > 0 && fill_quantity > 0 {
191                                let head_idx = price_level.head;
192                                let first_order_node =
193                                    orderbook.bid.order_pool[head_idx].as_mut().unwrap();
194                                if fill_quantity >= first_order_node.current_quantity {
195                                    fill_quantity -= first_order_node.current_quantity;
196                                    price_level.total_quantity -= first_order_node.current_quantity;
197                                    let next = first_order_node.next;
198                                    orderbook.bid.order_pool[head_idx] = None;
199                                    orderbook.bid.free_list.push(head_idx);
200                                    orders_consumed += 1;
201                                    if let Some(next_order_idx) = next {
202                                        price_level.head = next_order_idx;
203                                    } else {
204                                        span.record("reason", "exhausted");
205                                        break;
206                                    }
207                                } else {
208                                    first_order_node.current_quantity -= fill_quantity;
209                                    price_level.total_quantity -= fill_quantity;
210                                    fill_quantity = 0;
211                                    span.record("filled", true);
212                                }
213                            }
214                            remove_node = price_level.total_quantity == 0;
215                        }
216                        if remove_node {
217                            orderbook.bid.price_map.pop_first();
218                            levels_touched += 1;
219                        }
220                    }
221                    span.record("order_type", "market");
222                    span.record("is_buy_side", false);
223                    span.record("levels_touched", levels_touched);
224                    span.record("orders_consumed", orders_consumed);
225                }
226                OrderType::Market(market_limit) => {
227                    let mut fill_quantity = order.initial_quantity;
228                    let mut levels_touched = 0;
229                    let mut orders_consumed = 0;
230                    while fill_quantity > 0 {
231                        let remove_node: bool;
232                        {
233                            let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
234                                break;
235                            };
236                            if market_limit.unwrap() >= *price_node.key() {
237                                break;
238                            }
239                            let price_level = price_node.get_mut();
240                            while price_level.total_quantity > 0 && fill_quantity > 0 {
241                                let head_idx = price_level.head;
242                                let first_order_node =
243                                    orderbook.bid.order_pool[head_idx].as_mut().unwrap();
244                                if fill_quantity >= first_order_node.current_quantity {
245                                    fill_quantity -= first_order_node.current_quantity;
246                                    price_level.total_quantity -= first_order_node.current_quantity;
247                                    let next = first_order_node.next;
248                                    orderbook.bid.order_pool[head_idx] = None;
249                                    orderbook.bid.free_list.push(head_idx);
250                                    orders_consumed += 1;
251                                    if let Some(next_order_idx) = next {
252                                        price_level.head = next_order_idx;
253                                    } else {
254                                        span.record("reason", "exhausted");
255                                        break;
256                                    }
257                                } else {
258                                    first_order_node.current_quantity -= fill_quantity;
259                                    price_level.total_quantity -= fill_quantity;
260                                    fill_quantity = 0;
261                                    span.record("filled", true);
262                                }
263                            }
264                            remove_node = price_level.total_quantity == 0;
265                        }
266                        if remove_node {
267                            orderbook.bid.price_map.pop_first();
268                            levels_touched += 1;
269                        }
270                    }
271                    span.record("order_type", "market");
272                    span.record("is_buy_side", false);
273                    span.record("levels_touched", levels_touched);
274                    span.record("orders_consumed", orders_consumed);
275                }
276                OrderType::Limit => {
277                    let mut fill_quantity = order.initial_quantity;
278                    let mut levels_touched = 0;
279                    let mut orders_consumed = 0;
280                    while fill_quantity > 0 {
281                        let remove_node: bool;
282                        {
283                            let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
284                                break;
285                            };
286                            if order.price >= Some(*price_node.key()) {
287                                break;
288                            }
289                            let price_level = price_node.get_mut();
290                            while price_level.total_quantity > 0 && fill_quantity > 0 {
291                                let head_idx = price_level.head;
292                                let first_order_node =
293                                    orderbook.bid.order_pool[head_idx].as_mut().unwrap();
294                                if fill_quantity >= first_order_node.current_quantity {
295                                    fill_quantity -= first_order_node.current_quantity;
296                                    price_level.total_quantity -= first_order_node.current_quantity;
297                                    let next = first_order_node.next;
298                                    orderbook.bid.order_pool[head_idx] = None;
299                                    orderbook.bid.free_list.push(head_idx);
300                                    orders_consumed += 1;
301                                    if let Some(next_order_idx) = next {
302                                        price_level.head = next_order_idx;
303                                    } else {
304                                        span.record("reason", "partially_filled");
305                                        break;
306                                    }
307                                } else {
308                                    first_order_node.current_quantity -= fill_quantity;
309                                    price_level.total_quantity -= fill_quantity;
310                                    fill_quantity = 0;
311                                }
312                            }
313                            remove_node = price_level.total_quantity == 0;
314                        }
315                        if remove_node {
316                            orderbook.bid.price_map.pop_first();
317                            levels_touched += 1;
318                        }
319                    }
320                    let alloted_index = orderbook.create_sell_order(
321                        order.engine_order_id,
322                        OrderNode {
323                            initial_quantity: order.initial_quantity,
324                            current_quantity: fill_quantity,
325                            market_limit: order.price.unwrap(),
326                            next: None,
327                            prev: None,
328                        },
329                    )?;
330                    let order_location = OrderLocation {
331                        security_id : order.security_id,
332                        is_buy_side : order.is_buy_side,
333                        order_index : alloted_index
334                    };
335                    self._global_registry.insert(order.engine_order_id, order_location);
336                    span.record("order_type", "limit");
337                    span.record("is_buy_side", false);
338                    span.record("levels_touched", levels_touched);
339                    span.record("orders_consumed", orders_consumed);
340                }
341            }
342        } else {
343            match order.order_type {
344                OrderType::Market(None) => {
345                    // need to immediatly execute the order on the best of other half
346                    let mut fill_quantity = order.initial_quantity;
347                    let mut levels_touched = 0;
348                    let mut orders_consumed = 0;
349                    while fill_quantity > 0 {
350                        let remove_node: bool;
351                        {
352                            let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
353                                break;
354                            };
355                            let price_level = price_node.get_mut();
356                            while price_level.total_quantity > 0 && fill_quantity > 0 {
357                                let head_idx = price_level.head;
358                                let first_order_node =
359                                    orderbook.ask.order_pool[head_idx].as_mut().unwrap();
360                                if fill_quantity >= first_order_node.current_quantity {
361                                    fill_quantity -= first_order_node.current_quantity;
362                                    price_level.total_quantity -= first_order_node.current_quantity;
363                                    let next = first_order_node.next;
364                                    orderbook.ask.order_pool[head_idx] = None;
365                                    orderbook.ask.free_list.push(head_idx);
366                                    orders_consumed += 1;
367                                    if let Some(next_order_idx) = next {
368                                        price_level.head = next_order_idx;
369                                    } else {
370                                        span.record("reason", "exhausted");
371                                        break;
372                                    }
373                                } else {
374                                    first_order_node.current_quantity -= fill_quantity;
375                                    price_level.total_quantity -= fill_quantity;
376                                    fill_quantity = 0;
377                                    span.record("filled", true);
378                                }
379                            }
380                            remove_node = price_level.total_quantity == 0;
381                        }
382                        if remove_node {
383                            orderbook.bid.price_map.pop_last();
384                            levels_touched += 1;
385                        }
386                    }
387                    span.record("order_type", "market");
388                    span.record("is_buy_side", true);
389                    span.record("levels_touched", levels_touched);
390                    span.record("orders_consumed", orders_consumed);
391                }
392                OrderType::Market(market_limit) => {
393                    let mut fill_quantity = order.initial_quantity;
394                    let mut levels_touched = 0;
395                    let mut orders_consumed = 0;
396                    while fill_quantity > 0 {
397                        let remove_node: bool;
398                        {
399                            let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
400                                break;
401                            };
402                            if market_limit.unwrap() <= *price_node.key() {
403                                break;
404                            }
405                            let price_level = price_node.get_mut();
406                            while price_level.total_quantity > 0 && fill_quantity > 0 {
407                                let head_idx = price_level.head;
408                                let first_order_node =
409                                    orderbook.ask.order_pool[head_idx].as_mut().unwrap();
410                                if fill_quantity >= first_order_node.current_quantity {
411                                    fill_quantity -= first_order_node.current_quantity;
412                                    price_level.total_quantity -= first_order_node.current_quantity;
413                                    let next = first_order_node.next;
414                                    orderbook.ask.order_pool[head_idx] = None;
415                                    orderbook.ask.free_list.push(head_idx);
416                                    orders_consumed += 1;
417                                    if let Some(next_order_idx) = next {
418                                        price_level.head = next_order_idx;
419                                    } else {
420                                        span.record("reason", "exhausted");
421                                        break;
422                                    }
423                                } else {
424                                    first_order_node.current_quantity -= fill_quantity;
425                                    price_level.total_quantity -= fill_quantity;
426                                    fill_quantity = 0;
427                                    span.record("filled", true);
428                                }
429                            }
430                            remove_node = price_level.total_quantity == 0;
431                        }
432                        if remove_node {
433                            orderbook.bid.price_map.pop_last();
434                            levels_touched += 1;
435                        }
436                    }
437                    span.record("order_type", "market");
438                    span.record("is_buy_side", true);
439                    span.record("levels_touched", levels_touched);
440                    span.record("orders_consumed", orders_consumed);
441                }
442                OrderType::Limit => {
443                    let mut fill_quantity = order.initial_quantity;
444                    let mut levels_touched = 0;
445                    let mut orders_consumed = 0;
446                    while fill_quantity > 0 {
447                        let remove_node: bool;
448                        {
449                            let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
450                                break;
451                            };
452                            if order.price <= Some(*price_node.key()) {
453                                break;
454                            }
455                            let price_level = price_node.get_mut();
456                            while price_level.total_quantity > 0 && fill_quantity > 0 {
457                                let head_idx = price_level.head;
458                                let first_order_node =
459                                    orderbook.ask.order_pool[head_idx].as_mut().unwrap();
460                                if fill_quantity >= first_order_node.current_quantity {
461                                    fill_quantity -= first_order_node.current_quantity;
462                                    price_level.total_quantity -= first_order_node.current_quantity;
463                                    let next = first_order_node.next;
464                                    orderbook.ask.order_pool[head_idx] = None;
465                                    orderbook.ask.free_list.push(head_idx);
466                                    orders_consumed += 1;
467                                    if let Some(next_order_idx) = next {
468                                        price_level.head = next_order_idx;
469                                    } else {
470                                        span.record("reason", "partially_filled");
471                                        break;
472                                    }
473                                } else {
474                                    first_order_node.current_quantity -= fill_quantity;
475                                    price_level.total_quantity -= fill_quantity;
476                                    fill_quantity = 0;
477                                }
478                            }
479                            remove_node = price_level.total_quantity == 0;
480                        }
481                        if remove_node {
482                            orderbook.bid.price_map.pop_last();
483                            levels_touched += 1;
484                        }
485                    }
486                    let alloted_index = orderbook.create_buy_order(
487                        order.engine_order_id,
488                        OrderNode {
489                            initial_quantity: order.initial_quantity,
490                            current_quantity: fill_quantity,
491                            market_limit: order.price.unwrap(),
492                            next: None,
493                            prev: None,
494                        },
495                    )?;
496                    let order_location = OrderLocation {
497                        security_id : order.security_id,
498                        is_buy_side : order.is_buy_side,
499                        order_index : alloted_index
500                    };
501                    self._global_registry.insert(order.engine_order_id, order_location);
502                    span.record("order_type", "limit");
503                    span.record("is_buy_side", true);
504                    span.record("levels_touched", levels_touched);
505                    span.record("orders_consumed", orders_consumed);
506                }
507            }
508        }
509        Ok(())
510    }
511}