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