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