1use std::collections::BTreeMap;
104
105use ahash::AHashMap;
106use nautilus_model::{
107 enums::{OrderSide, OrderType, TriggerType},
108 identifiers::{ClientOrderId, InstrumentId},
109 orders::{Order, OrderError, PassiveOrderAny, StopOrderAny},
110 types::Price,
111};
112use smallvec::SmallVec;
113
114const INLINE_ORDERS_PER_LEVEL: usize = 4;
118
119type OrderBucket = SmallVec<[RestingOrder; INLINE_ORDERS_PER_LEVEL]>;
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum MatchAction {
124 FillLimit(ClientOrderId),
125 TriggerStop(ClientOrderId),
126}
127
128#[derive(Clone, Debug)]
131pub struct OrderMatchingCore {
132 pub instrument_id: InstrumentId,
134 pub price_increment: Price,
136 pub bid: Option<Price>,
138 pub ask: Option<Price>,
140 pub last: Option<Price>,
142 fill_limit_inside_spread: bool,
143 bid_limits: BTreeMap<Price, OrderBucket>,
144 ask_limits: BTreeMap<Price, OrderBucket>,
145 bid_stops: BTreeMap<Price, OrderBucket>,
146 ask_stops: BTreeMap<Price, OrderBucket>,
147 pending_bid: SmallVec<[RestingOrder; 2]>,
148 pending_ask: SmallVec<[RestingOrder; 2]>,
149 order_index: AHashMap<ClientOrderId, (OrderSide, Option<(BookKind, Price)>)>,
150 pub(crate) mark: Option<Price>,
151}
152
153impl OrderMatchingCore {
154 #[must_use]
156 pub fn new(instrument_id: InstrumentId, price_increment: Price) -> Self {
157 Self {
158 instrument_id,
159 price_increment,
160 bid: None,
161 ask: None,
162 last: None,
163 fill_limit_inside_spread: false,
164 bid_limits: BTreeMap::new(),
165 ask_limits: BTreeMap::new(),
166 bid_stops: BTreeMap::new(),
167 ask_stops: BTreeMap::new(),
168 pending_bid: SmallVec::new(),
169 pending_ask: SmallVec::new(),
170 order_index: AHashMap::new(),
171 mark: None,
172 }
173 }
174
175 #[must_use]
177 pub const fn price_precision(&self) -> u8 {
178 self.price_increment.precision
179 }
180
181 #[must_use]
183 pub fn get_order(&self, client_order_id: ClientOrderId) -> Option<&RestingOrder> {
184 let (side, location) = self.order_index.get(&client_order_id).copied()?;
185 let orders: &[RestingOrder] = if let Some((kind, price)) = location {
186 self.book_for(side, kind).get(&price)?
187 } else {
188 self.pending_for(side)
189 };
190
191 orders.iter().find(|o| o.client_order_id == client_order_id)
192 }
193
194 pub fn iter_bid_orders(&self) -> impl Iterator<Item = &RestingOrder> {
199 self.iter_bid_book_orders().chain(self.pending_bid.iter())
200 }
201
202 pub fn iter_ask_orders(&self) -> impl Iterator<Item = &RestingOrder> {
207 self.iter_ask_book_orders().chain(self.pending_ask.iter())
208 }
209
210 pub fn iter_orders(&self) -> impl Iterator<Item = &RestingOrder> {
214 self.iter_bid_orders().chain(self.iter_ask_orders())
215 }
216
217 #[must_use]
222 pub fn get_orders_bid(&self) -> Vec<RestingOrder> {
223 self.iter_bid_orders().copied().collect()
224 }
225
226 #[must_use]
231 pub fn get_orders_ask(&self) -> Vec<RestingOrder> {
232 self.iter_ask_orders().copied().collect()
233 }
234
235 #[must_use]
239 pub fn get_orders(&self) -> Vec<RestingOrder> {
240 self.iter_orders().copied().collect()
241 }
242
243 #[must_use]
245 pub fn order_exists(&self, client_order_id: ClientOrderId) -> bool {
246 self.order_index.contains_key(&client_order_id)
247 }
248
249 pub const fn set_last_raw(&mut self, last: Price) {
251 self.last = Some(last);
252 }
253
254 pub const fn set_bid_raw(&mut self, bid: Price) {
256 self.bid = Some(bid);
257 }
258
259 pub const fn set_ask_raw(&mut self, ask: Price) {
261 self.ask = Some(ask);
262 }
263
264 pub const fn update_price_increment(&mut self, price_increment: Price) {
266 self.price_increment = price_increment;
267 }
268
269 pub fn reset(&mut self) {
271 self.bid = None;
272 self.ask = None;
273 self.last = None;
274 self.mark = None;
275 self.bid_limits.clear();
276 self.ask_limits.clear();
277 self.bid_stops.clear();
278 self.ask_stops.clear();
279 self.pending_bid.clear();
280 self.pending_ask.clear();
281 self.order_index.clear();
282 }
283
284 pub fn add_order(&mut self, order: RestingOrder) {
304 debug_assert!(
305 !self.order_exists(order.client_order_id),
306 "duplicate add_order for {}; caller must delete before re-adding",
307 order.client_order_id,
308 );
309
310 let side = order.order_side;
311 let client_order_id = order.client_order_id;
312 let location = Self::locate(&order);
313
314 if let Some((kind, price)) = location {
315 let book = self.book_for_mut(side, kind);
316 book.entry(price).or_default().push(order);
317 } else {
318 self.pending_for_mut(side).push(order);
319 }
320 self.order_index.insert(client_order_id, (side, location));
321 }
322
323 pub fn delete_order(&mut self, client_order_id: ClientOrderId) -> Result<(), OrderError> {
334 let Some((side, location)) = self.order_index.remove(&client_order_id) else {
335 return Err(OrderError::NotFound(client_order_id));
336 };
337
338 if let Some((kind, price)) = location {
339 let book = self.book_for_mut(side, kind);
340 let bucket = book
341 .get_mut(&price)
342 .expect("order_index points to existing bucket");
343 let pos = bucket
344 .iter()
345 .position(|o| o.client_order_id == client_order_id)
346 .expect("order_index points to existing slot");
347
348 bucket.remove(pos);
349 if bucket.is_empty() {
350 book.remove(&price);
351 }
352 } else {
353 let pending = self.pending_for_mut(side);
354 let pos = pending
355 .iter()
356 .position(|o| o.client_order_id == client_order_id)
357 .expect("order_index points to existing pending slot");
358 pending.remove(pos);
359 }
360 Ok(())
361 }
362
363 pub fn iterate(&self) -> Vec<MatchAction> {
366 let mut actions = self.iterate_bids();
367 actions.extend(self.iterate_asks());
368 actions
369 }
370
371 pub fn iterate_bids(&self) -> Vec<MatchAction> {
374 self.bid_limits
375 .iter()
376 .rev()
377 .take_while(|(price, _)| self.is_limit_fillable(OrderSide::Buy, **price))
378 .flat_map(|(_, bucket)| bucket)
379 .chain(self.bid_stops.values().flatten())
380 .filter_map(|order| self.match_order(order))
381 .collect()
382 }
383
384 pub fn iterate_asks(&self) -> Vec<MatchAction> {
387 self.ask_limits
388 .iter()
389 .take_while(|(price, _)| self.is_limit_fillable(OrderSide::Sell, **price))
390 .flat_map(|(_, bucket)| bucket)
391 .chain(self.ask_stops.values().rev().flatten())
392 .filter_map(|order| self.match_order(order))
393 .collect()
394 }
395
396 pub fn match_order(&self, order: &RestingOrder) -> Option<MatchAction> {
399 if order.is_stop() {
400 self.match_stop_order(order)
401 } else if order.is_limit() {
402 self.match_limit_order(order)
403 } else {
404 None
405 }
406 }
407
408 #[must_use]
411 pub fn is_limit_matched(&self, side: OrderSide, price: Price) -> bool {
412 match side {
413 OrderSide::Buy => self.ask.is_some_and(|a| a <= price),
414 OrderSide::Sell => self.bid.is_some_and(|b| b >= price),
415 }
416 }
417
418 #[must_use]
421 pub fn is_stop_matched(&self, side: OrderSide, price: Price) -> bool {
422 self.is_stop_matched_with_trigger_type(side, price, TriggerType::BidAsk)
423 }
424
425 #[must_use]
426 pub(crate) fn is_stop_matched_with_trigger_type(
427 &self,
428 side: OrderSide,
429 price: Price,
430 trigger_type: TriggerType,
431 ) -> bool {
432 self.market_price_for_trigger(side, trigger_type)
433 .is_some_and(|market_price| match side {
434 OrderSide::Buy => market_price >= price,
435 OrderSide::Sell => market_price <= price,
436 })
437 }
438
439 #[must_use]
442 pub fn is_touch_triggered(&self, side: OrderSide, trigger_price: Price) -> bool {
443 self.is_touch_triggered_with_trigger_type(side, trigger_price, TriggerType::BidAsk)
444 }
445
446 #[must_use]
447 pub(crate) fn is_touch_triggered_with_trigger_type(
448 &self,
449 side: OrderSide,
450 trigger_price: Price,
451 trigger_type: TriggerType,
452 ) -> bool {
453 self.market_price_for_trigger(side, trigger_type)
454 .is_some_and(|market_price| match side {
455 OrderSide::Buy => market_price <= trigger_price,
456 OrderSide::Sell => market_price >= trigger_price,
457 })
458 }
459
460 pub fn set_fill_limit_inside_spread(&mut self, value: bool) {
462 self.fill_limit_inside_spread = value;
463 }
464
465 #[must_use]
471 pub fn is_limit_fillable(&self, side: OrderSide, price: Price) -> bool {
472 if self.is_limit_matched(side, price) {
474 return true;
475 }
476
477 if !self.fill_limit_inside_spread {
478 return false;
479 }
480
481 if let (Some(bid), Some(ask)) = (self.bid, self.ask) {
482 match side {
483 OrderSide::Buy => price >= bid,
484 OrderSide::Sell => price <= ask,
485 }
486 } else {
487 false
488 }
489 }
490
491 fn book_for(&self, side: OrderSide, kind: BookKind) -> &BTreeMap<Price, OrderBucket> {
492 match (side, kind) {
493 (OrderSide::Buy, BookKind::Limit) => &self.bid_limits,
494 (OrderSide::Buy, BookKind::Stop) => &self.bid_stops,
495 (OrderSide::Sell, BookKind::Limit) => &self.ask_limits,
496 (OrderSide::Sell, BookKind::Stop) => &self.ask_stops,
497 }
498 }
499
500 fn book_for_mut(
501 &mut self,
502 side: OrderSide,
503 kind: BookKind,
504 ) -> &mut BTreeMap<Price, OrderBucket> {
505 match (side, kind) {
506 (OrderSide::Buy, BookKind::Limit) => &mut self.bid_limits,
507 (OrderSide::Buy, BookKind::Stop) => &mut self.bid_stops,
508 (OrderSide::Sell, BookKind::Limit) => &mut self.ask_limits,
509 (OrderSide::Sell, BookKind::Stop) => &mut self.ask_stops,
510 }
511 }
512
513 fn pending_for(&self, side: OrderSide) -> &[RestingOrder] {
514 match side {
515 OrderSide::Buy => &self.pending_bid,
516 OrderSide::Sell => &self.pending_ask,
517 }
518 }
519
520 fn pending_for_mut(&mut self, side: OrderSide) -> &mut SmallVec<[RestingOrder; 2]> {
521 match side {
522 OrderSide::Buy => &mut self.pending_bid,
523 OrderSide::Sell => &mut self.pending_ask,
524 }
525 }
526
527 fn iter_bid_book_orders(&self) -> impl Iterator<Item = &RestingOrder> {
528 self.bid_limits
529 .values()
530 .rev()
531 .flatten()
532 .chain(self.bid_stops.values().flatten())
533 }
534
535 fn iter_ask_book_orders(&self) -> impl Iterator<Item = &RestingOrder> {
536 self.ask_limits
537 .values()
538 .flatten()
539 .chain(self.ask_stops.values().rev().flatten())
540 }
541
542 fn locate(order: &RestingOrder) -> Option<(BookKind, Price)> {
543 if let Some(price) = order.trigger_price {
544 Some((BookKind::Stop, price))
545 } else {
546 order.limit_price.map(|price| (BookKind::Limit, price))
547 }
548 }
549
550 fn match_limit_order(&self, order: &RestingOrder) -> Option<MatchAction> {
551 self.is_limit_fillable(order.order_side, order.limit_price?)
552 .then_some(MatchAction::FillLimit(order.client_order_id))
553 }
554
555 fn match_stop_order(&self, order: &RestingOrder) -> Option<MatchAction> {
556 if !order.is_activated {
557 return None;
558 }
559
560 let trigger_price = order.trigger_price?;
561
562 let is_triggered = match order.order_type {
563 OrderType::MarketIfTouched | OrderType::LimitIfTouched => self
564 .is_touch_triggered_with_trigger_type(
565 order.order_side,
566 trigger_price,
567 order.trigger_type.unwrap_or(TriggerType::Default),
568 ),
569 _ => self.is_stop_matched_with_trigger_type(
570 order.order_side,
571 trigger_price,
572 order.trigger_type.unwrap_or(TriggerType::Default),
573 ),
574 };
575
576 if is_triggered {
577 Some(MatchAction::TriggerStop(order.client_order_id))
578 } else {
579 None
580 }
581 }
582
583 fn market_price_for_trigger(
584 &self,
585 side: OrderSide,
586 trigger_type: TriggerType,
587 ) -> Option<Price> {
588 let quote_price = match side {
589 OrderSide::Buy => self.ask,
590 OrderSide::Sell => self.bid,
591 };
592
593 match trigger_type {
594 TriggerType::LastPrice => self.last,
595 TriggerType::MarkPrice => self.mark,
596 TriggerType::LastOrBidAsk => self.last.or(quote_price),
597 _ => quote_price,
598 }
599 }
600}
601
602#[derive(Clone, Copy, Debug, PartialEq, Eq)]
604pub struct RestingOrder {
605 pub client_order_id: ClientOrderId,
606 pub order_side: OrderSide,
607 pub order_type: OrderType,
608 pub trigger_type: Option<TriggerType>,
609 pub trigger_price: Option<Price>,
610 pub limit_price: Option<Price>,
611 pub is_activated: bool,
612}
613
614impl RestingOrder {
615 #[must_use]
622 pub const fn new(
623 client_order_id: ClientOrderId,
624 order_side: OrderSide,
625 order_type: OrderType,
626 trigger_price: Option<Price>,
627 limit_price: Option<Price>,
628 is_activated: bool,
629 ) -> Self {
630 Self::new_with_trigger_type(
631 client_order_id,
632 order_side,
633 order_type,
634 match trigger_price {
635 Some(_) => Some(TriggerType::Default),
636 None => None,
637 },
638 trigger_price,
639 limit_price,
640 is_activated,
641 )
642 }
643
644 #[must_use]
645 pub(crate) const fn new_with_trigger_type(
646 client_order_id: ClientOrderId,
647 order_side: OrderSide,
648 order_type: OrderType,
649 trigger_type: Option<TriggerType>,
650 trigger_price: Option<Price>,
651 limit_price: Option<Price>,
652 is_activated: bool,
653 ) -> Self {
654 Self {
655 client_order_id,
656 order_side,
657 order_type,
658 trigger_type,
659 trigger_price,
660 limit_price,
661 is_activated,
662 }
663 }
664
665 #[must_use]
667 pub const fn is_stop(&self) -> bool {
668 self.trigger_price.is_some()
669 }
670
671 #[must_use]
673 pub const fn is_limit(&self) -> bool {
674 self.limit_price.is_some() && self.trigger_price.is_none()
675 }
676}
677
678impl From<&PassiveOrderAny> for RestingOrder {
679 fn from(order: &PassiveOrderAny) -> Self {
680 match order {
681 PassiveOrderAny::Limit(limit) => Self {
682 client_order_id: limit.client_order_id(),
683 order_side: limit.order_side(),
684 order_type: limit.order_type(),
685 trigger_type: None,
686 trigger_price: None,
687 limit_price: Some(limit.limit_px()),
688 is_activated: true,
689 },
690 PassiveOrderAny::Stop(stop) => {
691 let limit_price = match stop {
692 StopOrderAny::LimitIfTouched(o) => Some(o.price),
693 StopOrderAny::StopLimit(o) => Some(o.price),
694 StopOrderAny::TrailingStopLimit(o) => o.price,
695 StopOrderAny::MarketIfTouched(_)
696 | StopOrderAny::StopMarket(_)
697 | StopOrderAny::TrailingStopMarket(_) => None,
698 };
699 let is_activated = match stop {
700 StopOrderAny::TrailingStopMarket(o) => o.is_activated,
701 StopOrderAny::TrailingStopLimit(o) => o.is_activated,
702 _ => true,
703 };
704 Self {
705 client_order_id: stop.client_order_id(),
706 order_side: stop.order_side(),
707 order_type: stop.order_type(),
708 trigger_type: Some(stop.trigger_type().unwrap_or(TriggerType::Default)),
709 trigger_price: stop.stop_px(),
710 limit_price,
711 is_activated,
712 }
713 }
714 }
715 }
716}
717
718#[derive(Debug, Clone, Copy, PartialEq, Eq)]
719enum BookKind {
720 Limit,
721 Stop,
722}
723
724#[cfg(test)]
725mod tests {
726 use nautilus_model::{
727 enums::{OrderSide, OrderType, TrailingOffsetType, TriggerType},
728 events::{OrderEventAny, OrderInitialized, order::spec::OrderInitializedSpec},
729 orders::{Order, OrderAny, builder::OrderTestBuilder},
730 types::Quantity,
731 };
732 use rstest::rstest;
733 use rust_decimal::Decimal;
734
735 use super::*;
736
737 fn create_matching_core(
738 instrument_id: InstrumentId,
739 price_increment: Price,
740 ) -> OrderMatchingCore {
741 OrderMatchingCore::new(instrument_id, price_increment)
742 }
743
744 #[rstest]
745 fn test_add_order_bid_side() {
746 let instrument_id = InstrumentId::from("AAPL.XNAS");
747 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
748
749 let order = OrderTestBuilder::new(OrderType::Limit)
750 .instrument_id(instrument_id)
751 .side(OrderSide::Buy)
752 .price(Price::from("100.00"))
753 .quantity(Quantity::from("100"))
754 .build();
755
756 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
757 matching_core.add_order(match_info);
758
759 assert!(matching_core.get_orders_bid().contains(&match_info));
760 assert!(!matching_core.get_orders_ask().contains(&match_info));
761 assert_eq!(matching_core.get_orders_bid().len(), 1);
762 assert!(matching_core.get_orders_ask().is_empty());
763 assert!(matching_core.order_exists(match_info.client_order_id));
764 }
765
766 #[rstest]
767 fn test_add_order_ask_side() {
768 let instrument_id = InstrumentId::from("AAPL.XNAS");
769 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
770
771 let order = OrderTestBuilder::new(OrderType::Limit)
772 .instrument_id(instrument_id)
773 .side(OrderSide::Sell)
774 .price(Price::from("100.00"))
775 .quantity(Quantity::from("100"))
776 .build();
777
778 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
779 matching_core.add_order(match_info);
780
781 assert!(matching_core.get_orders_ask().contains(&match_info));
782 assert!(!matching_core.get_orders_bid().contains(&match_info));
783 assert_eq!(matching_core.get_orders_ask().len(), 1);
784 assert!(matching_core.get_orders_bid().is_empty());
785 assert!(matching_core.order_exists(match_info.client_order_id));
786 }
787
788 #[rstest]
789 fn test_reset() {
790 let instrument_id = InstrumentId::from("AAPL.XNAS");
791 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
792
793 let orders = [
794 limit_order(OrderSide::Buy, "110.00", "O-B-LMT"),
795 stop_order(OrderSide::Buy, "101.00", "O-B-STP"),
796 pending_order(OrderSide::Buy, "O-B-PENDING"),
797 limit_order(OrderSide::Sell, "90.00", "O-A-LMT"),
798 stop_order(OrderSide::Sell, "99.00", "O-A-STP"),
799 pending_order(OrderSide::Sell, "O-A-PENDING"),
800 ];
801
802 for order in orders {
803 matching_core.add_order(order);
804 }
805 matching_core.set_bid_raw(Price::from("94.00"));
806 matching_core.set_ask_raw(Price::from("106.00"));
807 matching_core.set_last_raw(Price::from("100.00"));
808
809 assert_eq!(matching_core.get_orders(), orders);
810
811 matching_core.reset();
812
813 assert!(matching_core.bid.is_none());
814 assert!(matching_core.ask.is_none());
815 assert!(matching_core.last.is_none());
816 assert!(matching_core.get_orders_bid().is_empty());
817 assert!(matching_core.get_orders_ask().is_empty());
818 assert!(matching_core.get_orders().is_empty());
819 for order in orders {
820 assert_eq!(matching_core.get_order(order.client_order_id), None);
821 assert!(!matching_core.order_exists(order.client_order_id));
822 }
823
824 matching_core.set_bid_raw(Price::from("94.00"));
825 matching_core.set_ask_raw(Price::from("106.00"));
826
827 assert!(matching_core.iterate().is_empty());
828
829 for order in orders {
830 matching_core.add_order(order);
831 }
832
833 assert_eq!(matching_core.get_orders(), orders);
834 }
835
836 #[rstest]
837 fn test_delete_order_when_not_exists() {
838 let instrument_id = InstrumentId::from("AAPL.XNAS");
839 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
840
841 let order = OrderTestBuilder::new(OrderType::Limit)
842 .instrument_id(instrument_id)
843 .side(OrderSide::Buy)
844 .price(Price::from("100.00"))
845 .quantity(Quantity::from("100"))
846 .build();
847
848 let result = matching_core.delete_order(order.client_order_id());
849 assert!(result.is_err());
850 }
851
852 #[rstest]
853 #[case(OrderSide::Buy)]
854 #[case(OrderSide::Sell)]
855 fn test_delete_order_when_exists(#[case] order_side: OrderSide) {
856 let instrument_id = InstrumentId::from("AAPL.XNAS");
857 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
858
859 let order = OrderTestBuilder::new(OrderType::Limit)
860 .instrument_id(instrument_id)
861 .side(order_side)
862 .price(Price::from("100.00"))
863 .quantity(Quantity::from("100"))
864 .build();
865
866 let client_order_id = order.client_order_id();
867 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
868 matching_core.add_order(match_info);
869 matching_core.delete_order(client_order_id).unwrap();
870
871 assert!(matching_core.get_orders_ask().is_empty());
872 assert!(matching_core.get_orders_bid().is_empty());
873 }
874
875 #[rstest]
876 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
877 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
878 #[case(
879 Some(Price::from("100.00")),
880 Some(Price::from("101.00")),
881 Price::from("100.00"),
882 OrderSide::Buy,
883 false
884 )]
885 #[case(
886 Some(Price::from("100.00")),
887 Some(Price::from("101.00")),
888 Price::from("101.00"),
889 OrderSide::Buy,
890 true
891 )]
892 #[case(
893 Some(Price::from("100.00")),
894 Some(Price::from("101.00")),
895 Price::from("102.00"),
896 OrderSide::Buy,
897 true
898 )]
899 #[case(
900 Some(Price::from("100.00")),
901 Some(Price::from("101.00")),
902 Price::from("101.00"),
903 OrderSide::Sell,
904 false
905 )]
906 #[case(
907 Some(Price::from("100.00")),
908 Some(Price::from("101.00")),
909 Price::from("100.00"),
910 OrderSide::Sell,
911 true
912 )]
913 #[case(
914 Some(Price::from("100.00")),
915 Some(Price::from("101.00")),
916 Price::from("99.00"),
917 OrderSide::Sell,
918 true
919 )]
920 fn test_is_limit_matched(
921 #[case] bid: Option<Price>,
922 #[case] ask: Option<Price>,
923 #[case] price: Price,
924 #[case] order_side: OrderSide,
925 #[case] expected: bool,
926 ) {
927 let instrument_id = InstrumentId::from("AAPL.XNAS");
928 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
929 matching_core.bid = bid;
930 matching_core.ask = ask;
931
932 let order = OrderTestBuilder::new(OrderType::Limit)
933 .instrument_id(instrument_id)
934 .side(order_side)
935 .price(price)
936 .quantity(Quantity::from("100"))
937 .build();
938
939 let result = matching_core.is_limit_matched(order.order_side(), order.price().unwrap());
940 assert_eq!(result, expected);
941 }
942
943 #[rstest]
944 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
945 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
946 #[case(
947 Some(Price::from("100.00")),
948 Some(Price::from("101.00")),
949 Price::from("102.00"),
950 OrderSide::Buy,
951 false
952 )]
953 #[case(
954 Some(Price::from("100.00")),
955 Some(Price::from("101.00")),
956 Price::from("101.00"),
957 OrderSide::Buy,
958 true
959 )]
960 #[case(
961 Some(Price::from("100.00")),
962 Some(Price::from("101.00")),
963 Price::from("100.00"),
964 OrderSide::Buy,
965 true
966 )]
967 #[case(
968 Some(Price::from("100.00")),
969 Some(Price::from("101.00")),
970 Price::from("99.00"),
971 OrderSide::Sell,
972 false
973 )]
974 #[case(
975 Some(Price::from("100.00")),
976 Some(Price::from("101.00")),
977 Price::from("100.00"),
978 OrderSide::Sell,
979 true
980 )]
981 #[case(
982 Some(Price::from("100.00")),
983 Some(Price::from("101.00")),
984 Price::from("101.00"),
985 OrderSide::Sell,
986 true
987 )]
988 fn test_is_stop_matched(
989 #[case] bid: Option<Price>,
990 #[case] ask: Option<Price>,
991 #[case] trigger_price: Price,
992 #[case] order_side: OrderSide,
993 #[case] expected: bool,
994 ) {
995 let instrument_id = InstrumentId::from("AAPL.XNAS");
996 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
997 matching_core.bid = bid;
998 matching_core.ask = ask;
999
1000 let order = OrderTestBuilder::new(OrderType::StopMarket)
1001 .instrument_id(instrument_id)
1002 .side(order_side)
1003 .trigger_price(trigger_price)
1004 .quantity(Quantity::from("100"))
1005 .build();
1006
1007 let result =
1008 matching_core.is_stop_matched(order.order_side(), order.trigger_price().unwrap());
1009 assert_eq!(result, expected);
1010 }
1011
1012 #[rstest]
1013 #[case::last_price_below_trigger(Some(Price::from("99.00")), TriggerType::LastPrice, false)]
1014 #[case::last_price_at_trigger(Some(Price::from("100.00")), TriggerType::LastPrice, true)]
1015 #[case::last_price_unavailable(None, TriggerType::LastPrice, false)]
1016 #[case::last_or_bid_ask_prefers_last(
1017 Some(Price::from("99.00")),
1018 TriggerType::LastOrBidAsk,
1019 false
1020 )]
1021 #[case::last_or_bid_ask_falls_back_to_quote(None, TriggerType::LastOrBidAsk, true)]
1022 #[case::bid_ask_uses_quote(Some(Price::from("99.00")), TriggerType::BidAsk, true)]
1023 fn test_is_stop_matched_uses_trigger_type(
1024 #[case] last: Option<Price>,
1025 #[case] trigger_type: TriggerType,
1026 #[case] expected: bool,
1027 ) {
1028 let instrument_id = InstrumentId::from("AAPL.XNAS");
1029 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1030 matching_core.ask = Some(Price::from("101.00"));
1031 matching_core.last = last;
1032
1033 let result = matching_core.is_stop_matched_with_trigger_type(
1034 OrderSide::Buy,
1035 Price::from("100.00"),
1036 trigger_type,
1037 );
1038
1039 assert_eq!(result, expected);
1040 }
1041
1042 #[rstest]
1043 #[case::missing(None, false)]
1044 #[case::safe(Some(Price::from("100.00")), false)]
1045 #[case::crossed(Some(Price::from("110.00")), true)]
1046 fn test_mark_price_trigger_ignores_quote_and_last(
1047 #[case] mark: Option<Price>,
1048 #[case] expected: bool,
1049 ) {
1050 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1051 core.bid = Some(Price::from("120.00"));
1052 core.ask = Some(Price::from("121.00"));
1053 core.last = Some(Price::from("122.00"));
1054 core.mark = mark;
1055 assert_eq!(
1056 core.is_stop_matched_with_trigger_type(
1057 OrderSide::Buy,
1058 Price::from("105.00"),
1059 TriggerType::MarkPrice
1060 ),
1061 expected
1062 );
1063 core.reset();
1064 assert_eq!(core.mark, None);
1065 }
1066
1067 #[rstest]
1068 fn test_iterate_returns_empty_when_no_orders() {
1069 let instrument_id = InstrumentId::from("AAPL.XNAS");
1070 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1071 matching_core.set_bid_raw(Price::from("100.00"));
1072 matching_core.set_ask_raw(Price::from("101.00"));
1073
1074 let actions = matching_core.iterate();
1075
1076 assert!(actions.is_empty());
1077 }
1078
1079 #[rstest]
1080 fn test_iterate_returns_empty_when_no_market_data() {
1081 let instrument_id = InstrumentId::from("AAPL.XNAS");
1082 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1083
1084 let order = OrderTestBuilder::new(OrderType::Limit)
1085 .instrument_id(instrument_id)
1086 .side(OrderSide::Buy)
1087 .price(Price::from("100.00"))
1088 .quantity(Quantity::from("100"))
1089 .build();
1090 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1091 matching_core.add_order(match_info);
1092
1093 let actions = matching_core.iterate();
1094
1095 assert!(actions.is_empty());
1096 }
1097
1098 #[rstest]
1099 fn test_iterate_returns_fill_limit_for_matched_buy() {
1100 let instrument_id = InstrumentId::from("AAPL.XNAS");
1101 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1102 matching_core.set_ask_raw(Price::from("100.00"));
1103
1104 let order = OrderTestBuilder::new(OrderType::Limit)
1105 .instrument_id(instrument_id)
1106 .side(OrderSide::Buy)
1107 .price(Price::from("100.00"))
1108 .quantity(Quantity::from("100"))
1109 .build();
1110 let client_order_id = order.client_order_id();
1111 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1112 matching_core.add_order(match_info);
1113
1114 let actions = matching_core.iterate();
1115
1116 assert_eq!(actions, vec![MatchAction::FillLimit(client_order_id)]);
1117 }
1118
1119 #[rstest]
1120 fn test_iterate_returns_fill_limit_for_matched_sell() {
1121 let instrument_id = InstrumentId::from("AAPL.XNAS");
1122 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1123 matching_core.set_bid_raw(Price::from("100.00"));
1124
1125 let order = OrderTestBuilder::new(OrderType::Limit)
1126 .instrument_id(instrument_id)
1127 .side(OrderSide::Sell)
1128 .price(Price::from("100.00"))
1129 .quantity(Quantity::from("100"))
1130 .build();
1131 let client_order_id = order.client_order_id();
1132 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1133 matching_core.add_order(match_info);
1134
1135 let actions = matching_core.iterate();
1136
1137 assert_eq!(actions, vec![MatchAction::FillLimit(client_order_id)]);
1138 }
1139
1140 #[rstest]
1141 fn test_iterate_returns_no_fill_for_unmatched_limit() {
1142 let instrument_id = InstrumentId::from("AAPL.XNAS");
1143 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1144 matching_core.set_ask_raw(Price::from("101.00"));
1145
1146 let order = OrderTestBuilder::new(OrderType::Limit)
1147 .instrument_id(instrument_id)
1148 .side(OrderSide::Buy)
1149 .price(Price::from("100.00"))
1150 .quantity(Quantity::from("100"))
1151 .build();
1152 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1153 matching_core.add_order(match_info);
1154
1155 let actions = matching_core.iterate();
1156
1157 assert!(actions.is_empty());
1158 }
1159
1160 #[rstest]
1161 fn test_iterate_returns_trigger_stop_for_matched_buy() {
1162 let instrument_id = InstrumentId::from("AAPL.XNAS");
1163 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1164 matching_core.set_ask_raw(Price::from("101.00"));
1165
1166 let order = OrderTestBuilder::new(OrderType::StopMarket)
1167 .instrument_id(instrument_id)
1168 .side(OrderSide::Buy)
1169 .trigger_price(Price::from("101.00"))
1170 .trigger_type(TriggerType::Default)
1171 .quantity(Quantity::from("100"))
1172 .build();
1173 let client_order_id = order.client_order_id();
1174 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1175 matching_core.add_order(match_info);
1176
1177 let actions = matching_core.iterate();
1178
1179 assert_eq!(actions, vec![MatchAction::TriggerStop(client_order_id)]);
1180 }
1181
1182 #[rstest]
1183 fn test_iterate_returns_trigger_stop_for_matched_sell() {
1184 let instrument_id = InstrumentId::from("AAPL.XNAS");
1185 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1186 matching_core.set_bid_raw(Price::from("99.00"));
1187
1188 let order = OrderTestBuilder::new(OrderType::StopMarket)
1189 .instrument_id(instrument_id)
1190 .side(OrderSide::Sell)
1191 .trigger_price(Price::from("99.00"))
1192 .quantity(Quantity::from("100"))
1193 .build();
1194 let client_order_id = order.client_order_id();
1195 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1196 matching_core.add_order(match_info);
1197
1198 let actions = matching_core.iterate();
1199
1200 assert_eq!(actions, vec![MatchAction::TriggerStop(client_order_id)]);
1201 }
1202
1203 #[rstest]
1204 fn test_iterate_skips_unactivated_stop_order() {
1205 let instrument_id = InstrumentId::from("AAPL.XNAS");
1206 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1207 matching_core.set_ask_raw(Price::from("110.00"));
1208
1209 let match_info = RestingOrder::new(
1210 ClientOrderId::from("O-001"),
1211 OrderSide::Buy,
1212 OrderType::TrailingStopMarket,
1213 Some(Price::from("105.00")),
1214 None,
1215 false,
1216 );
1217 matching_core.add_order(match_info);
1218
1219 let actions = matching_core.iterate();
1220
1221 assert!(actions.is_empty());
1222 }
1223
1224 #[rstest]
1225 fn test_iterate_triggers_activated_stop_order() {
1226 let instrument_id = InstrumentId::from("AAPL.XNAS");
1227 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1228 matching_core.set_ask_raw(Price::from("110.00"));
1229
1230 let client_order_id = ClientOrderId::from("O-001");
1231 let match_info = RestingOrder::new(
1232 client_order_id,
1233 OrderSide::Buy,
1234 OrderType::TrailingStopMarket,
1235 Some(Price::from("105.00")),
1236 None,
1237 true,
1238 );
1239 matching_core.add_order(match_info);
1240
1241 let actions = matching_core.iterate();
1242
1243 assert_eq!(actions, vec![MatchAction::TriggerStop(client_order_id)]);
1244 }
1245
1246 #[rstest]
1247 #[case(false, "101.00", "99.00")]
1248 #[case(true, "99.00", "101.00")]
1249 fn test_iterate_returns_mixed_actions_for_limits_and_stops(
1250 #[case] fill_inside: bool,
1251 #[case] buy_price: &str,
1252 #[case] sell_price: &str,
1253 ) {
1254 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1255 core.set_bid_raw(Price::from("99.00"));
1256 core.set_ask_raw(Price::from("101.00"));
1257 core.set_fill_limit_inside_spread(fill_inside);
1258
1259 core.add_order(limit_order(OrderSide::Buy, "102.00", "O-BUY-BEST"));
1260 core.add_order(limit_order(OrderSide::Buy, buy_price, "O-BUY-LIMIT"));
1261 core.add_order(limit_order(OrderSide::Buy, "98.00", "O-BUY-MISS"));
1262 core.add_order(stop_order(OrderSide::Buy, "101.00", "O-BUY-STOP"));
1263 core.add_order(limit_order(OrderSide::Sell, "98.00", "O-SELL-BEST"));
1264 core.add_order(limit_order(OrderSide::Sell, sell_price, "O-SELL-LIMIT"));
1265 core.add_order(limit_order(OrderSide::Sell, "102.00", "O-SELL-MISS"));
1266 core.add_order(stop_order(OrderSide::Sell, "99.00", "O-SELL-STOP"));
1267
1268 let actions = core.iterate();
1269
1270 assert_eq!(
1271 actions,
1272 vec![
1273 MatchAction::FillLimit(ClientOrderId::from("O-BUY-BEST")),
1274 MatchAction::FillLimit(ClientOrderId::from("O-BUY-LIMIT")),
1275 MatchAction::TriggerStop(ClientOrderId::from("O-BUY-STOP")),
1276 MatchAction::FillLimit(ClientOrderId::from("O-SELL-BEST")),
1277 MatchAction::FillLimit(ClientOrderId::from("O-SELL-LIMIT")),
1278 MatchAction::TriggerStop(ClientOrderId::from("O-SELL-STOP")),
1279 ],
1280 );
1281 }
1282
1283 #[rstest]
1284 fn test_is_limit_fillable_delegates_to_is_limit_matched_by_default() {
1285 let instrument_id = InstrumentId::from("AAPL.XNAS");
1286 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
1287 core.set_bid_raw(Price::from("100.00"));
1288 core.set_ask_raw(Price::from("101.00"));
1289
1290 assert!(core.is_limit_fillable(OrderSide::Buy, Price::from("101.00")));
1291 assert!(!core.is_limit_fillable(OrderSide::Buy, Price::from("100.00")));
1292 assert!(core.is_limit_fillable(OrderSide::Sell, Price::from("100.00")));
1293 assert!(!core.is_limit_fillable(OrderSide::Sell, Price::from("101.00")));
1294 }
1295
1296 #[rstest]
1297 fn test_is_limit_fillable_inside_spread_buy_at_bid() {
1298 let instrument_id = InstrumentId::from("AAPL.XNAS");
1299 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
1300 core.set_bid_raw(Price::from("100.00"));
1301 core.set_ask_raw(Price::from("101.00"));
1302 core.set_fill_limit_inside_spread(true);
1303
1304 assert!(core.is_limit_fillable(OrderSide::Buy, Price::from("100.00")));
1305 assert!(core.is_limit_fillable(OrderSide::Buy, Price::from("100.50")));
1306 assert!(!core.is_limit_fillable(OrderSide::Buy, Price::from("99.00")));
1307 }
1308
1309 #[rstest]
1310 fn test_is_limit_fillable_inside_spread_sell_at_ask() {
1311 let instrument_id = InstrumentId::from("AAPL.XNAS");
1312 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
1313 core.set_bid_raw(Price::from("100.00"));
1314 core.set_ask_raw(Price::from("101.00"));
1315 core.set_fill_limit_inside_spread(true);
1316
1317 assert!(core.is_limit_fillable(OrderSide::Sell, Price::from("101.00")));
1318 assert!(core.is_limit_fillable(OrderSide::Sell, Price::from("100.50")));
1319 assert!(!core.is_limit_fillable(OrderSide::Sell, Price::from("102.00")));
1320 }
1321
1322 #[rstest]
1323 fn test_is_limit_fillable_inside_spread_requires_both_quotes_present() {
1324 let instrument_id = InstrumentId::from("AAPL.XNAS");
1325 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
1326 core.set_fill_limit_inside_spread(true);
1327
1328 core.set_bid_raw(Price::from("100.00"));
1329 assert!(!core.is_limit_fillable(OrderSide::Buy, Price::from("100.00")));
1330
1331 let mut core2 = create_matching_core(instrument_id, Price::from("0.01"));
1332 core2.set_fill_limit_inside_spread(true);
1333 core2.set_ask_raw(Price::from("101.00"));
1334 assert!(!core2.is_limit_fillable(OrderSide::Sell, Price::from("101.00")));
1335
1336 let mut core3 = create_matching_core(instrument_id, Price::from("0.01"));
1337 core3.set_fill_limit_inside_spread(true);
1338 core3.set_bid_raw(Price::from("100.00"));
1339 core3.set_ask_raw(Price::from("101.00"));
1340 core3.ask = None;
1341 assert!(!core3.is_limit_fillable(OrderSide::Buy, Price::from("100.00")));
1342 }
1343
1344 #[rstest]
1345 fn test_iterate_fills_limit_inside_spread_when_enabled() {
1346 let instrument_id = InstrumentId::from("AAPL.XNAS");
1347 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
1348 core.set_bid_raw(Price::from("100.00"));
1349 core.set_ask_raw(Price::from("101.00"));
1350 core.set_fill_limit_inside_spread(true);
1351
1352 let order = OrderTestBuilder::new(OrderType::Limit)
1353 .instrument_id(instrument_id)
1354 .side(OrderSide::Buy)
1355 .price(Price::from("100.00"))
1356 .quantity(Quantity::from("100"))
1357 .build();
1358 let client_order_id = order.client_order_id();
1359 let match_info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1360 core.add_order(match_info);
1361
1362 let actions = core.iterate();
1363 assert_eq!(actions, vec![MatchAction::FillLimit(client_order_id)]);
1364 }
1365
1366 #[rstest]
1367 #[case(None, None, Price::from("100.00"), OrderSide::Buy, false)]
1368 #[case(None, None, Price::from("100.00"), OrderSide::Sell, false)]
1369 #[case(
1370 Some(Price::from("100.00")),
1371 Some(Price::from("101.00")),
1372 Price::from("102.00"),
1373 OrderSide::Buy,
1374 true
1375 )]
1376 #[case(
1377 Some(Price::from("100.00")),
1378 Some(Price::from("101.00")),
1379 Price::from("101.00"),
1380 OrderSide::Buy,
1381 true
1382 )]
1383 #[case(
1384 Some(Price::from("100.00")),
1385 Some(Price::from("101.00")),
1386 Price::from("100.00"),
1387 OrderSide::Buy,
1388 false
1389 )]
1390 #[case(
1391 Some(Price::from("100.00")),
1392 Some(Price::from("101.00")),
1393 Price::from("99.00"),
1394 OrderSide::Sell,
1395 true
1396 )]
1397 #[case(
1398 Some(Price::from("100.00")),
1399 Some(Price::from("101.00")),
1400 Price::from("100.00"),
1401 OrderSide::Sell,
1402 true
1403 )]
1404 #[case(
1405 Some(Price::from("100.00")),
1406 Some(Price::from("101.00")),
1407 Price::from("101.00"),
1408 OrderSide::Sell,
1409 false
1410 )]
1411 fn test_is_touch_triggered(
1412 #[case] bid: Option<Price>,
1413 #[case] ask: Option<Price>,
1414 #[case] trigger_price: Price,
1415 #[case] order_side: OrderSide,
1416 #[case] expected: bool,
1417 ) {
1418 let instrument_id = InstrumentId::from("AAPL.XNAS");
1419 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1420 matching_core.bid = bid;
1421 matching_core.ask = ask;
1422
1423 let result = matching_core.is_touch_triggered(order_side, trigger_price);
1424 assert_eq!(result, expected);
1425 }
1426
1427 #[rstest]
1428 fn test_update_price_increment_updates_increment_and_precision() {
1429 let instrument_id = InstrumentId::from("AAPL.XNAS");
1430 let mut matching_core = create_matching_core(instrument_id, Price::from("0.01"));
1431
1432 assert_eq!(matching_core.price_increment, Price::from("0.01"));
1433 assert_eq!(matching_core.price_precision(), 2);
1434
1435 matching_core.update_price_increment(Price::from("0.001"));
1436
1437 assert_eq!(matching_core.price_increment, Price::from("0.001"));
1438 assert_eq!(matching_core.price_precision(), 3);
1439 }
1440
1441 fn order_from_init(spec: OrderInitialized) -> OrderAny {
1442 OrderAny::from_events(vec![OrderEventAny::Initialized(spec)]).unwrap()
1443 }
1444
1445 #[rstest]
1446 fn test_get_order_finds_orders_on_either_side() {
1447 let instrument_id = InstrumentId::from("AAPL.XNAS");
1448 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
1449
1450 let buy = order_from_init(
1451 OrderInitializedSpec::builder()
1452 .instrument_id(instrument_id)
1453 .client_order_id(ClientOrderId::from("O-BUY"))
1454 .order_side(OrderSide::Buy)
1455 .order_type(OrderType::Limit)
1456 .quantity(Quantity::from("10"))
1457 .price(Price::from("100.00"))
1458 .build(),
1459 );
1460 let buy_id = buy.client_order_id();
1461 core.add_order(RestingOrder::from(&PassiveOrderAny::try_from(buy).unwrap()));
1462
1463 let sell = order_from_init(
1464 OrderInitializedSpec::builder()
1465 .instrument_id(instrument_id)
1466 .client_order_id(ClientOrderId::from("O-SELL"))
1467 .order_side(OrderSide::Sell)
1468 .order_type(OrderType::Limit)
1469 .quantity(Quantity::from("10"))
1470 .price(Price::from("101.00"))
1471 .build(),
1472 );
1473 let sell_id = sell.client_order_id();
1474 core.add_order(RestingOrder::from(
1475 &PassiveOrderAny::try_from(sell).unwrap(),
1476 ));
1477
1478 assert_eq!(
1479 core.get_order(buy_id).map(|o| o.client_order_id),
1480 Some(buy_id)
1481 );
1482 assert_eq!(
1483 core.get_order(sell_id).map(|o| o.client_order_id),
1484 Some(sell_id)
1485 );
1486 assert!(core.get_order(ClientOrderId::from("O-MISSING")).is_none());
1487 }
1488
1489 #[rstest]
1490 fn test_match_order_returns_none_when_neither_price_set() {
1491 let instrument_id = InstrumentId::from("AAPL.XNAS");
1492 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
1493 core.set_bid_raw(Price::from("100.00"));
1494 core.set_ask_raw(Price::from("101.00"));
1495
1496 let info = RestingOrder::new(
1497 ClientOrderId::from("O-NEITHER"),
1498 OrderSide::Buy,
1499 OrderType::MarketToLimit,
1500 None,
1501 None,
1502 true,
1503 );
1504 assert!(core.match_order(&info).is_none());
1505 }
1506
1507 #[rstest]
1508 fn test_from_passive_order_extracts_limit_price_for_stop_limit() {
1509 let order = order_from_init(
1510 OrderInitializedSpec::builder()
1511 .order_type(OrderType::StopLimit)
1512 .order_side(OrderSide::Buy)
1513 .quantity(Quantity::from("10"))
1514 .price(Price::from("101.00"))
1515 .trigger_price(Price::from("100.00"))
1516 .trigger_type(TriggerType::Default)
1517 .build(),
1518 );
1519
1520 let info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1521
1522 assert_eq!(info.trigger_price, Some(Price::from("100.00")));
1523 assert_eq!(info.limit_price, Some(Price::from("101.00")));
1524 assert!(info.is_activated);
1525 }
1526
1527 #[rstest]
1528 fn test_from_passive_order_extracts_limit_price_for_limit_if_touched() {
1529 let order = order_from_init(
1530 OrderInitializedSpec::builder()
1531 .order_type(OrderType::LimitIfTouched)
1532 .order_side(OrderSide::Sell)
1533 .quantity(Quantity::from("10"))
1534 .price(Price::from("99.00"))
1535 .trigger_price(Price::from("100.00"))
1536 .trigger_type(TriggerType::LastPrice)
1537 .build(),
1538 );
1539
1540 let info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1541
1542 assert_eq!(info.trigger_price, Some(Price::from("100.00")));
1543 assert_eq!(info.limit_price, Some(Price::from("99.00")));
1544 assert_eq!(info.trigger_type, Some(TriggerType::LastPrice));
1545 assert!(info.is_activated);
1546 }
1547
1548 #[rstest]
1549 fn test_from_passive_order_extracts_is_activated_for_trailing_stop_market() {
1550 let order = order_from_init(
1551 OrderInitializedSpec::builder()
1552 .order_type(OrderType::TrailingStopMarket)
1553 .order_side(OrderSide::Buy)
1554 .quantity(Quantity::from("10"))
1555 .trigger_price(Price::from("101.00"))
1556 .trigger_type(TriggerType::Default)
1557 .trailing_offset(Decimal::from(1))
1558 .trailing_offset_type(TrailingOffsetType::Price)
1559 .build(),
1560 );
1561
1562 let info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1563
1564 assert_eq!(info.trigger_price, Some(Price::from("101.00")));
1565 assert_eq!(info.limit_price, None);
1566 assert!(!info.is_activated);
1567 }
1568
1569 #[rstest]
1570 fn test_from_passive_order_extracts_limit_and_is_activated_for_trailing_stop_limit() {
1571 let order = order_from_init(
1572 OrderInitializedSpec::builder()
1573 .order_type(OrderType::TrailingStopLimit)
1574 .order_side(OrderSide::Sell)
1575 .quantity(Quantity::from("10"))
1576 .price(Price::from("99.00"))
1577 .trigger_price(Price::from("100.00"))
1578 .trigger_type(TriggerType::Default)
1579 .limit_offset(Decimal::from(1))
1580 .trailing_offset(Decimal::from(1))
1581 .trailing_offset_type(TrailingOffsetType::Price)
1582 .build(),
1583 );
1584
1585 let info = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
1586
1587 assert_eq!(info.trigger_price, Some(Price::from("100.00")));
1588 assert_eq!(info.limit_price, Some(Price::from("99.00")));
1589 assert!(!info.is_activated);
1590 }
1591
1592 fn limit_order(side: OrderSide, price: &str, id: &str) -> RestingOrder {
1593 let order = order_from_init(
1594 OrderInitializedSpec::builder()
1595 .client_order_id(ClientOrderId::from(id))
1596 .order_type(OrderType::Limit)
1597 .order_side(side)
1598 .quantity(Quantity::from("10"))
1599 .price(Price::from(price))
1600 .build(),
1601 );
1602 RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap())
1603 }
1604
1605 fn stop_order(side: OrderSide, trigger: &str, id: &str) -> RestingOrder {
1606 let order = order_from_init(
1607 OrderInitializedSpec::builder()
1608 .client_order_id(ClientOrderId::from(id))
1609 .order_type(OrderType::StopMarket)
1610 .order_side(side)
1611 .quantity(Quantity::from("10"))
1612 .trigger_price(Price::from(trigger))
1613 .trigger_type(TriggerType::Default)
1614 .build(),
1615 );
1616 RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap())
1617 }
1618
1619 fn stop_limit_order(side: OrderSide, trigger: &str, limit: &str, id: &str) -> RestingOrder {
1620 let order = order_from_init(
1621 OrderInitializedSpec::builder()
1622 .client_order_id(ClientOrderId::from(id))
1623 .order_type(OrderType::StopLimit)
1624 .order_side(side)
1625 .quantity(Quantity::from("10"))
1626 .price(Price::from(limit))
1627 .trigger_price(Price::from(trigger))
1628 .trigger_type(TriggerType::Default)
1629 .build(),
1630 );
1631 RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap())
1632 }
1633
1634 #[rstest]
1635 fn test_iterate_bids_returns_limits_in_descending_price_order() {
1636 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1637 core.set_ask_raw(Price::from("99.00"));
1638
1639 core.add_order(limit_order(OrderSide::Buy, "100.00", "O-MID"));
1640 core.add_order(limit_order(OrderSide::Buy, "100.50", "O-HIGH"));
1641 core.add_order(limit_order(OrderSide::Buy, "99.50", "O-LOW"));
1642
1643 let actions = core.iterate_bids();
1644 assert_eq!(
1645 actions,
1646 vec![
1647 MatchAction::FillLimit(ClientOrderId::from("O-HIGH")),
1648 MatchAction::FillLimit(ClientOrderId::from("O-MID")),
1649 MatchAction::FillLimit(ClientOrderId::from("O-LOW")),
1650 ],
1651 );
1652 }
1653
1654 #[rstest]
1655 fn test_iterate_asks_returns_limits_in_ascending_price_order() {
1656 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1657 core.set_bid_raw(Price::from("101.00"));
1658
1659 core.add_order(limit_order(OrderSide::Sell, "100.50", "O-MID"));
1660 core.add_order(limit_order(OrderSide::Sell, "100.00", "O-LOW"));
1661 core.add_order(limit_order(OrderSide::Sell, "100.75", "O-HIGH"));
1662
1663 let actions = core.iterate_asks();
1664 assert_eq!(
1665 actions,
1666 vec![
1667 MatchAction::FillLimit(ClientOrderId::from("O-LOW")),
1668 MatchAction::FillLimit(ClientOrderId::from("O-MID")),
1669 MatchAction::FillLimit(ClientOrderId::from("O-HIGH")),
1670 ],
1671 );
1672 }
1673
1674 #[rstest]
1675 fn test_iterate_limits_preserves_fifo_within_same_price() {
1676 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1677 core.set_ask_raw(Price::from("99.00"));
1678
1679 for id in ["O-1", "O-2", "O-3", "O-4"] {
1680 core.add_order(limit_order(OrderSide::Buy, "100.00", id));
1681 }
1682
1683 let actions = core.iterate_bids();
1684 assert_eq!(
1685 actions,
1686 vec![
1687 MatchAction::FillLimit(ClientOrderId::from("O-1")),
1688 MatchAction::FillLimit(ClientOrderId::from("O-2")),
1689 MatchAction::FillLimit(ClientOrderId::from("O-3")),
1690 MatchAction::FillLimit(ClientOrderId::from("O-4")),
1691 ],
1692 );
1693 }
1694
1695 #[rstest]
1696 fn test_buy_stops_trigger_in_ascending_price_order_when_ask_crosses_multiple() {
1697 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1698 core.set_ask_raw(Price::from("106.00"));
1699
1700 core.add_order(stop_order(OrderSide::Buy, "105.00", "O-FAR"));
1701 core.add_order(stop_order(OrderSide::Buy, "101.00", "O-NEAR"));
1702
1703 let actions = core.iterate_bids();
1704 assert_eq!(
1705 actions,
1706 vec![
1707 MatchAction::TriggerStop(ClientOrderId::from("O-NEAR")),
1708 MatchAction::TriggerStop(ClientOrderId::from("O-FAR")),
1709 ],
1710 );
1711 }
1712
1713 #[rstest]
1714 fn test_sell_stops_trigger_in_descending_price_order_when_bid_crosses_multiple() {
1715 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1716 core.set_bid_raw(Price::from("94.00"));
1717
1718 core.add_order(stop_order(OrderSide::Sell, "95.00", "O-FAR"));
1719 core.add_order(stop_order(OrderSide::Sell, "99.00", "O-NEAR"));
1720
1721 let actions = core.iterate_asks();
1722 assert_eq!(
1723 actions,
1724 vec![
1725 MatchAction::TriggerStop(ClientOrderId::from("O-NEAR")),
1726 MatchAction::TriggerStop(ClientOrderId::from("O-FAR")),
1727 ],
1728 );
1729 }
1730
1731 #[rstest]
1732 fn test_iterate_stops_preserves_fifo_within_same_trigger() {
1733 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1734 core.set_ask_raw(Price::from("106.00"));
1735
1736 for id in ["O-S1", "O-S2", "O-S3"] {
1737 core.add_order(stop_order(OrderSide::Buy, "101.00", id));
1738 }
1739
1740 let actions = core.iterate_bids();
1741 assert_eq!(
1742 actions,
1743 vec![
1744 MatchAction::TriggerStop(ClientOrderId::from("O-S1")),
1745 MatchAction::TriggerStop(ClientOrderId::from("O-S2")),
1746 MatchAction::TriggerStop(ClientOrderId::from("O-S3")),
1747 ],
1748 );
1749 }
1750
1751 #[rstest]
1752 fn test_iterate_bids_processes_limits_before_stops() {
1753 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1754 core.set_ask_raw(Price::from("106.00"));
1755
1756 core.add_order(limit_order(OrderSide::Buy, "110.00", "O-LMT"));
1757 core.add_order(stop_order(OrderSide::Buy, "101.00", "O-STP"));
1758
1759 let actions = core.iterate_bids();
1760 assert_eq!(
1761 actions,
1762 vec![
1763 MatchAction::FillLimit(ClientOrderId::from("O-LMT")),
1764 MatchAction::TriggerStop(ClientOrderId::from("O-STP")),
1765 ],
1766 );
1767 }
1768
1769 #[rstest]
1770 fn test_iterate_asks_processes_limits_before_stops() {
1771 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1772 core.set_bid_raw(Price::from("94.00"));
1773
1774 core.add_order(limit_order(OrderSide::Sell, "90.00", "O-LMT"));
1775 core.add_order(stop_order(OrderSide::Sell, "99.00", "O-STP"));
1776
1777 let actions = core.iterate_asks();
1778 assert_eq!(
1779 actions,
1780 vec![
1781 MatchAction::FillLimit(ClientOrderId::from("O-LMT")),
1782 MatchAction::TriggerStop(ClientOrderId::from("O-STP")),
1783 ],
1784 );
1785 }
1786
1787 #[rstest]
1788 fn test_stop_limit_routed_to_stop_book_keyed_by_trigger() {
1789 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1790 core.set_ask_raw(Price::from("106.00"));
1791
1792 core.add_order(stop_limit_order(
1794 OrderSide::Buy,
1795 "105.00",
1796 "110.00",
1797 "O-FAR",
1798 ));
1799 core.add_order(stop_limit_order(
1800 OrderSide::Buy,
1801 "101.00",
1802 "110.00",
1803 "O-NEAR",
1804 ));
1805
1806 let actions = core.iterate_bids();
1807 assert_eq!(
1808 actions,
1809 vec![
1810 MatchAction::TriggerStop(ClientOrderId::from("O-NEAR")),
1811 MatchAction::TriggerStop(ClientOrderId::from("O-FAR")),
1812 ],
1813 );
1814 }
1815
1816 #[rstest]
1817 fn test_iterate_full_walk_combines_bids_then_asks_each_with_limits_then_stops() {
1818 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1820 core.set_bid_raw(Price::from("94.00"));
1821 core.set_ask_raw(Price::from("106.00"));
1822
1823 core.add_order(limit_order(OrderSide::Buy, "110.00", "O-B-LMT-HIGH"));
1824 core.add_order(limit_order(OrderSide::Buy, "107.00", "O-B-LMT-LOW"));
1825 core.add_order(stop_order(OrderSide::Buy, "105.00", "O-B-STP-FAR"));
1826 core.add_order(stop_order(OrderSide::Buy, "101.00", "O-B-STP-NEAR"));
1827
1828 core.add_order(limit_order(OrderSide::Sell, "90.00", "O-A-LMT-LOW"));
1829 core.add_order(limit_order(OrderSide::Sell, "93.00", "O-A-LMT-HIGH"));
1830 core.add_order(stop_order(OrderSide::Sell, "95.00", "O-A-STP-FAR"));
1831 core.add_order(stop_order(OrderSide::Sell, "99.00", "O-A-STP-NEAR"));
1832
1833 let actions = core.iterate();
1834 assert_eq!(
1835 actions,
1836 vec![
1837 MatchAction::FillLimit(ClientOrderId::from("O-B-LMT-HIGH")),
1838 MatchAction::FillLimit(ClientOrderId::from("O-B-LMT-LOW")),
1839 MatchAction::TriggerStop(ClientOrderId::from("O-B-STP-NEAR")),
1840 MatchAction::TriggerStop(ClientOrderId::from("O-B-STP-FAR")),
1841 MatchAction::FillLimit(ClientOrderId::from("O-A-LMT-LOW")),
1842 MatchAction::FillLimit(ClientOrderId::from("O-A-LMT-HIGH")),
1843 MatchAction::TriggerStop(ClientOrderId::from("O-A-STP-NEAR")),
1844 MatchAction::TriggerStop(ClientOrderId::from("O-A-STP-FAR")),
1845 ],
1846 );
1847 }
1848
1849 #[rstest]
1850 fn test_pending_orders_skipped_in_iterate_but_visible_in_get_orders() {
1851 let instrument_id = InstrumentId::from("AAPL.XNAS");
1852 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
1853 core.set_bid_raw(Price::from("99.00"));
1854 core.set_ask_raw(Price::from("100.00"));
1855
1856 core.add_order(limit_order(OrderSide::Buy, "100.00", "O-LMT"));
1857
1858 let pending = RestingOrder::new(
1859 ClientOrderId::from("O-PENDING"),
1860 OrderSide::Buy,
1861 OrderType::MarketToLimit,
1862 None,
1863 None,
1864 true,
1865 );
1866 core.add_order(pending);
1867
1868 assert_eq!(
1869 core.iterate_bids(),
1870 vec![MatchAction::FillLimit(ClientOrderId::from("O-LMT"))],
1871 );
1872
1873 let bid_ids: Vec<_> = core
1874 .get_orders_bid()
1875 .iter()
1876 .map(|o| o.client_order_id)
1877 .collect();
1878 assert_eq!(
1879 bid_ids,
1880 vec![
1881 ClientOrderId::from("O-LMT"),
1882 ClientOrderId::from("O-PENDING"),
1883 ],
1884 );
1885 }
1886
1887 #[rstest]
1888 fn test_modify_then_readd_moves_order_to_back_of_new_level() {
1889 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1890 core.set_ask_raw(Price::from("99.00"));
1891
1892 core.add_order(limit_order(OrderSide::Buy, "100.00", "O-A"));
1893 core.add_order(limit_order(OrderSide::Buy, "100.00", "O-B"));
1894 core.add_order(limit_order(OrderSide::Buy, "100.00", "O-C"));
1895
1896 core.delete_order(ClientOrderId::from("O-A")).unwrap();
1897 core.add_order(limit_order(OrderSide::Buy, "100.50", "O-A"));
1898
1899 core.delete_order(ClientOrderId::from("O-B")).unwrap();
1901 core.add_order(limit_order(OrderSide::Buy, "100.00", "O-B"));
1902
1903 let actions = core.iterate_bids();
1904 assert_eq!(
1905 actions,
1906 vec![
1907 MatchAction::FillLimit(ClientOrderId::from("O-A")),
1908 MatchAction::FillLimit(ClientOrderId::from("O-C")),
1909 MatchAction::FillLimit(ClientOrderId::from("O-B")),
1910 ],
1911 );
1912 }
1913
1914 #[rstest]
1915 fn test_delete_unknown_order_returns_not_found() {
1916 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1917 let result = core.delete_order(ClientOrderId::from("O-MISSING"));
1918 assert!(matches!(result, Err(OrderError::NotFound(_))));
1919 }
1920
1921 #[rstest]
1922 #[case::buy(OrderSide::Buy)]
1923 #[case::sell(OrderSide::Sell)]
1924 fn test_pending_order_lookup_delete_and_conversion(#[case] side: OrderSide) {
1925 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1926 core.set_bid_raw(Price::from("100.00"));
1927 core.set_ask_raw(Price::from("100.00"));
1928 let first = pending_order(side, "O-FIRST");
1929 let second = pending_order(side, "O-SECOND");
1930 core.add_order(first);
1931 core.add_order(second);
1932
1933 assert_eq!(core.get_order(first.client_order_id), Some(&first));
1934 assert_eq!(core.get_order(second.client_order_id), Some(&second));
1935 assert!(core.order_exists(first.client_order_id));
1936 assert!(core.order_exists(second.client_order_id));
1937 assert_eq!(core.get_orders(), vec![first, second]);
1938 assert!(core.iterate().is_empty());
1939
1940 core.delete_order(second.client_order_id).unwrap();
1941
1942 assert_eq!(core.get_order(second.client_order_id), None);
1943 assert!(!core.order_exists(second.client_order_id));
1944 assert_eq!(core.get_order(first.client_order_id), Some(&first));
1945 assert!(core.order_exists(first.client_order_id));
1946 assert_eq!(core.get_orders(), vec![first]);
1947
1948 core.delete_order(first.client_order_id).unwrap();
1949
1950 assert_eq!(core.get_order(first.client_order_id), None);
1951 assert!(!core.order_exists(first.client_order_id));
1952 assert!(core.get_orders().is_empty());
1953
1954 let converted = RestingOrder {
1955 limit_price: Some(Price::from("100.00")),
1956 ..first
1957 };
1958 core.add_order(converted);
1959
1960 assert_eq!(core.get_order(first.client_order_id), Some(&converted));
1961 assert!(core.order_exists(first.client_order_id));
1962 assert_eq!(core.get_orders(), vec![converted]);
1963 assert_eq!(
1964 core.iterate(),
1965 vec![MatchAction::FillLimit(first.client_order_id)]
1966 );
1967 }
1968
1969 #[rstest]
1970 fn test_order_views_preserve_price_time_priority() {
1971 let mut core = create_matching_core(InstrumentId::from("AAPL.XNAS"), Price::from("0.01"));
1972 let bids = [
1973 limit_order(OrderSide::Buy, "101.00", "O-B-LMT-1"),
1974 limit_order(OrderSide::Buy, "101.00", "O-B-LMT-2"),
1975 limit_order(OrderSide::Buy, "100.00", "O-B-LMT-3"),
1976 stop_order(OrderSide::Buy, "102.00", "O-B-STP-1"),
1977 stop_order(OrderSide::Buy, "102.00", "O-B-STP-2"),
1978 stop_order(OrderSide::Buy, "103.00", "O-B-STP-3"),
1979 pending_order(OrderSide::Buy, "O-B-PENDING-1"),
1980 pending_order(OrderSide::Buy, "O-B-PENDING-2"),
1981 ];
1982 let asks = [
1983 limit_order(OrderSide::Sell, "100.00", "O-A-LMT-1"),
1984 limit_order(OrderSide::Sell, "100.00", "O-A-LMT-2"),
1985 limit_order(OrderSide::Sell, "101.00", "O-A-LMT-3"),
1986 stop_order(OrderSide::Sell, "99.00", "O-A-STP-1"),
1987 stop_order(OrderSide::Sell, "99.00", "O-A-STP-2"),
1988 stop_order(OrderSide::Sell, "98.00", "O-A-STP-3"),
1989 pending_order(OrderSide::Sell, "O-A-PENDING-1"),
1990 pending_order(OrderSide::Sell, "O-A-PENDING-2"),
1991 ];
1992
1993 for index in [6, 5, 2, 3, 0, 4, 1, 7] {
1994 core.add_order(asks[index]);
1995 core.add_order(bids[index]);
1996 }
1997 let orders = [bids, asks].concat();
1998
1999 assert_eq!(core.iter_bid_orders().copied().collect::<Vec<_>>(), bids);
2000 assert_eq!(core.get_orders_bid(), bids);
2001 assert_eq!(core.iter_ask_orders().copied().collect::<Vec<_>>(), asks);
2002 assert_eq!(core.get_orders_ask(), asks);
2003 assert_eq!(core.iter_orders().copied().collect::<Vec<_>>(), orders);
2004 assert_eq!(core.get_orders(), orders);
2005 }
2006
2007 #[rstest]
2008 #[case::buy(OrderSide::Buy, "99.00", "101.00")]
2009 #[case::sell(OrderSide::Sell, "101.00", "99.00")]
2010 fn test_touch_order_last_or_bid_ask_prefers_last_then_falls_back(
2011 #[case] side: OrderSide,
2012 #[case] crossed_price: &str,
2013 #[case] uncrossed_price: &str,
2014 #[values(OrderType::MarketIfTouched, OrderType::LimitIfTouched)] order_type: OrderType,
2015 ) {
2016 let instrument_id = InstrumentId::from("AAPL.XNAS");
2017 let mut core = create_matching_core(instrument_id, Price::from("0.01"));
2018 let trigger_price = Price::from("100.00");
2019 let crossed = Price::from(crossed_price);
2020 let uncrossed = Price::from(uncrossed_price);
2021 let order = OrderTestBuilder::new(order_type)
2022 .instrument_id(instrument_id)
2023 .client_order_id(ClientOrderId::from("O-TOUCH"))
2024 .side(side)
2025 .price(uncrossed)
2026 .trigger_price(trigger_price)
2027 .trigger_type(TriggerType::LastOrBidAsk)
2028 .quantity(Quantity::from("10"))
2029 .build();
2030 let resting = RestingOrder::from(&PassiveOrderAny::try_from(order).unwrap());
2031 let action = Some(MatchAction::TriggerStop(resting.client_order_id));
2032
2033 match side {
2034 OrderSide::Buy => {
2035 core.set_bid_raw(Price::from("98.00"));
2036 core.set_ask_raw(crossed);
2037 }
2038 OrderSide::Sell => {
2039 core.set_bid_raw(crossed);
2040 core.set_ask_raw(Price::from("102.00"));
2041 }
2042 }
2043 core.set_last_raw(uncrossed);
2044
2045 assert_eq!(core.match_order(&resting), None);
2046
2047 match side {
2048 OrderSide::Buy => core.set_ask_raw(uncrossed),
2049 OrderSide::Sell => core.set_bid_raw(uncrossed),
2050 }
2051 core.set_last_raw(trigger_price);
2052
2053 assert_eq!(core.match_order(&resting), action);
2054
2055 core.last = None;
2056
2057 assert_eq!(core.match_order(&resting), None);
2058
2059 match side {
2060 OrderSide::Buy => core.set_ask_raw(crossed),
2061 OrderSide::Sell => core.set_bid_raw(crossed),
2062 }
2063
2064 assert_eq!(core.match_order(&resting), action);
2065
2066 match side {
2067 OrderSide::Buy => core.ask = None,
2068 OrderSide::Sell => core.bid = None,
2069 }
2070
2071 assert_eq!(core.match_order(&resting), None);
2072 }
2073
2074 fn pending_order(side: OrderSide, id: &str) -> RestingOrder {
2075 RestingOrder::new(
2076 ClientOrderId::from(id),
2077 side,
2078 OrderType::MarketToLimit,
2079 None,
2080 None,
2081 true,
2082 )
2083 }
2084}