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