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, 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)]
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 fn get_orders_bid(&self) -> &[PassiveOrderAny] {
119 self.orders_bid.as_slice()
120 }
121
122 #[must_use]
123 pub 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> {
171 match order.order_side_specified() {
172 OrderSideSpecified::Buy => {
173 self.orders_bid.push(order);
174 Ok(())
175 }
176 OrderSideSpecified::Sell => {
177 self.orders_ask.push(order);
178 Ok(())
179 }
180 }
181 }
182
183 pub fn delete_order(&mut self, order: &PassiveOrderAny) -> Result<(), OrderError> {
184 match order.order_side_specified() {
185 OrderSideSpecified::Buy => {
186 let index = self
187 .orders_bid
188 .iter()
189 .position(|o| o == order)
190 .ok_or(OrderError::NotFound(order.client_order_id()))?;
191 self.orders_bid.remove(index);
192 Ok(())
193 }
194 OrderSideSpecified::Sell => {
195 let index = self
196 .orders_ask
197 .iter()
198 .position(|o| o == order)
199 .ok_or(OrderError::NotFound(order.client_order_id()))?;
200 self.orders_ask.remove(index);
201 Ok(())
202 }
203 }
204 }
205
206 pub fn iterate(&mut self) {
207 self.iterate_bids();
208 self.iterate_asks();
209 }
210
211 pub fn iterate_bids(&mut self) {
212 let orders: Vec<_> = self.orders_bid.clone();
213 for order in &orders {
214 self.match_order(order, false);
215 }
216 }
217
218 pub fn iterate_asks(&mut self) {
219 let orders: Vec<_> = self.orders_ask.clone();
220 for order in &orders {
221 self.match_order(order, false);
222 }
223 }
224
225 fn iterate_orders(&mut self, orders: &[PassiveOrderAny]) {
226 for order in orders {
227 self.match_order(order, false);
228 }
229 }
230
231 pub fn match_order(&mut self, order: &PassiveOrderAny, _initial: bool) {
234 match order {
235 PassiveOrderAny::Limit(o) => self.match_limit_order(o),
236 PassiveOrderAny::Stop(o) => self.match_stop_order(o),
237 }
238 }
239
240 pub fn match_limit_order(&mut self, order: &LimitOrderAny) {
241 if self.is_limit_matched(order.order_side_specified(), order.limit_px()) {
242 if let Some(handler) = &mut self.fill_limit_order {
243 handler
244 .0
245 .fill_limit_order(&mut OrderAny::from(order.clone()));
246 }
247 }
248 }
249
250 pub fn match_stop_order(&mut self, order: &StopOrderAny) {
251 if self.is_stop_matched(order.order_side_specified(), order.stop_px()) {
252 if let Some(handler) = &mut self.trigger_stop_order {
253 handler
254 .0
255 .trigger_stop_order(&mut OrderAny::from(order.clone()));
256 }
257 }
258 }
259
260 #[must_use]
261 pub fn is_limit_matched(&self, side: OrderSideSpecified, price: Price) -> bool {
262 match side {
263 OrderSideSpecified::Buy => self.ask.is_some_and(|a| a <= price),
264 OrderSideSpecified::Sell => self.bid.is_some_and(|b| b >= price),
265 }
266 }
267
268 #[must_use]
269 pub fn is_stop_matched(&self, side: OrderSideSpecified, price: Price) -> bool {
270 match side {
271 OrderSideSpecified::Buy => self.ask.is_some_and(|a| a >= price),
272 OrderSideSpecified::Sell => self.bid.is_some_and(|b| b <= price),
273 }
274 }
275
276 #[must_use]
277 pub fn is_touch_triggered(&self, side: OrderSideSpecified, trigger_price: Price) -> bool {
278 match side {
279 OrderSideSpecified::Buy => self.ask.is_some_and(|a| a <= trigger_price),
280 OrderSideSpecified::Sell => self.bid.is_some_and(|b| b >= trigger_price),
281 }
282 }
283}
284
285#[cfg(test)]
289mod tests {
290 use nautilus_model::{
291 enums::{OrderSide, OrderType},
292 orders::builder::OrderTestBuilder,
293 types::Quantity,
294 };
295 use rstest::rstest;
296
297 use super::*;
298
299 const fn create_matching_core(
300 instrument_id: InstrumentId,
301 price_increment: Price,
302 ) -> OrderMatchingCore {
303 OrderMatchingCore::new(instrument_id, price_increment, None, None, None)
304 }
305
306 #[rstest]
307 fn test_add_order_bid_side() {
308 let instrument_id = InstrumentId::from("AAPL.XNAS");
309 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
310
311 let order = OrderTestBuilder::new(OrderType::Limit)
312 .instrument_id(instrument_id)
313 .side(OrderSide::Buy)
314 .price(Price::from("100.00"))
315 .quantity(Quantity::from("100"))
316 .build();
317
318 matching_core.add_order(order.clone().into()).unwrap();
319
320 let passive_order: PassiveOrderAny = order.into();
321 assert!(matching_core.get_orders_bid().contains(&passive_order));
322 assert!(!matching_core.get_orders_ask().contains(&passive_order));
323 assert_eq!(matching_core.get_orders_bid().len(), 1);
324 assert!(matching_core.get_orders_ask().is_empty());
325 assert!(matching_core.order_exists(passive_order.client_order_id()));
326 }
327
328 #[rstest]
329 fn test_add_order_ask_side() {
330 let instrument_id = InstrumentId::from("AAPL.XNAS");
331 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
332
333 let order = OrderTestBuilder::new(OrderType::Limit)
334 .instrument_id(instrument_id)
335 .side(OrderSide::Sell)
336 .price(Price::from("100.00"))
337 .quantity(Quantity::from("100"))
338 .build();
339
340 matching_core.add_order(order.clone().into()).unwrap();
341
342 let passive_order: PassiveOrderAny = order.into();
343 assert!(matching_core.get_orders_ask().contains(&passive_order));
344 assert!(!matching_core.get_orders_bid().contains(&passive_order));
345 assert_eq!(matching_core.get_orders_ask().len(), 1);
346 assert!(matching_core.get_orders_bid().is_empty());
347 assert!(matching_core.order_exists(passive_order.client_order_id()));
348 }
349
350 #[rstest]
351 fn test_reset() {
352 let instrument_id = InstrumentId::from("AAPL.XNAS");
353 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
354
355 let order = OrderTestBuilder::new(OrderType::Limit)
356 .instrument_id(instrument_id)
357 .side(OrderSide::Sell)
358 .price(Price::from("100.00"))
359 .quantity(Quantity::from("100"))
360 .build();
361
362 let client_order_id = order.client_order_id();
363
364 matching_core.add_order(order.into()).unwrap();
365 matching_core.bid = Some(Price::from("100.00"));
366 matching_core.ask = Some(Price::from("100.00"));
367 matching_core.last = Some(Price::from("100.00"));
368
369 matching_core.reset();
370
371 assert!(matching_core.bid.is_none());
372 assert!(matching_core.ask.is_none());
373 assert!(matching_core.last.is_none());
374 assert!(matching_core.get_orders_bid().is_empty());
375 assert!(matching_core.get_orders_ask().is_empty());
376 assert!(!matching_core.order_exists(client_order_id));
377 }
378
379 #[rstest]
380 fn test_delete_order_when_not_exists() {
381 let instrument_id = InstrumentId::from("AAPL.XNAS");
382 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
383
384 let order = OrderTestBuilder::new(OrderType::Limit)
385 .instrument_id(instrument_id)
386 .side(OrderSide::Buy)
387 .price(Price::from("100.00"))
388 .quantity(Quantity::from("100"))
389 .build();
390
391 let result = matching_core.delete_order(&order.into());
392 assert!(result.is_err());
393 }
394
395 #[rstest]
396 #[case(OrderSide::Buy)]
397 #[case(OrderSide::Sell)]
398 fn test_delete_order_when_exists(#[case] order_side: OrderSide) {
399 let instrument_id = InstrumentId::from("AAPL.XNAS");
400 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
401
402 let order = OrderTestBuilder::new(OrderType::Limit)
403 .instrument_id(instrument_id)
404 .side(order_side)
405 .price(Price::from("100.00"))
406 .quantity(Quantity::from("100"))
407 .build();
408
409 matching_core.add_order(order.clone().into()).unwrap();
410 matching_core.delete_order(&order.into()).unwrap();
411
412 assert!(matching_core.get_orders_ask().is_empty());
413 assert!(matching_core.get_orders_bid().is_empty());
414 }
415
416 #[rstest]
417 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
418 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
419 #[case(
420 Some(Price::from("100.00")),
421 Some(Price::from("101.00")),
422 Price::from("100.00"), OrderSide::Buy,
424 false
425 )]
426 #[case(
427 Some(Price::from("100.00")),
428 Some(Price::from("101.00")),
429 Price::from("101.00"), OrderSide::Buy,
431 true
432 )]
433 #[case(
434 Some(Price::from("100.00")),
435 Some(Price::from("101.00")),
436 Price::from("102.00"), OrderSide::Buy,
438 true
439 )]
440 #[case(
441 Some(Price::from("100.00")),
442 Some(Price::from("101.00")),
443 Price::from("101.00"), OrderSide::Sell,
445 false
446 )]
447 #[case(
448 Some(Price::from("100.00")),
449 Some(Price::from("101.00")),
450 Price::from("100.00"), OrderSide::Sell,
452 true
453 )]
454 #[case(
455 Some(Price::from("100.00")),
456 Some(Price::from("101.00")),
457 Price::from("99.00"), OrderSide::Sell,
459 true
460 )]
461 fn test_is_limit_matched(
462 #[case] bid: Option<Price>,
463 #[case] ask: Option<Price>,
464 #[case] price: Price,
465 #[case] order_side: OrderSide,
466 #[case] expected: bool,
467 ) {
468 let instrument_id = InstrumentId::from("AAPL.XNAS");
469 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
470 matching_core.bid = bid;
471 matching_core.ask = ask;
472
473 let order = OrderTestBuilder::new(OrderType::Limit)
474 .instrument_id(instrument_id)
475 .side(order_side)
476 .price(price)
477 .quantity(Quantity::from("100"))
478 .build();
479
480 let result =
481 matching_core.is_limit_matched(order.order_side_specified(), order.price().unwrap());
482 assert_eq!(result, expected);
483 }
484
485 #[rstest]
486 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
487 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
488 #[case(
489 Some(Price::from("100.00")),
490 Some(Price::from("101.00")),
491 Price::from("102.00"), OrderSide::Buy,
493 false
494 )]
495 #[case(
496 Some(Price::from("100.00")),
497 Some(Price::from("101.00")),
498 Price::from("101.00"), OrderSide::Buy,
500 true
501 )]
502 #[case(
503 Some(Price::from("100.00")),
504 Some(Price::from("101.00")),
505 Price::from("100.00"), OrderSide::Buy,
507 true
508 )]
509 #[case(
510 Some(Price::from("100.00")),
511 Some(Price::from("101.00")),
512 Price::from("99.00"), OrderSide::Sell,
514 false
515 )]
516 #[case(
517 Some(Price::from("100.00")),
518 Some(Price::from("101.00")),
519 Price::from("100.00"), OrderSide::Sell,
521 true
522 )]
523 #[case(
524 Some(Price::from("100.00")),
525 Some(Price::from("101.00")),
526 Price::from("101.00"), OrderSide::Sell,
528 true
529 )]
530 fn test_is_stop_matched(
531 #[case] bid: Option<Price>,
532 #[case] ask: Option<Price>,
533 #[case] trigger_price: Price,
534 #[case] order_side: OrderSide,
535 #[case] expected: bool,
536 ) {
537 let instrument_id = InstrumentId::from("AAPL.XNAS");
538 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
539 matching_core.bid = bid;
540 matching_core.ask = ask;
541
542 let order = OrderTestBuilder::new(OrderType::StopMarket)
543 .instrument_id(instrument_id)
544 .side(order_side)
545 .trigger_price(trigger_price)
546 .quantity(Quantity::from("100"))
547 .build();
548
549 let result = matching_core
550 .is_stop_matched(order.order_side_specified(), order.trigger_price().unwrap());
551 assert_eq!(result, expected);
552 }
553}