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