Skip to main content

clob_engine/order_book/
matching_engine.rs

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