Skip to main content

clob_engine/order_book/
matching_engine.rs

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