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
335 .add_order(PassiveOrderAny::try_from(order.clone()).unwrap())
336 .unwrap();
337
338 let passive_order: PassiveOrderAny = PassiveOrderAny::try_from(order).unwrap();
339 assert!(matching_core.get_orders_bid().contains(&passive_order));
340 assert!(!matching_core.get_orders_ask().contains(&passive_order));
341 assert_eq!(matching_core.get_orders_bid().len(), 1);
342 assert!(matching_core.get_orders_ask().is_empty());
343 assert!(matching_core.order_exists(passive_order.client_order_id()));
344 }
345
346 #[rstest]
347 fn test_add_order_ask_side() {
348 let instrument_id = InstrumentId::from("AAPL.XNAS");
349 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
350
351 let order = OrderTestBuilder::new(OrderType::Limit)
352 .instrument_id(instrument_id)
353 .side(OrderSide::Sell)
354 .price(Price::from("100.00"))
355 .quantity(Quantity::from("100"))
356 .build();
357
358 matching_core
359 .add_order(PassiveOrderAny::try_from(order.clone()).unwrap())
360 .unwrap();
361
362 let passive_order: PassiveOrderAny = PassiveOrderAny::try_from(order).unwrap();
363 assert!(matching_core.get_orders_ask().contains(&passive_order));
364 assert!(!matching_core.get_orders_bid().contains(&passive_order));
365 assert_eq!(matching_core.get_orders_ask().len(), 1);
366 assert!(matching_core.get_orders_bid().is_empty());
367 assert!(matching_core.order_exists(passive_order.client_order_id()));
368 }
369
370 #[rstest]
371 fn test_reset() {
372 let instrument_id = InstrumentId::from("AAPL.XNAS");
373 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
374
375 let order = OrderTestBuilder::new(OrderType::Limit)
376 .instrument_id(instrument_id)
377 .side(OrderSide::Sell)
378 .price(Price::from("100.00"))
379 .quantity(Quantity::from("100"))
380 .build();
381
382 let client_order_id = order.client_order_id();
383
384 matching_core
385 .add_order(PassiveOrderAny::try_from(order).unwrap())
386 .unwrap();
387 matching_core.bid = Some(Price::from("100.00"));
388 matching_core.ask = Some(Price::from("100.00"));
389 matching_core.last = Some(Price::from("100.00"));
390
391 matching_core.reset();
392
393 assert!(matching_core.bid.is_none());
394 assert!(matching_core.ask.is_none());
395 assert!(matching_core.last.is_none());
396 assert!(matching_core.get_orders_bid().is_empty());
397 assert!(matching_core.get_orders_ask().is_empty());
398 assert!(!matching_core.order_exists(client_order_id));
399 }
400
401 #[rstest]
402 fn test_delete_order_when_not_exists() {
403 let instrument_id = InstrumentId::from("AAPL.XNAS");
404 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
405
406 let order = OrderTestBuilder::new(OrderType::Limit)
407 .instrument_id(instrument_id)
408 .side(OrderSide::Buy)
409 .price(Price::from("100.00"))
410 .quantity(Quantity::from("100"))
411 .build();
412
413 let result = matching_core.delete_order(&PassiveOrderAny::try_from(order).unwrap());
414 assert!(result.is_err());
415 }
416
417 #[rstest]
418 #[case(OrderSide::Buy)]
419 #[case(OrderSide::Sell)]
420 fn test_delete_order_when_exists(#[case] order_side: OrderSide) {
421 let instrument_id = InstrumentId::from("AAPL.XNAS");
422 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
423
424 let order = OrderTestBuilder::new(OrderType::Limit)
425 .instrument_id(instrument_id)
426 .side(order_side)
427 .price(Price::from("100.00"))
428 .quantity(Quantity::from("100"))
429 .build();
430
431 matching_core
432 .add_order(PassiveOrderAny::try_from(order.clone()).unwrap())
433 .unwrap();
434 matching_core
435 .delete_order(&PassiveOrderAny::try_from(order).unwrap())
436 .unwrap();
437
438 assert!(matching_core.get_orders_ask().is_empty());
439 assert!(matching_core.get_orders_bid().is_empty());
440 }
441
442 #[rstest]
443 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
444 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
445 #[case(
446 Some(Price::from("100.00")),
447 Some(Price::from("101.00")),
448 Price::from("100.00"), OrderSide::Buy,
450 false
451 )]
452 #[case(
453 Some(Price::from("100.00")),
454 Some(Price::from("101.00")),
455 Price::from("101.00"), OrderSide::Buy,
457 true
458 )]
459 #[case(
460 Some(Price::from("100.00")),
461 Some(Price::from("101.00")),
462 Price::from("102.00"), OrderSide::Buy,
464 true
465 )]
466 #[case(
467 Some(Price::from("100.00")),
468 Some(Price::from("101.00")),
469 Price::from("101.00"), OrderSide::Sell,
471 false
472 )]
473 #[case(
474 Some(Price::from("100.00")),
475 Some(Price::from("101.00")),
476 Price::from("100.00"), OrderSide::Sell,
478 true
479 )]
480 #[case(
481 Some(Price::from("100.00")),
482 Some(Price::from("101.00")),
483 Price::from("99.00"), OrderSide::Sell,
485 true
486 )]
487 fn test_is_limit_matched(
488 #[case] bid: Option<Price>,
489 #[case] ask: Option<Price>,
490 #[case] price: Price,
491 #[case] order_side: OrderSide,
492 #[case] expected: bool,
493 ) {
494 let instrument_id = InstrumentId::from("AAPL.XNAS");
495 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
496 matching_core.bid = bid;
497 matching_core.ask = ask;
498
499 let order = OrderTestBuilder::new(OrderType::Limit)
500 .instrument_id(instrument_id)
501 .side(order_side)
502 .price(price)
503 .quantity(Quantity::from("100"))
504 .build();
505
506 let result =
507 matching_core.is_limit_matched(order.order_side_specified(), order.price().unwrap());
508 assert_eq!(result, expected);
509 }
510
511 #[rstest]
512 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
513 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
514 #[case(
515 Some(Price::from("100.00")),
516 Some(Price::from("101.00")),
517 Price::from("102.00"), OrderSide::Buy,
519 false
520 )]
521 #[case(
522 Some(Price::from("100.00")),
523 Some(Price::from("101.00")),
524 Price::from("101.00"), OrderSide::Buy,
526 true
527 )]
528 #[case(
529 Some(Price::from("100.00")),
530 Some(Price::from("101.00")),
531 Price::from("100.00"), OrderSide::Buy,
533 true
534 )]
535 #[case(
536 Some(Price::from("100.00")),
537 Some(Price::from("101.00")),
538 Price::from("99.00"), OrderSide::Sell,
540 false
541 )]
542 #[case(
543 Some(Price::from("100.00")),
544 Some(Price::from("101.00")),
545 Price::from("100.00"), OrderSide::Sell,
547 true
548 )]
549 #[case(
550 Some(Price::from("100.00")),
551 Some(Price::from("101.00")),
552 Price::from("101.00"), OrderSide::Sell,
554 true
555 )]
556 fn test_is_stop_matched(
557 #[case] bid: Option<Price>,
558 #[case] ask: Option<Price>,
559 #[case] trigger_price: Price,
560 #[case] order_side: OrderSide,
561 #[case] expected: bool,
562 ) {
563 let instrument_id = InstrumentId::from("AAPL.XNAS");
564 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
565 matching_core.bid = bid;
566 matching_core.ask = ask;
567
568 let order = OrderTestBuilder::new(OrderType::StopMarket)
569 .instrument_id(instrument_id)
570 .side(order_side)
571 .trigger_price(trigger_price)
572 .quantity(Quantity::from("100"))
573 .build();
574
575 let result = matching_core
576 .is_stop_matched(order.order_side_specified(), order.trigger_price().unwrap());
577 assert_eq!(result, expected);
578 }
579}