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