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