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 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(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 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}