1use crate::order_book::{
2 orderbook::OrderBook,
3 types::{
4 CancelOrder, CancelOutcome, GlobalOrderRegistry, ModifyOrder, ModifyOutcome, NewOrder, OrderLocation, OrderNode, OrderType
5 },
6};
7use anyhow::{Context};
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 _gaurd = span.enter();
164
165 let orderbook = match self._book.get_mut(&order.security_id){
166 Some(orderbook) => {
167 orderbook
168 }
169 None => {
170 self._book.entry(order.security_id).or_insert(OrderBook::new(1))
171 }
172 };
173
174 if !order.is_buy_side {
175 match order.order_type {
177 OrderType::Market(None) => {
178 let mut fill_quantity = order.initial_quantity;
180 let mut levels_touched = 0;
181 let mut orders_consumed = 0;
182 while fill_quantity > 0 {
183 let remove_node: bool;
184 {
185 let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
186 break;
187 };
188 let price_level = price_node.get_mut();
189 while price_level.total_quantity > 0 && fill_quantity > 0 {
190 let head_idx = price_level.head;
191 let first_order_node =
192 orderbook.bid.order_pool[head_idx].as_mut().unwrap();
193 if fill_quantity >= first_order_node.current_quantity {
194 fill_quantity -= first_order_node.current_quantity;
195 price_level.total_quantity -= first_order_node.current_quantity;
196 let next = first_order_node.next;
197 orderbook.bid.order_pool[head_idx] = None;
198 orderbook.bid.free_list.push(head_idx);
199 orders_consumed += 1;
200 if let Some(next_order_idx) = next {
201 price_level.head = next_order_idx;
202 } else {
203 span.record("reason", "exhausted");
204 break;
205 }
206 } else {
207 first_order_node.current_quantity -= fill_quantity;
208 price_level.total_quantity -= fill_quantity;
209 fill_quantity = 0;
210 span.record("filled", true);
211 }
212 }
213 remove_node = price_level.total_quantity == 0;
214 }
215 if remove_node {
216 orderbook.bid.price_map.pop_first();
217 levels_touched += 1;
218 }
219 }
220 span.record("order_type", "market");
221 span.record("is_buy_side", false);
222 span.record("levels_touched", levels_touched);
223 span.record("orders_consumed", orders_consumed);
224 }
225 OrderType::Market(market_limit) => {
226 let mut fill_quantity = order.initial_quantity;
227 let mut levels_touched = 0;
228 let mut orders_consumed = 0;
229 while fill_quantity > 0 {
230 let remove_node: bool;
231 {
232 let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
233 break;
234 };
235 if market_limit.unwrap() >= *price_node.key() {
236 break;
237 }
238 let price_level = price_node.get_mut();
239 while price_level.total_quantity > 0 && fill_quantity > 0 {
240 let head_idx = price_level.head;
241 let first_order_node =
242 orderbook.bid.order_pool[head_idx].as_mut().unwrap();
243 if fill_quantity >= first_order_node.current_quantity {
244 fill_quantity -= first_order_node.current_quantity;
245 price_level.total_quantity -= first_order_node.current_quantity;
246 let next = first_order_node.next;
247 orderbook.bid.order_pool[head_idx] = None;
248 orderbook.bid.free_list.push(head_idx);
249 orders_consumed += 1;
250 if let Some(next_order_idx) = next {
251 price_level.head = next_order_idx;
252 } else {
253 span.record("reason", "exhausted");
254 break;
255 }
256 } else {
257 first_order_node.current_quantity -= fill_quantity;
258 price_level.total_quantity -= fill_quantity;
259 fill_quantity = 0;
260 span.record("filled", true);
261 }
262 }
263 remove_node = price_level.total_quantity == 0;
264 }
265 if remove_node {
266 orderbook.bid.price_map.pop_first();
267 levels_touched += 1;
268 }
269 }
270 span.record("order_type", "market");
271 span.record("is_buy_side", false);
272 span.record("levels_touched", levels_touched);
273 span.record("orders_consumed", orders_consumed);
274 }
275 OrderType::Limit => {
276 let mut fill_quantity = order.initial_quantity;
277 let mut levels_touched = 0;
278 let mut orders_consumed = 0;
279 while fill_quantity > 0 {
280 let remove_node: bool;
281 {
282 let Some(mut price_node) = orderbook.bid.price_map.first_entry() else {
283 break;
284 };
285 if order.price >= Some(*price_node.key()) {
286 break;
287 }
288 let price_level = price_node.get_mut();
289 while price_level.total_quantity > 0 && fill_quantity > 0 {
290 let head_idx = price_level.head;
291 let first_order_node =
292 orderbook.bid.order_pool[head_idx].as_mut().unwrap();
293 if fill_quantity >= first_order_node.current_quantity {
294 fill_quantity -= first_order_node.current_quantity;
295 price_level.total_quantity -= first_order_node.current_quantity;
296 let next = first_order_node.next;
297 orderbook.bid.order_pool[head_idx] = None;
298 orderbook.bid.free_list.push(head_idx);
299 orders_consumed += 1;
300 if let Some(next_order_idx) = next {
301 price_level.head = next_order_idx;
302 } else {
303 span.record("reason", "partially_filled");
304 break;
305 }
306 } else {
307 first_order_node.current_quantity -= fill_quantity;
308 price_level.total_quantity -= fill_quantity;
309 fill_quantity = 0;
310 }
311 }
312 remove_node = price_level.total_quantity == 0;
313 }
314 if remove_node {
315 orderbook.bid.price_map.pop_first();
316 levels_touched += 1;
317 }
318 }
319 let alloted_index = orderbook.create_sell_order(
320 order.engine_order_id,
321 OrderNode {
322 initial_quantity: order.initial_quantity,
323 current_quantity: fill_quantity,
324 market_limit: order.price.unwrap(),
325 next: None,
326 prev: None,
327 },
328 )?;
329 let order_location = OrderLocation {
330 security_id : order.security_id,
331 is_buy_side : order.is_buy_side,
332 order_index : alloted_index
333 };
334 self._global_registry.insert(order.engine_order_id, order_location);
335 span.record("order_type", "limit");
336 span.record("is_buy_side", false);
337 span.record("levels_touched", levels_touched);
338 span.record("orders_consumed", orders_consumed);
339 }
340 }
341 } else {
342 match order.order_type {
343 OrderType::Market(None) => {
344 let mut fill_quantity = order.initial_quantity;
346 let mut levels_touched = 0;
347 let mut orders_consumed = 0;
348 while fill_quantity > 0 {
349 let remove_node: bool;
350 {
351 let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
352 break;
353 };
354 let price_level = price_node.get_mut();
355 while price_level.total_quantity > 0 && fill_quantity > 0 {
356 let head_idx = price_level.head;
357 let first_order_node =
358 orderbook.ask.order_pool[head_idx].as_mut().unwrap();
359 if fill_quantity >= first_order_node.current_quantity {
360 fill_quantity -= first_order_node.current_quantity;
361 price_level.total_quantity -= first_order_node.current_quantity;
362 let next = first_order_node.next;
363 orderbook.ask.order_pool[head_idx] = None;
364 orderbook.ask.free_list.push(head_idx);
365 orders_consumed += 1;
366 if let Some(next_order_idx) = next {
367 price_level.head = next_order_idx;
368 } else {
369 span.record("reason", "exhausted");
370 break;
371 }
372 } else {
373 first_order_node.current_quantity -= fill_quantity;
374 price_level.total_quantity -= fill_quantity;
375 fill_quantity = 0;
376 span.record("filled", true);
377 }
378 }
379 remove_node = price_level.total_quantity == 0;
380 }
381 if remove_node {
382 orderbook.bid.price_map.pop_last();
383 levels_touched += 1;
384 }
385 }
386 span.record("order_type", "market");
387 span.record("is_buy_side", true);
388 span.record("levels_touched", levels_touched);
389 span.record("orders_consumed", orders_consumed);
390 }
391 OrderType::Market(market_limit) => {
392 let mut fill_quantity = order.initial_quantity;
393 let mut levels_touched = 0;
394 let mut orders_consumed = 0;
395 while fill_quantity > 0 {
396 let remove_node: bool;
397 {
398 let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
399 break;
400 };
401 if market_limit.unwrap() <= *price_node.key() {
402 break;
403 }
404 let price_level = price_node.get_mut();
405 while price_level.total_quantity > 0 && fill_quantity > 0 {
406 let head_idx = price_level.head;
407 let first_order_node =
408 orderbook.ask.order_pool[head_idx].as_mut().unwrap();
409 if fill_quantity >= first_order_node.current_quantity {
410 fill_quantity -= first_order_node.current_quantity;
411 price_level.total_quantity -= first_order_node.current_quantity;
412 let next = first_order_node.next;
413 orderbook.ask.order_pool[head_idx] = None;
414 orderbook.ask.free_list.push(head_idx);
415 orders_consumed += 1;
416 if let Some(next_order_idx) = next {
417 price_level.head = next_order_idx;
418 } else {
419 span.record("reason", "exhausted");
420 break;
421 }
422 } else {
423 first_order_node.current_quantity -= fill_quantity;
424 price_level.total_quantity -= fill_quantity;
425 fill_quantity = 0;
426 span.record("filled", true);
427 }
428 }
429 remove_node = price_level.total_quantity == 0;
430 }
431 if remove_node {
432 orderbook.bid.price_map.pop_last();
433 levels_touched += 1;
434 }
435 }
436 span.record("order_type", "market");
437 span.record("is_buy_side", true);
438 span.record("levels_touched", levels_touched);
439 span.record("orders_consumed", orders_consumed);
440 }
441 OrderType::Limit => {
442 let mut fill_quantity = order.initial_quantity;
443 let mut levels_touched = 0;
444 let mut orders_consumed = 0;
445 while fill_quantity > 0 {
446 let remove_node: bool;
447 {
448 let Some(mut price_node) = orderbook.ask.price_map.last_entry() else {
449 break;
450 };
451 if order.price <= Some(*price_node.key()) {
452 break;
453 }
454 let price_level = price_node.get_mut();
455 while price_level.total_quantity > 0 && fill_quantity > 0 {
456 let head_idx = price_level.head;
457 let first_order_node =
458 orderbook.ask.order_pool[head_idx].as_mut().unwrap();
459 if fill_quantity >= first_order_node.current_quantity {
460 fill_quantity -= first_order_node.current_quantity;
461 price_level.total_quantity -= first_order_node.current_quantity;
462 let next = first_order_node.next;
463 orderbook.ask.order_pool[head_idx] = None;
464 orderbook.ask.free_list.push(head_idx);
465 orders_consumed += 1;
466 if let Some(next_order_idx) = next {
467 price_level.head = next_order_idx;
468 } else {
469 span.record("reason", "partially_filled");
470 break;
471 }
472 } else {
473 first_order_node.current_quantity -= fill_quantity;
474 price_level.total_quantity -= fill_quantity;
475 fill_quantity = 0;
476 }
477 }
478 remove_node = price_level.total_quantity == 0;
479 }
480 if remove_node {
481 orderbook.bid.price_map.pop_last();
482 levels_touched += 1;
483 }
484 }
485 let alloted_index = orderbook.create_buy_order(
486 order.engine_order_id,
487 OrderNode {
488 initial_quantity: order.initial_quantity,
489 current_quantity: fill_quantity,
490 market_limit: order.price.unwrap(),
491 next: None,
492 prev: None,
493 },
494 )?;
495 let order_location = OrderLocation {
496 security_id : order.security_id,
497 is_buy_side : order.is_buy_side,
498 order_index : alloted_index
499 };
500 self._global_registry.insert(order.engine_order_id, order_location);
501 span.record("order_type", "limit");
502 span.record("is_buy_side", true);
503 span.record("levels_touched", levels_touched);
504 span.record("orders_consumed", orders_consumed);
505 }
506 }
507 }
508 Ok(())
509 }
510}