Skip to main content

clob_engine/order_book/
matching_engine.rs

1use crate::order_book::{
2    orderbook::OrderBook, types::{
3        BookDepth, EngineCancelOrder, CancelOutcome, EngineModifyOrder, ModifyOutcome, EngineNewOrder, OrderNode, OrderType
4    }
5};
6use anyhow::{Context, anyhow};
7use std::collections::HashMap;
8use tracing::{Span};
9
10#[derive(Debug)]
11pub struct MatchingEngine {
12    _book: HashMap<u32, OrderBook>
13}
14
15impl MatchingEngine {
16
17    pub fn new() -> Self{
18        Self { _book: HashMap::new()}
19    }
20
21    fn get_orderbook(
22        &mut self,
23        security_id : u32
24    ) -> Option<&mut OrderBook> {
25        
26        let Some(book) = self._book.get_mut(&security_id) else {
27            return None;
28        };
29        Some(book)
30    }
31
32    pub fn modify(
33        &mut self,
34        order_id: u64,
35        security_id : u32,
36        new_price: Option<u32>,
37        new_qty: Option<u32>,
38        is_buy_side : bool,
39        span: &Span,
40    ) -> Result< &'static str, anyhow::Error> {
41        let _gaurd = span.enter();
42        let orderbook = self
43            .get_orderbook(security_id)
44            .context("Could not find the orderbook")?;
45        if let Ok(potential_modfication) = orderbook.modify_order(
46            order_id,
47            EngineModifyOrder {
48                order_id,
49                security_id,
50                new_price,
51                is_buy_side,
52                new_quantity: new_qty,
53            },
54        ) {
55            if let Some(modification_result) = potential_modfication {
56                match modification_result {
57                    ModifyOutcome::Both {
58                        new_price,
59                        new_initial_qty,
60                        old_current_qty,
61                    } => {
62                        span.record("modify_outcome", "price & qty");
63                        let _ = self.match_order(
64                            EngineNewOrder {
65                                engine_order_id: order_id,
66                                price: Some(new_price),
67                                initial_quantity: new_initial_qty,
68                                current_quantity : old_current_qty,
69                                is_buy_side,
70                                security_id,
71                                order_type: OrderType::Limit,
72                            },
73                        span);
74                        return Ok("Both")
75                    },
76                    ModifyOutcome::Repriced { new_price, old_initial_qty, old_current_qty } => 
77                        {
78                        span.record("modify_outcome", "price");
79                            let _ = self.match_order(
80                            EngineNewOrder {
81                                engine_order_id: order_id,
82                                price: Some(new_price),
83                                initial_quantity: old_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("Repriced")
91                    },
92                    ModifyOutcome::Requantized { old_price, new_initial_qty, old_current_qty } => {
93                            let _ = self.match_order(
94                            EngineNewOrder {
95                                engine_order_id: order_id,
96                                price: Some(old_price),
97                                initial_quantity: new_initial_qty,
98                                current_quantity : old_current_qty,
99                                is_buy_side,
100                                security_id,
101                                order_type: OrderType::Limit,
102                            }, span);
103                            return Ok("Requantized")
104                    },
105                    ModifyOutcome::Inplace => {
106                        span.record("modify_outcome", "qty reduction");
107                        return Ok("Inplace")
108                    }
109                }
110            }
111            return Ok("No potential modification")
112        } else {
113            return Ok("No modification occured");
114        }
115    }
116
117    pub fn cancel(&mut self, order_id: u64,security_id : u32, span: &Span, is_buy_side : bool) -> Result<CancelOutcome, anyhow::Error>{
118        let orderbook = self
119            .get_orderbook(security_id)
120            .context("Could not find the orderbook")?;
121        if let Err(_) = orderbook.cancel_order(order_id, EngineCancelOrder{is_buy_side,security_id, order_id}){
122            span.record("reason", "orderbook cancellation failed");
123            span.record("success_status", false);
124            return Ok(CancelOutcome::Failed);
125        }; 
126        span.record("success_status", true);
127        return Ok(CancelOutcome::Success);
128    }
129
130    pub fn depth(&self, security_id : u32, levels_count :Option<u32>, span: &Span ) -> Result<BookDepth, anyhow::Error>{
131        let _gaurd = span.enter();
132        span.record("security_id", security_id.to_string());
133        let Some(order_book) = self._book.get(&security_id) else {
134            span.record("status", "failed");
135            span.record("reason", "orderbook doesn't exist");
136            return Err(anyhow!(""))
137        };
138        match order_book.depth(levels_count){
139            Ok(book_depth) => {
140                span.record("status", "success");
141                span.record("reason", "None");
142                Ok(book_depth)
143            },
144            Err(e) => Err(anyhow!("{}", e))
145        }
146    }
147
148    pub fn match_order(&mut self, order: EngineNewOrder, span: &Span) -> Result<Option<usize>, anyhow::Error> {
149        
150        let _gaurd = span.enter();
151
152        let orderbook = match self._book.get_mut(&order.security_id){
153            Some(orderbook) => {
154                orderbook
155            }
156            None => {
157                self._book.entry(order.security_id).or_insert(OrderBook::new())
158            }
159        };
160
161        if !order.is_buy_side {
162            // for ASK order
163            match order.order_type {
164                OrderType::Market(None) => {
165                    // need to immediatly execute the order on the best of other half
166                    let mut fill_quantity = order.initial_quantity;
167                    let mut levels_consumed = 0;
168                    let mut orders_touched = 0;
169                    while fill_quantity > 0 {
170                        let remove_node: bool;
171                        {
172                            let Some(mut price_node) = orderbook.bid.price_map.last_entry() else {
173                                break;
174                            };
175                            let price_level = price_node.get_mut();
176                            while price_level.total_quantity > 0 && fill_quantity > 0 {
177                                if let Some(head_idx) = price_level.head{
178
179                                    match orderbook.bid.order_pool[head_idx].as_mut(){
180                                        Some(first_order_node) => {
181
182                                            if fill_quantity >= first_order_node.current_quantity {
183                                                fill_quantity -= first_order_node.current_quantity;
184                                                
185                                                price_level.total_quantity = price_level.total_quantity.checked_sub(first_order_node.current_quantity).ok_or(anyhow!("error occured in sub of total qty - current qyt"))?;
186                                                let next = first_order_node.next;
187                                                orderbook.bid.order_pool[head_idx] = None;
188                                                orderbook.bid.free_list.push(head_idx);
189                                                orders_touched += 1;
190                                                if let Some(next_order_idx) = next {
191                                                    price_level.head = Some(next_order_idx);
192                                                } else {
193                                                    span.record("reason", "exhausted");
194                                                    price_level.total_quantity = 0;
195                                                    price_level.head = None;
196                                                    price_level.tail = None;
197                                                    price_level.order_count = 0;
198                                                    break;
199                                                }
200                                            } else {
201                                                first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
202                                                price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
203                                                fill_quantity = 0;
204                                                orders_touched += 1;
205                                                span.record("filled", true);
206                                            }
207                                        }
208                                        None => {
209                                            return Err(anyhow!("failed to get head_idx from order pool"));
210                                        }
211                                    };
212                                }else {
213                                    // price level has no head. i.e head = None
214                                    break;
215                                }
216                            }
217                            remove_node = price_level.total_quantity == 0;
218                        }
219                        if remove_node {
220                            match orderbook.bid.price_map.pop_last(){
221                                Some(_) => {
222                                    levels_consumed += 1;
223                                }
224                                None => {
225                                    break;
226                                }
227                            };
228                        }
229                    }
230                    span.record("order_type", "market");
231                    span.record("is_buy_side", false);
232                    span.record("levels_consumed", levels_consumed);
233                    span.record("orders_touched", orders_touched);
234                    Ok(None)
235                }
236                OrderType::Market(market_limit) => {
237                    let mut fill_quantity = order.initial_quantity;
238                    let mut levels_consumed = 0;
239                    let mut orders_touched = 0;
240                    while fill_quantity > 0 {
241                        let remove_node: bool;
242                        {
243                            let Some(mut price_node) = orderbook.bid.price_map.last_entry() else {
244                                break;
245                            };
246
247                            match market_limit {
248                                Some(price) => {
249                                    if price > *price_node.key(){
250                                        break;
251                                    }
252                                }
253                                None => {
254                                    return Err(anyhow!("did not recieve price for market-limit(SELL)"))
255                                }
256                            }
257                            let price_level = price_node.get_mut();
258                            while price_level.total_quantity > 0 && fill_quantity > 0 {
259                                if let Some(head_idx) = price_level.head{
260
261                                    match orderbook.bid.order_pool[head_idx].as_mut(){
262                                        Some(first_order_node) => {
263
264                                            if fill_quantity >= first_order_node.current_quantity {
265                                                fill_quantity -= first_order_node.current_quantity;
266                                                price_level.total_quantity = price_level.total_quantity.checked_sub(first_order_node.current_quantity).ok_or(anyhow!("error occured in sub of total qty - current qyt"))?;
267                                                let next = first_order_node.next;
268                                                orderbook.bid.order_pool[head_idx] = None;
269                                                orderbook.bid.free_list.push(head_idx);
270                                                orders_touched += 1;
271                                                if let Some(next_order_idx) = next {
272                                                    price_level.head = Some(next_order_idx);
273                                                } else {
274                                                    span.record("reason", "exhausted");
275                                                    price_level.total_quantity = 0;
276                                                    price_level.head = None;
277                                                    price_level.tail = None;
278                                                    price_level.order_count = 0;
279                                                    break;
280                                                }
281                                            } else {
282                                                first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
283                                                price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
284                                                fill_quantity = 0;
285                                                orders_touched += 1;
286                                                span.record("filled", true);
287                                            }
288                                        }
289                                        None => {
290                                            return Err(anyhow!("failed to get head_idx from order pool"));
291                                        }
292                                    };
293                                }else {
294                                    break;
295                                }
296                            }
297                            remove_node = price_level.total_quantity == 0;
298                        }
299                        if remove_node {
300                            match orderbook.bid.price_map.pop_last(){
301                                Some(_) => {
302                                    levels_consumed += 1;
303                                }
304                                None => {
305                                    break;
306                                }
307                            };
308                        }
309                    }
310                    span.record("order_type", "market");
311                    span.record("is_buy_side", false);
312                    span.record("levels_consumed", levels_consumed);
313                    span.record("orders_touched", orders_touched);
314                    Ok(None)
315                }
316                OrderType::Limit => {
317                    let mut fill_quantity = order.initial_quantity;
318                    let mut levels_consumed = 0;
319                    let mut orders_touched = 0;
320                    while fill_quantity > 0 {
321                        let remove_node: bool;
322                        {
323                            let Some(mut price_node) = orderbook.bid.price_map.last_entry() else {
324                                break;
325                            };
326
327                            match order.price {
328                                Some(price) => {
329                                    if price > *price_node.key(){
330                                        break;
331                                    }
332                                }
333                                None => {
334                                    return Err(anyhow!("did not recieve price for limit order (SELL)"))
335                                }
336                            }
337                            let price_level = price_node.get_mut();
338                            while price_level.total_quantity > 0 && fill_quantity > 0 {
339                                if let Some(head_idx) = price_level.head{
340                                    match orderbook.bid.order_pool[head_idx].as_mut(){
341                                        Some(first_order_node) => {
342                                            if fill_quantity >= first_order_node.current_quantity {
343                                        fill_quantity -= first_order_node.current_quantity;
344                                        price_level.total_quantity = price_level.total_quantity.checked_sub(first_order_node.current_quantity).ok_or(anyhow!("error occured in sub of total qty - current qyt"))?;
345                                        let next = first_order_node.next;
346                                        orderbook.bid.order_pool[head_idx] = None;
347                                        orderbook.bid.free_list.push(head_idx);
348                                        orders_touched += 1;
349                                        if let Some(next_order_idx) = next {
350                                            price_level.head = Some(next_order_idx);
351                                        } else {
352                                            span.record("reason", "partially_filled");
353                                            price_level.total_quantity = 0;
354                                            price_level.head = None;
355                                            price_level.tail = None;
356                                            price_level.order_count = 0;
357                                            break;
358                                        }
359                                    } else {
360                                        first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
361                                        price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
362                                        fill_quantity = 0;
363                                        orders_touched += 1;
364                                        span.record("filled", true);
365                                    }
366                                        }
367                                        None => {
368                                            return Err(anyhow!("failed to get head_idx from order pool"));
369                                        }
370                                    };
371                                }else {
372                                    break;
373                                }
374                            }
375                            remove_node = price_level.total_quantity == 0;
376                        }
377                        if remove_node {
378                            match orderbook.bid.price_map.pop_last(){
379                                Some(_) => {
380                                    levels_consumed += 1;
381                                }
382                                None => {
383                                    break;
384                                }
385                            };
386                        }
387                    }
388                    if fill_quantity > 0 {
389                        let alloted_index = orderbook.create_sell_order(
390                            order.engine_order_id,
391                            OrderNode {
392                                order_id : order.engine_order_id,
393                                initial_quantity: order.initial_quantity,
394                                current_quantity: fill_quantity,
395                                market_limit: order.price.unwrap(),
396                                next: None,
397                                prev: None,
398                            },
399                        )?;
400                        span.record("order_type", "limit");
401                        span.record("is_buy_side", false);
402                        span.record("levels_consumed", levels_consumed);
403                        span.record("orders_touched", orders_touched);
404                        return Ok(Some(alloted_index))
405                    }
406                    Ok(None)
407                }
408            }
409        } else {
410            match order.order_type {
411                OrderType::Market(None) => {
412                    // need to immediatly execute the order on the best of other half
413                    let mut fill_quantity = order.initial_quantity;
414                    let mut levels_consumed = 0;
415                    let mut orders_touched = 0;
416                    while fill_quantity > 0 {
417                        let remove_node: bool;
418                        {
419                            let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
420                                break;
421                            };
422                            let price_level = price_node.get_mut();
423                            while price_level.total_quantity > 0 && fill_quantity > 0 {
424                                if let Some(head_idx) = price_level.head{
425                                    match orderbook.ask.order_pool[head_idx].as_mut(){
426                                        Some(first_order_node) => {
427
428                                            if fill_quantity >= first_order_node.current_quantity {
429                                                fill_quantity -= first_order_node.current_quantity;
430                                                price_level.total_quantity = price_level.total_quantity.checked_sub(first_order_node.current_quantity).ok_or(anyhow!("error occured in sub of total qty - current qyt"))?;
431                                                let next = first_order_node.next;
432                                                orderbook.ask.order_pool[head_idx] = None;
433                                                orderbook.ask.free_list.push(head_idx);
434                                                orders_touched += 1;
435                                                if let Some(next_order_idx) = next {
436                                                    price_level.head = Some(next_order_idx);
437                                                } else {
438                                                    span.record("reason", "exhausted");
439                                                    price_level.total_quantity = 0;
440                                                    price_level.head = None;
441                                                    price_level.tail = None;
442                                                    price_level.order_count = 0;
443                                                    break;
444                                                }
445                                            } else {
446                                                first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
447                                                price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
448                                                fill_quantity = 0;
449                                                orders_touched += 1;
450                                                span.record("filled", true);
451                                            }
452                                        }
453                                        None => {
454                                            return Err(anyhow!("failed to get head_idx from order pool"));
455                                        }
456                                    };
457                                }
458                                else {
459                                    break;
460                                }
461                            }
462                            remove_node = price_level.total_quantity == 0;
463                        }
464                        if remove_node {
465                            match orderbook.ask.price_map.pop_first(){
466                                Some(_) => {
467                                    levels_consumed += 1;
468                                }
469                                None => {
470                                    break;
471                                }
472                            };
473                        }
474                    }
475                    span.record("order_type", "market");
476                    span.record("is_buy_side", true);
477                    span.record("levels_consumed", levels_consumed);
478                    span.record("orders_touched", orders_touched);
479                    Ok(None)
480                }
481                OrderType::Market(market_limit) => {
482                    let mut fill_quantity = order.initial_quantity;
483                    let mut levels_consumed = 0;
484                    let mut orders_touched = 0;
485                    while fill_quantity > 0 {
486                        let remove_node: bool;
487                        {
488                            let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
489                                break;
490                            };
491
492                            match market_limit {
493                                Some(price) => {
494                                    if price < *price_node.key(){
495                                        break;
496                                    }
497                                }
498                                None => {
499                                    return Err(anyhow!("did not recieve price for market-limit(BUY)"))
500                                }
501                            }
502                            let price_level = price_node.get_mut();
503                            while price_level.total_quantity > 0 && fill_quantity > 0 {
504                                let head_pointer = price_level.head;
505                                if let Some(head_idx) = head_pointer{
506                                    match orderbook.ask.order_pool[head_idx].as_mut(){
507                                        Some(first_order_node) => {
508
509                                            if fill_quantity >= first_order_node.current_quantity {
510                                                fill_quantity -= first_order_node.current_quantity;
511                                                price_level.total_quantity = price_level.total_quantity.checked_sub(first_order_node.current_quantity).ok_or(anyhow!("error occured in sub of total qty - current qyt"))?;
512                                                let next = first_order_node.next;
513                                                orderbook.ask.order_pool[head_idx] = None;
514                                                orderbook.ask.free_list.push(head_idx);
515                                                orders_touched += 1;
516                                                if let Some(next_order_idx) = next {
517                                                    price_level.head = Some(next_order_idx);
518                                                } else {
519                                                    span.record("reason", "exhausted");
520                                                    price_level.head = None;
521                                                    price_level.total_quantity = 0;
522                                                    price_level.head = None;
523                                                    price_level.tail = None;
524                                                    price_level.order_count = 0;
525                                                    break;
526                                                }
527                                            } else {
528                                                first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
529                                                price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
530                                                fill_quantity = 0;
531                                                orders_touched += 1;
532                                                span.record("filled", true);
533                                            }
534                                        }
535                                        None => {
536                                            return Err(anyhow!("failed to get head_idx from order pool"));
537                                        }
538                                    };
539                                }
540                                else {
541                                    break;
542                                }
543                            }
544                            remove_node = price_level.total_quantity == 0;
545                        }
546                        if remove_node {
547                            match orderbook.ask.price_map.pop_first(){
548                                Some(_) => {
549                                    levels_consumed += 1;
550                                }
551                                None => {
552                                    break;
553                                }
554                            };
555                        }
556                    }
557                    span.record("order_type", "market");
558                    span.record("is_buy_side", true);
559                    span.record("levels_consumed", levels_consumed);
560                    span.record("orders_touched", orders_touched);
561                    Ok(None)
562                }
563                OrderType::Limit => {
564                    let mut fill_quantity = order.initial_quantity;
565                    let mut levels_consumed = 0;
566                    let mut orders_touched = 0;
567                    while fill_quantity > 0 {
568                        let remove_node: bool;
569                        {
570                            let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
571                                break;
572                            };
573
574                            match order.price {
575                                Some(price) => {
576                                    if price < *price_node.key(){
577                                        break;
578                                    }
579                                }
580                                None => {
581                                    return Err(anyhow!("did not recieve price for limit(BUY)"))
582                                }
583                            }
584                            let price_level = price_node.get_mut();
585                            while price_level.total_quantity > 0 && fill_quantity > 0 {
586                                if let Some(head_idx) = price_level.head{
587
588                                    match orderbook.ask.order_pool[head_idx].as_mut(){
589                                        Some(first_order_node) => {
590
591                                            if fill_quantity >= first_order_node.current_quantity {
592                                                fill_quantity -= first_order_node.current_quantity;
593                                                price_level.total_quantity = price_level.total_quantity.checked_sub(first_order_node.current_quantity).ok_or(anyhow!("error occured in sub of total qty - current qyt"))?;
594                                                let next = first_order_node.next;
595                                                orderbook.ask.order_pool[head_idx] = None;
596                                                orderbook.ask.free_list.push(head_idx);
597                                                orders_touched += 1;
598                                                if let Some(next_order_idx) = next {
599                                                    price_level.head = Some(next_order_idx);
600                                                } else {
601                                                    span.record("reason", "partially_filled");
602                                                    price_level.total_quantity = 0;
603                                                    price_level.head = None;
604                                                    price_level.tail = None;
605                                                    price_level.order_count = 0;
606                                                    break;
607                                                }
608                                            } else {
609                                                first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
610                                                price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
611                                                fill_quantity = 0;
612                                                orders_touched += 1;
613                                                span.record("filled", true);
614                                            }
615                                        }
616                                        None => {
617                                            return Err(anyhow!("failed to get head_idx from order pool"));
618                                        }
619                                    };
620                                }else {
621                                    break;
622                                }
623                            }
624                            remove_node = price_level.total_quantity == 0;
625                        }
626                        if remove_node {
627                            match orderbook.ask.price_map.pop_first(){
628                                Some(_) => {
629                                    levels_consumed += 1;
630                                }
631                                None => {
632                                    break;
633                                }
634                            };
635                        }
636                    }
637                    if fill_quantity > 0{
638                        let alloted_index = orderbook.create_buy_order(
639                            order.engine_order_id,
640                            OrderNode {
641                                order_id : order.engine_order_id,
642                                initial_quantity: order.initial_quantity,
643                                current_quantity: fill_quantity,
644                                market_limit: order.price.unwrap(),
645                                next: None,
646                                prev: None,
647                            },
648                        )?;
649                        span.record("order_type", "limit");
650                        span.record("is_buy_side", true);
651                        span.record("levels_consumed", levels_consumed);
652                        span.record("orders_touched", orders_touched);
653                        return Ok(Some(alloted_index))
654                    }
655                    Ok(None)
656                }
657            }
658        }
659    }
660}