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                    if fill_quantity > 0 {
351                        let alloted_index = orderbook.create_sell_order(
352                            order.engine_order_id,
353                            OrderNode {
354                                initial_quantity: order.initial_quantity,
355                                current_quantity: fill_quantity,
356                                market_limit: order.price.unwrap(),
357                                next: None,
358                                prev: None,
359                            },
360                        )?;
361                        let order_location = OrderLocation {
362                            security_id : order.security_id,
363                            is_buy_side : order.is_buy_side,
364                            order_index : alloted_index
365                        };
366                        self._global_registry.insert(order.engine_order_id, order_location);
367                        span.record("order_type", "limit");
368                        span.record("is_buy_side", false);
369                        span.record("levels_touched", levels_touched);
370                        span.record("orders_consumed", orders_consumed);
371                    }
372                }
373            }
374        } else {
375            match order.order_type {
376                OrderType::Market(None) => {
377                    // need to immediatly execute the order on the best of other half
378                    let mut fill_quantity = order.initial_quantity;
379                    let mut levels_touched = 0;
380                    let mut orders_consumed = 0;
381                    while fill_quantity > 0 {
382                        let remove_node: bool;
383                        {
384                            let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
385                                break;
386                            };
387                            let price_level = price_node.get_mut();
388                            while price_level.total_quantity > 0 && fill_quantity > 0 {
389                                if let Some(head_idx) = price_level.head{
390                                    let first_order_node =
391                                        orderbook.ask.order_pool[head_idx].as_mut().unwrap();
392                                    if fill_quantity >= first_order_node.current_quantity {
393                                        fill_quantity -= first_order_node.current_quantity;
394                                        price_level.total_quantity -= first_order_node.current_quantity;
395                                        let next = first_order_node.next;
396                                        orderbook.ask.order_pool[head_idx] = None;
397                                        orderbook.ask.free_list.push(head_idx);
398                                        orders_consumed += 1;
399                                        if let Some(next_order_idx) = next {
400                                            price_level.head = Some(next_order_idx);
401                                        } else {
402                                            span.record("reason", "exhausted");
403                                            price_level.total_quantity = 0;
404                                            break;
405                                        }
406                                    } else {
407                                        first_order_node.current_quantity -= fill_quantity;
408                                        price_level.total_quantity -= fill_quantity;
409                                        fill_quantity = 0;
410                                        span.record("filled", true);
411                                    }
412                                }
413                                else {
414                                    break;
415                                }
416                            }
417                            remove_node = price_level.total_quantity == 0;
418                        }
419                        if remove_node {
420                            orderbook.bid.price_map.pop_first();
421                            levels_touched += 1;
422                        }
423                    }
424                    span.record("order_type", "market");
425                    span.record("is_buy_side", true);
426                    span.record("levels_touched", levels_touched);
427                    span.record("orders_consumed", orders_consumed);
428                }
429                OrderType::Market(market_limit) => {
430                    let mut fill_quantity = order.initial_quantity;
431                    let mut levels_touched = 0;
432                    let mut orders_consumed = 0;
433                    while fill_quantity > 0 {
434                        let remove_node: bool;
435                        {
436                            let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
437                                break;
438                            };
439                            if market_limit.unwrap() < *price_node.key() {
440                                break;
441                            }
442                            let price_level = price_node.get_mut();
443                            while price_level.total_quantity > 0 && fill_quantity > 0 {
444                                let head_pointer = price_level.head;
445                                if let Some(head_idx) = head_pointer{
446                                    let first_order_node =
447                                        orderbook.ask.order_pool[head_idx].as_mut().unwrap();
448                                    if fill_quantity >= first_order_node.current_quantity {
449                                        fill_quantity -= first_order_node.current_quantity;
450                                        price_level.total_quantity -= first_order_node.current_quantity;
451                                        let next = first_order_node.next;
452                                        orderbook.ask.order_pool[head_idx] = None;
453                                        orderbook.ask.free_list.push(head_idx);
454                                        orders_consumed += 1;
455                                        if let Some(next_order_idx) = next {
456                                            price_level.head = Some(next_order_idx);
457                                        } else {
458                                            span.record("reason", "exhausted");
459                                            price_level.head = None;
460                                            price_level.total_quantity = 0;
461                                            break;
462                                        }
463                                    } else {
464                                        first_order_node.current_quantity -= fill_quantity;
465                                        price_level.total_quantity -= fill_quantity;
466                                        fill_quantity = 0;
467                                        span.record("filled", true);
468                                    }
469                                }
470                                else {
471                                    break;
472                                }
473                            }
474                            remove_node = price_level.total_quantity == 0;
475                        }
476                        if remove_node {
477                            orderbook.bid.price_map.pop_first();
478                            levels_touched += 1;
479                        }
480                    }
481                    span.record("order_type", "market");
482                    span.record("is_buy_side", true);
483                    span.record("levels_touched", levels_touched);
484                    span.record("orders_consumed", orders_consumed);
485                }
486                OrderType::Limit => {
487                    let mut fill_quantity = order.initial_quantity;
488                    let mut levels_touched = 0;
489                    let mut orders_consumed = 0;
490                    while fill_quantity > 0 {
491                        let remove_node: bool;
492                        {
493                            let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
494                                break;
495                            };
496                            if order.price < Some(*price_node.key()) {
497                                break;
498                            }
499                            let price_level = price_node.get_mut();
500                            while price_level.total_quantity > 0 && fill_quantity > 0 {
501                                if let Some(head_idx) = price_level.head{
502
503                                    let first_order_node = orderbook.ask.order_pool[head_idx].as_mut().unwrap();
504                                    if fill_quantity >= first_order_node.current_quantity {
505                                        fill_quantity -= first_order_node.current_quantity;
506                                        price_level.total_quantity -= first_order_node.current_quantity;
507                                        let next = first_order_node.next;
508                                        orderbook.ask.order_pool[head_idx] = None;
509                                        orderbook.ask.free_list.push(head_idx);
510                                        orders_consumed += 1;
511                                        if let Some(next_order_idx) = next {
512                                            price_level.head = Some(next_order_idx);
513                                        } else {
514                                            span.record("reason", "partially_filled");
515                                            price_level.total_quantity = 0;
516                                            break;
517                                        }
518                                    } else {
519                                        first_order_node.current_quantity -= fill_quantity;
520                                        price_level.total_quantity -= fill_quantity;
521                                        fill_quantity = 0;
522                                        span.record("filled", true);
523                                    }
524                                }else {
525                                    break;
526                                }
527                            }
528                            remove_node = price_level.total_quantity == 0;
529                        }
530                        if remove_node {
531                            orderbook.bid.price_map.pop_first();
532                            levels_touched += 1;
533                        }
534                    }
535                    if fill_quantity > 0{
536                        let alloted_index = orderbook.create_buy_order(
537                            order.engine_order_id,
538                            OrderNode {
539                                initial_quantity: order.initial_quantity,
540                                current_quantity: fill_quantity,
541                                market_limit: order.price.unwrap(),
542                                next: None,
543                                prev: None,
544                            },
545                        )?;
546                        let order_location = OrderLocation {
547                            security_id : order.security_id,
548                            is_buy_side : order.is_buy_side,
549                            order_index : alloted_index
550                        };
551                        self._global_registry.insert(order.engine_order_id, order_location);
552                        span.record("order_type", "limit");
553                        span.record("is_buy_side", true);
554                        span.record("levels_touched", levels_touched);
555                        span.record("orders_consumed", orders_consumed);
556                    }
557                }
558            }
559        }
560        Ok(())
561    }
562}