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