1#![allow(dead_code)]
20#![allow(unused_variables)]
21
22pub mod handlers;
23
24use nautilus_model::{
25 enums::OrderSideSpecified,
26 identifiers::{ClientOrderId, InstrumentId},
27 orders::{LimitOrderAny, Order, OrderAny, OrderError, PassiveOrderAny, StopOrderAny},
28 types::Price,
29};
30
31use crate::matching_core::handlers::{
32 FillLimitOrderHandler, ShareableFillLimitOrderHandler, ShareableFillMarketOrderHandler,
33 ShareableTriggerStopOrderHandler, TriggerStopOrderHandler,
34};
35
36#[derive(Clone, Debug)]
38pub struct OrderMatchingCore {
39 pub instrument_id: InstrumentId,
41 pub price_increment: Price,
43 pub bid: Option<Price>,
45 pub ask: Option<Price>,
47 pub last: Option<Price>,
49 pub is_bid_initialized: bool,
50 pub is_ask_initialized: bool,
51 pub is_last_initialized: bool,
52 orders_bid: Vec<PassiveOrderAny>,
53 orders_ask: Vec<PassiveOrderAny>,
54 trigger_stop_order: Option<ShareableTriggerStopOrderHandler>,
55 fill_market_order: Option<ShareableFillMarketOrderHandler>,
56 fill_limit_order: Option<ShareableFillLimitOrderHandler>,
57}
58
59impl OrderMatchingCore {
60 #[must_use]
62 pub const fn new(
63 instrument_id: InstrumentId,
64 price_increment: Price,
65 trigger_stop_order: Option<ShareableTriggerStopOrderHandler>,
66 fill_market_order: Option<ShareableFillMarketOrderHandler>,
67 fill_limit_order: Option<ShareableFillLimitOrderHandler>,
68 ) -> Self {
69 Self {
70 instrument_id,
71 price_increment,
72 bid: None,
73 ask: None,
74 last: None,
75 is_bid_initialized: false,
76 is_ask_initialized: false,
77 is_last_initialized: false,
78 orders_bid: Vec::new(),
79 orders_ask: Vec::new(),
80 trigger_stop_order,
81 fill_market_order,
82 fill_limit_order,
83 }
84 }
85
86 pub fn set_fill_limit_order_handler(&mut self, handler: ShareableFillLimitOrderHandler) {
87 self.fill_limit_order = Some(handler);
88 }
89
90 pub fn set_trigger_stop_order_handler(&mut self, handler: ShareableTriggerStopOrderHandler) {
91 self.trigger_stop_order = Some(handler);
92 }
93
94 pub fn set_fill_market_order_handler(&mut self, handler: ShareableFillMarketOrderHandler) {
95 self.fill_market_order = Some(handler);
96 }
97
98 #[must_use]
101 pub const fn price_precision(&self) -> u8 {
102 self.price_increment.precision
103 }
104
105 #[must_use]
106 pub fn get_order(&self, client_order_id: ClientOrderId) -> Option<&PassiveOrderAny> {
107 self.orders_bid
108 .iter()
109 .find(|o| o.client_order_id() == client_order_id)
110 .or_else(|| {
111 self.orders_ask
112 .iter()
113 .find(|o| o.client_order_id() == client_order_id)
114 })
115 }
116
117 #[must_use]
118 pub const fn get_orders_bid(&self) -> &[PassiveOrderAny] {
119 self.orders_bid.as_slice()
120 }
121
122 #[must_use]
123 pub const fn get_orders_ask(&self) -> &[PassiveOrderAny] {
124 self.orders_ask.as_slice()
125 }
126
127 #[must_use]
128 pub fn get_orders(&self) -> Vec<PassiveOrderAny> {
129 let mut orders = self.orders_bid.clone();
130 orders.extend_from_slice(&self.orders_ask);
131 orders
132 }
133
134 #[must_use]
135 pub fn order_exists(&self, client_order_id: ClientOrderId) -> bool {
136 self.orders_bid
137 .iter()
138 .any(|o| o.client_order_id() == client_order_id)
139 || self
140 .orders_ask
141 .iter()
142 .any(|o| o.client_order_id() == client_order_id)
143 }
144
145 pub const fn set_last_raw(&mut self, last: Price) {
148 self.last = Some(last);
149 self.is_last_initialized = true;
150 }
151
152 pub const fn set_bid_raw(&mut self, bid: Price) {
153 self.bid = Some(bid);
154 self.is_bid_initialized = true;
155 }
156
157 pub const fn set_ask_raw(&mut self, ask: Price) {
158 self.ask = Some(ask);
159 self.is_ask_initialized = true;
160 }
161
162 pub fn reset(&mut self) {
163 self.bid = None;
164 self.ask = None;
165 self.last = None;
166 self.orders_bid.clear();
167 self.orders_ask.clear();
168 }
169
170 pub fn add_order(&mut self, order: PassiveOrderAny) -> Result<(), OrderError> {
176 match order.order_side_specified() {
177 OrderSideSpecified::Buy => {
178 self.orders_bid.push(order);
179 Ok(())
180 }
181 OrderSideSpecified::Sell => {
182 self.orders_ask.push(order);
183 Ok(())
184 }
185 }
186 }
187
188 pub fn delete_order(&mut self, order: &PassiveOrderAny) -> Result<(), OrderError> {
194 match order.order_side_specified() {
195 OrderSideSpecified::Buy => {
196 let index = self
197 .orders_bid
198 .iter()
199 .position(|o| o == order)
200 .ok_or(OrderError::NotFound(order.client_order_id()))?;
201 self.orders_bid.remove(index);
202 Ok(())
203 }
204 OrderSideSpecified::Sell => {
205 let index = self
206 .orders_ask
207 .iter()
208 .position(|o| o == order)
209 .ok_or(OrderError::NotFound(order.client_order_id()))?;
210 self.orders_ask.remove(index);
211 Ok(())
212 }
213 }
214 }
215
216 pub fn iterate(&mut self) {
217 self.iterate_bids();
218 self.iterate_asks();
219 }
220
221 pub fn iterate_bids(&mut self) {
222 let orders: Vec<_> = self.orders_bid.clone();
223 for order in &orders {
224 self.match_order(order, false);
225 }
226 }
227
228 pub fn iterate_asks(&mut self) {
229 let orders: Vec<_> = self.orders_ask.clone();
230 for order in &orders {
231 self.match_order(order, false);
232 }
233 }
234
235 fn iterate_orders(&mut self, orders: &[PassiveOrderAny]) {
236 for order in orders {
237 self.match_order(order, false);
238 }
239 }
240
241 pub fn match_order(&mut self, order: &PassiveOrderAny, _initial: bool) {
244 match order {
245 PassiveOrderAny::Limit(o) => self.match_limit_order(o),
246 PassiveOrderAny::Stop(o) => self.match_stop_order(o),
247 }
248 }
249
250 pub fn match_limit_order(&mut self, order: &LimitOrderAny) {
251 if self.is_limit_matched(order.order_side_specified(), order.limit_px())
252 && let Some(handler) = &mut self.fill_limit_order
253 {
254 handler
255 .0
256 .fill_limit_order(&mut OrderAny::from(order.clone()));
257 }
258 }
259
260 pub fn match_stop_order(&mut self, order: &StopOrderAny) {
261 match order {
262 StopOrderAny::TrailingStopMarket(o) if !o.is_activated => return,
263 StopOrderAny::TrailingStopLimit(o) if !o.is_activated => return,
264 _ => {}
265 }
266
267 if self.is_stop_matched(order.order_side_specified(), order.stop_px())
268 && let Some(handler) = &mut self.trigger_stop_order
269 {
270 handler
271 .0
272 .trigger_stop_order(&mut OrderAny::from(order.clone()));
273 }
274 }
275
276 #[must_use]
277 pub fn is_limit_matched(&self, side: OrderSideSpecified, price: Price) -> bool {
278 match side {
279 OrderSideSpecified::Buy => self.ask.is_some_and(|a| a <= price),
280 OrderSideSpecified::Sell => self.bid.is_some_and(|b| b >= price),
281 }
282 }
283
284 #[must_use]
285 pub fn is_stop_matched(&self, side: OrderSideSpecified, price: Price) -> bool {
286 match side {
287 OrderSideSpecified::Buy => self.ask.is_some_and(|a| a >= price),
288 OrderSideSpecified::Sell => self.bid.is_some_and(|b| b <= price),
289 }
290 }
291
292 #[must_use]
293 pub fn is_touch_triggered(&self, side: OrderSideSpecified, trigger_price: Price) -> bool {
294 match side {
295 OrderSideSpecified::Buy => self.ask.is_some_and(|a| a <= trigger_price),
296 OrderSideSpecified::Sell => self.bid.is_some_and(|b| b >= trigger_price),
297 }
298 }
299}
300
301#[cfg(test)]
305mod tests {
306 use nautilus_model::{
307 enums::{OrderSide, OrderType},
308 orders::{Order, builder::OrderTestBuilder},
309 types::Quantity,
310 };
311 use rstest::rstest;
312
313 use super::*;
314
315 const fn create_matching_core(
316 instrument_id: InstrumentId,
317 price_increment: Price,
318 ) -> OrderMatchingCore {
319 OrderMatchingCore::new(instrument_id, price_increment, None, None, None)
320 }
321
322 #[rstest]
323 fn test_add_order_bid_side() {
324 let instrument_id = InstrumentId::from("AAPL.XNAS");
325 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
326
327 let order = OrderTestBuilder::new(OrderType::Limit)
328 .instrument_id(instrument_id)
329 .side(OrderSide::Buy)
330 .price(Price::from("100.00"))
331 .quantity(Quantity::from("100"))
332 .build();
333
334 matching_core.add_order(order.clone().into()).unwrap();
335
336 let passive_order: PassiveOrderAny = order.into();
337 assert!(matching_core.get_orders_bid().contains(&passive_order));
338 assert!(!matching_core.get_orders_ask().contains(&passive_order));
339 assert_eq!(matching_core.get_orders_bid().len(), 1);
340 assert!(matching_core.get_orders_ask().is_empty());
341 assert!(matching_core.order_exists(passive_order.client_order_id()));
342 }
343
344 #[rstest]
345 fn test_add_order_ask_side() {
346 let instrument_id = InstrumentId::from("AAPL.XNAS");
347 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
348
349 let order = OrderTestBuilder::new(OrderType::Limit)
350 .instrument_id(instrument_id)
351 .side(OrderSide::Sell)
352 .price(Price::from("100.00"))
353 .quantity(Quantity::from("100"))
354 .build();
355
356 matching_core.add_order(order.clone().into()).unwrap();
357
358 let passive_order: PassiveOrderAny = order.into();
359 assert!(matching_core.get_orders_ask().contains(&passive_order));
360 assert!(!matching_core.get_orders_bid().contains(&passive_order));
361 assert_eq!(matching_core.get_orders_ask().len(), 1);
362 assert!(matching_core.get_orders_bid().is_empty());
363 assert!(matching_core.order_exists(passive_order.client_order_id()));
364 }
365
366 #[rstest]
367 fn test_reset() {
368 let instrument_id = InstrumentId::from("AAPL.XNAS");
369 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
370
371 let order = OrderTestBuilder::new(OrderType::Limit)
372 .instrument_id(instrument_id)
373 .side(OrderSide::Sell)
374 .price(Price::from("100.00"))
375 .quantity(Quantity::from("100"))
376 .build();
377
378 let client_order_id = order.client_order_id();
379
380 matching_core.add_order(order.into()).unwrap();
381 matching_core.bid = Some(Price::from("100.00"));
382 matching_core.ask = Some(Price::from("100.00"));
383 matching_core.last = Some(Price::from("100.00"));
384
385 matching_core.reset();
386
387 assert!(matching_core.bid.is_none());
388 assert!(matching_core.ask.is_none());
389 assert!(matching_core.last.is_none());
390 assert!(matching_core.get_orders_bid().is_empty());
391 assert!(matching_core.get_orders_ask().is_empty());
392 assert!(!matching_core.order_exists(client_order_id));
393 }
394
395 #[rstest]
396 fn test_delete_order_when_not_exists() {
397 let instrument_id = InstrumentId::from("AAPL.XNAS");
398 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
399
400 let order = OrderTestBuilder::new(OrderType::Limit)
401 .instrument_id(instrument_id)
402 .side(OrderSide::Buy)
403 .price(Price::from("100.00"))
404 .quantity(Quantity::from("100"))
405 .build();
406
407 let result = matching_core.delete_order(&order.into());
408 assert!(result.is_err());
409 }
410
411 #[rstest]
412 #[case(OrderSide::Buy)]
413 #[case(OrderSide::Sell)]
414 fn test_delete_order_when_exists(#[case] order_side: OrderSide) {
415 let instrument_id = InstrumentId::from("AAPL.XNAS");
416 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
417
418 let order = OrderTestBuilder::new(OrderType::Limit)
419 .instrument_id(instrument_id)
420 .side(order_side)
421 .price(Price::from("100.00"))
422 .quantity(Quantity::from("100"))
423 .build();
424
425 matching_core.add_order(order.clone().into()).unwrap();
426 matching_core.delete_order(&order.into()).unwrap();
427
428 assert!(matching_core.get_orders_ask().is_empty());
429 assert!(matching_core.get_orders_bid().is_empty());
430 }
431
432 #[rstest]
433 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
434 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
435 #[case(
436 Some(Price::from("100.00")),
437 Some(Price::from("101.00")),
438 Price::from("100.00"), OrderSide::Buy,
440 false
441 )]
442 #[case(
443 Some(Price::from("100.00")),
444 Some(Price::from("101.00")),
445 Price::from("101.00"), OrderSide::Buy,
447 true
448 )]
449 #[case(
450 Some(Price::from("100.00")),
451 Some(Price::from("101.00")),
452 Price::from("102.00"), OrderSide::Buy,
454 true
455 )]
456 #[case(
457 Some(Price::from("100.00")),
458 Some(Price::from("101.00")),
459 Price::from("101.00"), OrderSide::Sell,
461 false
462 )]
463 #[case(
464 Some(Price::from("100.00")),
465 Some(Price::from("101.00")),
466 Price::from("100.00"), OrderSide::Sell,
468 true
469 )]
470 #[case(
471 Some(Price::from("100.00")),
472 Some(Price::from("101.00")),
473 Price::from("99.00"), OrderSide::Sell,
475 true
476 )]
477 fn test_is_limit_matched(
478 #[case] bid: Option<Price>,
479 #[case] ask: Option<Price>,
480 #[case] price: Price,
481 #[case] order_side: OrderSide,
482 #[case] expected: bool,
483 ) {
484 let instrument_id = InstrumentId::from("AAPL.XNAS");
485 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
486 matching_core.bid = bid;
487 matching_core.ask = ask;
488
489 let order = OrderTestBuilder::new(OrderType::Limit)
490 .instrument_id(instrument_id)
491 .side(order_side)
492 .price(price)
493 .quantity(Quantity::from("100"))
494 .build();
495
496 let result =
497 matching_core.is_limit_matched(order.order_side_specified(), order.price().unwrap());
498 assert_eq!(result, expected);
499 }
500
501 #[rstest]
502 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
503 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
504 #[case(
505 Some(Price::from("100.00")),
506 Some(Price::from("101.00")),
507 Price::from("102.00"), OrderSide::Buy,
509 false
510 )]
511 #[case(
512 Some(Price::from("100.00")),
513 Some(Price::from("101.00")),
514 Price::from("101.00"), OrderSide::Buy,
516 true
517 )]
518 #[case(
519 Some(Price::from("100.00")),
520 Some(Price::from("101.00")),
521 Price::from("100.00"), OrderSide::Buy,
523 true
524 )]
525 #[case(
526 Some(Price::from("100.00")),
527 Some(Price::from("101.00")),
528 Price::from("99.00"), OrderSide::Sell,
530 false
531 )]
532 #[case(
533 Some(Price::from("100.00")),
534 Some(Price::from("101.00")),
535 Price::from("100.00"), OrderSide::Sell,
537 true
538 )]
539 #[case(
540 Some(Price::from("100.00")),
541 Some(Price::from("101.00")),
542 Price::from("101.00"), OrderSide::Sell,
544 true
545 )]
546 fn test_is_stop_matched(
547 #[case] bid: Option<Price>,
548 #[case] ask: Option<Price>,
549 #[case] trigger_price: Price,
550 #[case] order_side: OrderSide,
551 #[case] expected: bool,
552 ) {
553 let instrument_id = InstrumentId::from("AAPL.XNAS");
554 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
555 matching_core.bid = bid;
556 matching_core.ask = ask;
557
558 let order = OrderTestBuilder::new(OrderType::StopMarket)
559 .instrument_id(instrument_id)
560 .side(order_side)
561 .trigger_price(trigger_price)
562 .quantity(Quantity::from("100"))
563 .build();
564
565 let result = matching_core
566 .is_stop_matched(order.order_side_specified(), order.trigger_price().unwrap());
567 assert_eq!(result, expected);
568 }
569}