1use crate::order_book::{
2 orderbook::OrderBook, types::{
3 BookDepth, CancelOutcome, EngineCancelOrder, EngineModifyOrder, EngineNewOrder, MatchOutcome, ModifyOutcome, 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<MatchOutcome, 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 match order.order_type {
164 OrderType::Market(None) => {
165 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 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(MatchOutcome{
235 order_index : None,
236 levels_consumed,
237 orders_touched
238 })
239 }
240 OrderType::Market(market_limit) => {
241 let mut fill_quantity = order.initial_quantity;
242 let mut levels_consumed = 0;
243 let mut orders_touched = 0;
244 while fill_quantity > 0 {
245 let remove_node: bool;
246 {
247 let Some(mut price_node) = orderbook.bid.price_map.last_entry() else {
248 break;
249 };
250
251 match market_limit {
252 Some(price) => {
253 if price > *price_node.key(){
254 break;
255 }
256 }
257 None => {
258 return Err(anyhow!("did not recieve price for market-limit(SELL)"))
259 }
260 }
261 let price_level = price_node.get_mut();
262 while price_level.total_quantity > 0 && fill_quantity > 0 {
263 if let Some(head_idx) = price_level.head{
264
265 match orderbook.bid.order_pool[head_idx].as_mut(){
266 Some(first_order_node) => {
267
268 if fill_quantity >= first_order_node.current_quantity {
269 fill_quantity -= first_order_node.current_quantity;
270 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"))?;
271 let next = first_order_node.next;
272 orderbook.bid.order_pool[head_idx] = None;
273 orderbook.bid.free_list.push(head_idx);
274 orders_touched += 1;
275 if let Some(next_order_idx) = next {
276 price_level.head = Some(next_order_idx);
277 } else {
278 span.record("reason", "exhausted");
279 price_level.total_quantity = 0;
280 price_level.head = None;
281 price_level.tail = None;
282 price_level.order_count = 0;
283 break;
284 }
285 } else {
286 first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
287 price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
288 fill_quantity = 0;
289 orders_touched += 1;
290 span.record("filled", true);
291 }
292 }
293 None => {
294 return Err(anyhow!("failed to get head_idx from order pool"));
295 }
296 };
297 }else {
298 break;
299 }
300 }
301 remove_node = price_level.total_quantity == 0;
302 }
303 if remove_node {
304 match orderbook.bid.price_map.pop_last(){
305 Some(_) => {
306 levels_consumed += 1;
307 }
308 None => {
309 break;
310 }
311 };
312 }
313 }
314 span.record("order_type", "market");
315 span.record("is_buy_side", false);
316 span.record("levels_consumed", levels_consumed);
317 span.record("orders_touched", orders_touched);
318 Ok(MatchOutcome{
319 order_index : None,
320 levels_consumed,
321 orders_touched
322 })
323 }
324 OrderType::Limit => {
325 let mut fill_quantity = order.initial_quantity;
326 let mut levels_consumed = 0;
327 let mut orders_touched = 0;
328 while fill_quantity > 0 {
329 let remove_node: bool;
330 {
331 let Some(mut price_node) = orderbook.bid.price_map.last_entry() else {
332 break;
333 };
334
335 match order.price {
336 Some(price) => {
337 if price > *price_node.key(){
338 break;
339 }
340 }
341 None => {
342 return Err(anyhow!("did not recieve price for limit order (SELL)"))
343 }
344 }
345 let price_level = price_node.get_mut();
346 while price_level.total_quantity > 0 && fill_quantity > 0 {
347 if let Some(head_idx) = price_level.head{
348 match orderbook.bid.order_pool[head_idx].as_mut(){
349 Some(first_order_node) => {
350 if fill_quantity >= first_order_node.current_quantity {
351 fill_quantity -= first_order_node.current_quantity;
352 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"))?;
353 let next = first_order_node.next;
354 orderbook.bid.order_pool[head_idx] = None;
355 orderbook.bid.free_list.push(head_idx);
356 orders_touched += 1;
357 if let Some(next_order_idx) = next {
358 price_level.head = Some(next_order_idx);
359 } else {
360 span.record("reason", "partially_filled");
361 price_level.total_quantity = 0;
362 price_level.head = None;
363 price_level.tail = None;
364 price_level.order_count = 0;
365 break;
366 }
367 } else {
368 first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
369 price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
370 fill_quantity = 0;
371 orders_touched += 1;
372 span.record("filled", true);
373 }
374 }
375 None => {
376 return Err(anyhow!("failed to get head_idx from order pool"));
377 }
378 };
379 }else {
380 break;
381 }
382 }
383 remove_node = price_level.total_quantity == 0;
384 }
385 if remove_node {
386 match orderbook.bid.price_map.pop_last(){
387 Some(_) => {
388 levels_consumed += 1;
389 }
390 None => {
391 break;
392 }
393 };
394 }
395 }
396 if fill_quantity > 0 {
397 let alloted_index = orderbook.create_sell_order(
398 order.engine_order_id,
399 OrderNode {
400 order_id : order.engine_order_id,
401 initial_quantity: order.initial_quantity,
402 current_quantity: fill_quantity,
403 market_limit: order.price.unwrap(),
404 next: None,
405 prev: None,
406 },
407 )?;
408 span.record("order_type", "limit");
409 span.record("is_buy_side", false);
410 span.record("levels_consumed", levels_consumed);
411 span.record("orders_touched", orders_touched);
412 return Ok(MatchOutcome{
413 order_index : Some(alloted_index as u32),
414 levels_consumed,
415 orders_touched
416 })
417 }
418 Ok(MatchOutcome{
419 order_index : None,
420 levels_consumed,
421 orders_touched
422 })
423 }
424 }
425 } else {
426 match order.order_type {
427 OrderType::Market(None) => {
428 let mut fill_quantity = order.initial_quantity;
430 let mut levels_consumed = 0;
431 let mut orders_touched = 0;
432 while fill_quantity > 0 {
433 let remove_node: bool;
434 {
435 let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
436 break;
437 };
438 let price_level = price_node.get_mut();
439 while price_level.total_quantity > 0 && fill_quantity > 0 {
440 if let Some(head_idx) = price_level.head{
441 match orderbook.ask.order_pool[head_idx].as_mut(){
442 Some(first_order_node) => {
443
444 if fill_quantity >= first_order_node.current_quantity {
445 fill_quantity -= first_order_node.current_quantity;
446 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"))?;
447 let next = first_order_node.next;
448 orderbook.ask.order_pool[head_idx] = None;
449 orderbook.ask.free_list.push(head_idx);
450 orders_touched += 1;
451 if let Some(next_order_idx) = next {
452 price_level.head = Some(next_order_idx);
453 } else {
454 span.record("reason", "exhausted");
455 price_level.total_quantity = 0;
456 price_level.head = None;
457 price_level.tail = None;
458 price_level.order_count = 0;
459 break;
460 }
461 } else {
462 first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
463 price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
464 fill_quantity = 0;
465 orders_touched += 1;
466 span.record("filled", true);
467 }
468 }
469 None => {
470 return Err(anyhow!("failed to get head_idx from order pool"));
471 }
472 };
473 }
474 else {
475 break;
476 }
477 }
478 remove_node = price_level.total_quantity == 0;
479 }
480 if remove_node {
481 match orderbook.ask.price_map.pop_first(){
482 Some(_) => {
483 levels_consumed += 1;
484 }
485 None => {
486 break;
487 }
488 };
489 }
490 }
491 span.record("order_type", "market");
492 span.record("is_buy_side", true);
493 span.record("levels_consumed", levels_consumed);
494 span.record("orders_touched", orders_touched);
495 Ok(MatchOutcome{
496 order_index : None,
497 levels_consumed,
498 orders_touched
499 })
500 }
501 OrderType::Market(market_limit) => {
502 let mut fill_quantity = order.initial_quantity;
503 let mut levels_consumed = 0;
504 let mut orders_touched = 0;
505 while fill_quantity > 0 {
506 let remove_node: bool;
507 {
508 let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
509 break;
510 };
511
512 match market_limit {
513 Some(price) => {
514 if price < *price_node.key(){
515 break;
516 }
517 }
518 None => {
519 return Err(anyhow!("did not recieve price for market-limit(BUY)"))
520 }
521 }
522 let price_level = price_node.get_mut();
523 while price_level.total_quantity > 0 && fill_quantity > 0 {
524 let head_pointer = price_level.head;
525 if let Some(head_idx) = head_pointer{
526 match orderbook.ask.order_pool[head_idx].as_mut(){
527 Some(first_order_node) => {
528
529 if fill_quantity >= first_order_node.current_quantity {
530 fill_quantity -= first_order_node.current_quantity;
531 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"))?;
532 let next = first_order_node.next;
533 orderbook.ask.order_pool[head_idx] = None;
534 orderbook.ask.free_list.push(head_idx);
535 orders_touched += 1;
536 if let Some(next_order_idx) = next {
537 price_level.head = Some(next_order_idx);
538 } else {
539 span.record("reason", "exhausted");
540 price_level.head = None;
541 price_level.total_quantity = 0;
542 price_level.head = None;
543 price_level.tail = None;
544 price_level.order_count = 0;
545 break;
546 }
547 } else {
548 first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
549 price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
550 fill_quantity = 0;
551 orders_touched += 1;
552 span.record("filled", true);
553 }
554 }
555 None => {
556 return Err(anyhow!("failed to get head_idx from order pool"));
557 }
558 };
559 }
560 else {
561 break;
562 }
563 }
564 remove_node = price_level.total_quantity == 0;
565 }
566 if remove_node {
567 match orderbook.ask.price_map.pop_first(){
568 Some(_) => {
569 levels_consumed += 1;
570 }
571 None => {
572 break;
573 }
574 };
575 }
576 }
577 span.record("order_type", "market");
578 span.record("is_buy_side", true);
579 span.record("levels_consumed", levels_consumed);
580 span.record("orders_touched", orders_touched);
581 Ok(MatchOutcome{
582 order_index : None,
583 levels_consumed,
584 orders_touched
585 })
586 }
587 OrderType::Limit => {
588 let mut fill_quantity = order.initial_quantity;
589 let mut levels_consumed = 0;
590 let mut orders_touched = 0;
591 while fill_quantity > 0 {
592 let remove_node: bool;
593 {
594 let Some(mut price_node) = orderbook.ask.price_map.first_entry() else {
595 break;
596 };
597
598 match order.price {
599 Some(price) => {
600 if price < *price_node.key(){
601 break;
602 }
603 }
604 None => {
605 return Err(anyhow!("did not recieve price for limit(BUY)"))
606 }
607 }
608 let price_level = price_node.get_mut();
609 while price_level.total_quantity > 0 && fill_quantity > 0 {
610 if let Some(head_idx) = price_level.head{
611
612 match orderbook.ask.order_pool[head_idx].as_mut(){
613 Some(first_order_node) => {
614
615 if fill_quantity >= first_order_node.current_quantity {
616 fill_quantity -= first_order_node.current_quantity;
617 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"))?;
618 let next = first_order_node.next;
619 orderbook.ask.order_pool[head_idx] = None;
620 orderbook.ask.free_list.push(head_idx);
621 orders_touched += 1;
622 if let Some(next_order_idx) = next {
623 price_level.head = Some(next_order_idx);
624 } else {
625 span.record("reason", "partially_filled");
626 price_level.total_quantity = 0;
627 price_level.head = None;
628 price_level.tail = None;
629 price_level.order_count = 0;
630 break;
631 }
632 } else {
633 first_order_node.current_quantity = first_order_node.current_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fnq - fq"))?;
634 price_level.total_quantity = price_level.total_quantity.checked_sub(fill_quantity).ok_or(anyhow!("error occured subtracting fntq - fq"))?;
635 fill_quantity = 0;
636 orders_touched += 1;
637 span.record("filled", true);
638 }
639 }
640 None => {
641 return Err(anyhow!("failed to get head_idx from order pool"));
642 }
643 };
644 }else {
645 break;
646 }
647 }
648 remove_node = price_level.total_quantity == 0;
649 }
650 if remove_node {
651 match orderbook.ask.price_map.pop_first(){
652 Some(_) => {
653 levels_consumed += 1;
654 }
655 None => {
656 break;
657 }
658 };
659 }
660 }
661 if fill_quantity > 0{
662 let alloted_index = orderbook.create_buy_order(
663 order.engine_order_id,
664 OrderNode {
665 order_id : order.engine_order_id,
666 initial_quantity: order.initial_quantity,
667 current_quantity: fill_quantity,
668 market_limit: order.price.unwrap(),
669 next: None,
670 prev: None,
671 },
672 )?;
673 span.record("order_type", "limit");
674 span.record("is_buy_side", true);
675 span.record("levels_consumed", levels_consumed);
676 span.record("orders_touched", orders_touched);
677 return Ok(MatchOutcome{
678 order_index : Some(alloted_index as u32),
679 levels_consumed,
680 orders_touched
681 })
682 }
683 Ok(MatchOutcome{
684 order_index : None,
685 levels_consumed,
686 orders_touched
687 })
688 }
689 }
690 }
691 }
692}