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