1#![allow(dead_code)]
18#![allow(unused_variables)]
19
20use std::{
21 any::Any,
22 cell::RefCell,
23 cmp::min,
24 collections::HashMap,
25 fmt::Debug,
26 ops::{Add, Sub},
27 rc::Rc,
28};
29
30use chrono::TimeDelta;
31use nautilus_common::{
32 cache::Cache,
33 clock::Clock,
34 messages::execution::{BatchCancelOrders, CancelAllOrders, CancelOrder, ModifyOrder},
35 msgbus,
36};
37use nautilus_core::{UUID4, UnixNanos};
38use nautilus_model::{
39 data::{Bar, BarType, OrderBookDelta, OrderBookDeltas, QuoteTick, TradeTick, order::BookOrder},
40 enums::{
41 AccountType, AggregationSource, AggressorSide, BarAggregation, BookType, ContingencyType,
42 LiquiditySide, MarketStatus, MarketStatusAction, OmsType, OrderSide, OrderSideSpecified,
43 OrderStatus, OrderType, PriceType, TimeInForce,
44 },
45 events::{
46 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderEventAny, OrderExpired,
47 OrderFilled, OrderModifyRejected, OrderRejected, OrderTriggered, OrderUpdated,
48 },
49 identifiers::{
50 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TraderId, Venue,
51 VenueOrderId,
52 },
53 instruments::{EXPIRING_INSTRUMENT_TYPES, Instrument, InstrumentAny},
54 orderbook::OrderBook,
55 orders::{Order, OrderAny, PassiveOrderAny, StopOrderAny},
56 position::Position,
57 types::{Currency, Money, Price, Quantity, fixed::FIXED_PRECISION},
58};
59use ustr::Ustr;
60
61use crate::{
62 matching_core::OrderMatchingCore,
63 matching_engine::{config::OrderMatchingEngineConfig, ids_generator::IdsGenerator},
64 models::{
65 fee::{FeeModel, FeeModelAny},
66 fill::FillModel,
67 },
68 trailing::trailing_stop_calculate,
69};
70
71pub struct OrderMatchingEngine {
73 pub venue: Venue,
75 pub instrument: InstrumentAny,
77 pub raw_id: u32,
79 pub book_type: BookType,
81 pub oms_type: OmsType,
83 pub account_type: AccountType,
85 pub market_status: MarketStatus,
87 pub config: OrderMatchingEngineConfig,
89 clock: Rc<RefCell<dyn Clock>>,
90 cache: Rc<RefCell<Cache>>,
91 book: OrderBook,
92 pub core: OrderMatchingCore,
93 fill_model: FillModel,
94 fee_model: FeeModelAny,
95 target_bid: Option<Price>,
96 target_ask: Option<Price>,
97 target_last: Option<Price>,
98 last_bar_bid: Option<Bar>,
99 last_bar_ask: Option<Bar>,
100 execution_bar_types: HashMap<InstrumentId, BarType>,
101 execution_bar_deltas: HashMap<BarType, TimeDelta>,
102 account_ids: HashMap<TraderId, AccountId>,
103 cached_filled_qty: HashMap<ClientOrderId, Quantity>,
104 ids_generator: IdsGenerator,
105}
106
107impl Debug for OrderMatchingEngine {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct(stringify!(OrderMatchingEngine))
110 .field("venue", &self.venue)
111 .field("instrument", &self.instrument.id())
112 .finish()
113 }
114}
115
116impl OrderMatchingEngine {
117 #[allow(clippy::too_many_arguments)]
119 pub fn new(
120 instrument: InstrumentAny,
121 raw_id: u32,
122 fill_model: FillModel,
123 fee_model: FeeModelAny,
124 book_type: BookType,
125 oms_type: OmsType,
126 account_type: AccountType,
127 clock: Rc<RefCell<dyn Clock>>,
128 cache: Rc<RefCell<Cache>>,
129 config: OrderMatchingEngineConfig,
130 ) -> Self {
131 let book = OrderBook::new(instrument.id(), book_type);
132 let core = OrderMatchingCore::new(
133 instrument.id(),
134 instrument.price_increment(),
135 None, None, None, );
139 let ids_generator = IdsGenerator::new(
140 instrument.id().venue,
141 oms_type,
142 raw_id,
143 config.use_random_ids,
144 config.use_position_ids,
145 cache.clone(),
146 );
147
148 Self {
149 venue: instrument.id().venue,
150 instrument,
151 raw_id,
152 fill_model,
153 fee_model,
154 book_type,
155 oms_type,
156 account_type,
157 clock,
158 cache,
159 book,
160 core,
161 market_status: MarketStatus::Open,
162 config,
163 target_bid: None,
164 target_ask: None,
165 target_last: None,
166 last_bar_bid: None,
167 last_bar_ask: None,
168 execution_bar_types: HashMap::new(),
169 execution_bar_deltas: HashMap::new(),
170 account_ids: HashMap::new(),
171 cached_filled_qty: HashMap::new(),
172 ids_generator,
173 }
174 }
175
176 pub fn reset(&mut self) {
182 self.book.clear(0, UnixNanos::default());
183 self.execution_bar_types.clear();
184 self.execution_bar_deltas.clear();
185 self.account_ids.clear();
186 self.cached_filled_qty.clear();
187 self.core.reset();
188 self.target_bid = None;
189 self.target_ask = None;
190 self.target_last = None;
191 self.ids_generator.reset();
192
193 log::info!("Reset {}", self.instrument.id());
194 }
195
196 pub const fn set_fill_model(&mut self, fill_model: FillModel) {
198 self.fill_model = fill_model;
199 }
200
201 #[must_use]
202 pub fn best_bid_price(&self) -> Option<Price> {
204 self.book.best_bid_price()
205 }
206
207 #[must_use]
208 pub fn best_ask_price(&self) -> Option<Price> {
210 self.book.best_ask_price()
211 }
212
213 #[must_use]
214 pub const fn get_book(&self) -> &OrderBook {
216 &self.book
217 }
218
219 #[must_use]
220 pub const fn get_open_bid_orders(&self) -> &[PassiveOrderAny] {
222 self.core.get_orders_bid()
223 }
224
225 #[must_use]
226 pub const fn get_open_ask_orders(&self) -> &[PassiveOrderAny] {
228 self.core.get_orders_ask()
229 }
230
231 #[must_use]
232 pub fn get_open_orders(&self) -> Vec<PassiveOrderAny> {
234 let mut orders = Vec::new();
236 orders.extend_from_slice(self.core.get_orders_bid());
237 orders.extend_from_slice(self.core.get_orders_ask());
238 orders
239 }
240
241 #[must_use]
242 pub fn order_exists(&self, client_order_id: ClientOrderId) -> bool {
244 self.core.order_exists(client_order_id)
245 }
246
247 pub fn process_order_book_delta(&mut self, delta: &OrderBookDelta) -> anyhow::Result<()> {
255 log::debug!("Processing {delta}");
256
257 if self.book_type == BookType::L2_MBP || self.book_type == BookType::L3_MBO {
258 self.book.apply_delta(delta)?;
259 }
260
261 self.iterate(delta.ts_init);
262 Ok(())
263 }
264
265 pub fn process_order_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
271 log::debug!("Processing {deltas}");
272
273 if self.book_type == BookType::L2_MBP || self.book_type == BookType::L3_MBO {
274 self.book.apply_deltas(deltas)?;
275 }
276
277 self.iterate(deltas.ts_init);
278 Ok(())
279 }
280
281 pub fn process_quote_tick(&mut self, quote: &QuoteTick) {
285 log::debug!("Processing {quote}");
286
287 if self.book_type == BookType::L1_MBP {
288 self.book.update_quote_tick(quote).unwrap();
289 }
290
291 self.iterate(quote.ts_init);
292 }
293
294 pub fn process_bar(&mut self, bar: &Bar) {
298 log::debug!("Processing {bar}");
299
300 if !self.config.bar_execution || self.book_type != BookType::L1_MBP {
302 return;
303 }
304
305 let bar_type = bar.bar_type;
306 if bar_type.aggregation_source() == AggregationSource::Internal {
308 return;
309 }
310
311 if bar_type.spec().aggregation == BarAggregation::Month {
313 return;
314 }
315
316 let execution_bar_type =
317 if let Some(execution_bar_type) = self.execution_bar_types.get(&bar.instrument_id()) {
318 execution_bar_type.to_owned()
319 } else {
320 self.execution_bar_types
321 .insert(bar.instrument_id(), bar_type);
322 self.execution_bar_deltas
323 .insert(bar_type, bar_type.spec().timedelta());
324 bar_type
325 };
326
327 if execution_bar_type != bar_type {
328 let mut bar_type_timedelta = self.execution_bar_deltas.get(&bar_type).copied();
329 if bar_type_timedelta.is_none() {
330 bar_type_timedelta = Some(bar_type.spec().timedelta());
331 self.execution_bar_deltas
332 .insert(bar_type, bar_type_timedelta.unwrap());
333 }
334 if self.execution_bar_deltas.get(&execution_bar_type).unwrap()
335 >= &bar_type_timedelta.unwrap()
336 {
337 self.execution_bar_types
338 .insert(bar_type.instrument_id(), bar_type);
339 } else {
340 return;
341 }
342 }
343
344 match bar_type.spec().price_type {
345 PriceType::Last | PriceType::Mid => self.process_trade_ticks_from_bar(bar),
346 PriceType::Bid => {
347 self.last_bar_bid = Some(bar.to_owned());
348 self.process_quote_ticks_from_bar(bar);
349 }
350 PriceType::Ask => {
351 self.last_bar_ask = Some(bar.to_owned());
352 self.process_quote_ticks_from_bar(bar);
353 }
354 PriceType::Mark => panic!("Not implemented"),
355 }
356 }
357
358 fn process_trade_ticks_from_bar(&mut self, bar: &Bar) {
359 let size = Quantity::new(bar.volume.as_f64() / 4.0, bar.volume.precision);
361 let aggressor_side = if !self.core.is_last_initialized || bar.open > self.core.last.unwrap()
362 {
363 AggressorSide::Buyer
364 } else {
365 AggressorSide::Seller
366 };
367
368 let mut trade_tick = TradeTick::new(
370 bar.instrument_id(),
371 bar.open,
372 size,
373 aggressor_side,
374 self.ids_generator.generate_trade_id(),
375 bar.ts_init,
376 bar.ts_init,
377 );
378
379 if !self.core.is_last_initialized {
382 self.book.update_trade_tick(&trade_tick).unwrap();
383 self.iterate(trade_tick.ts_init);
384 self.core.set_last_raw(trade_tick.price);
385 }
386
387 if self.core.last.is_some_and(|last| bar.high > last) {
390 trade_tick.price = bar.high;
391 trade_tick.aggressor_side = AggressorSide::Buyer;
392 trade_tick.trade_id = self.ids_generator.generate_trade_id();
393
394 self.book.update_trade_tick(&trade_tick).unwrap();
395 self.iterate(trade_tick.ts_init);
396
397 self.core.set_last_raw(trade_tick.price);
398 }
399
400 if self.core.last.is_some_and(|last| bar.low < last) {
404 trade_tick.price = bar.low;
405 trade_tick.aggressor_side = AggressorSide::Seller;
406 trade_tick.trade_id = self.ids_generator.generate_trade_id();
407
408 self.book.update_trade_tick(&trade_tick).unwrap();
409 self.iterate(trade_tick.ts_init);
410
411 self.core.set_last_raw(trade_tick.price);
412 }
413
414 if self.core.last.is_some_and(|last| bar.close != last) {
419 trade_tick.price = bar.close;
420 trade_tick.aggressor_side = if bar.close > self.core.last.unwrap() {
421 AggressorSide::Buyer
422 } else {
423 AggressorSide::Seller
424 };
425 trade_tick.trade_id = self.ids_generator.generate_trade_id();
426
427 self.book.update_trade_tick(&trade_tick).unwrap();
428 self.iterate(trade_tick.ts_init);
429
430 self.core.set_last_raw(trade_tick.price);
431 }
432 }
433
434 fn process_quote_ticks_from_bar(&mut self, bar: &Bar) {
435 if self.last_bar_bid.is_none()
437 || self.last_bar_ask.is_none()
438 || self.last_bar_bid.unwrap().ts_init != self.last_bar_ask.unwrap().ts_init
439 {
440 return;
441 }
442 let bid_bar = self.last_bar_bid.unwrap();
443 let ask_bar = self.last_bar_ask.unwrap();
444 let bid_size = Quantity::new(bid_bar.volume.as_f64() / 4.0, bar.volume.precision);
445 let ask_size = Quantity::new(ask_bar.volume.as_f64() / 4.0, bar.volume.precision);
446
447 let mut quote_tick = QuoteTick::new(
449 self.book.instrument_id,
450 bid_bar.open,
451 ask_bar.open,
452 bid_size,
453 ask_size,
454 bid_bar.ts_init,
455 bid_bar.ts_init,
456 );
457
458 self.book.update_quote_tick("e_tick).unwrap();
460 self.iterate(quote_tick.ts_init);
461
462 quote_tick.bid_price = bid_bar.high;
464 quote_tick.ask_price = ask_bar.high;
465 self.book.update_quote_tick("e_tick).unwrap();
466 self.iterate(quote_tick.ts_init);
467
468 quote_tick.bid_price = bid_bar.low;
470 quote_tick.ask_price = ask_bar.low;
471 self.book.update_quote_tick("e_tick).unwrap();
472 self.iterate(quote_tick.ts_init);
473
474 quote_tick.bid_price = bid_bar.close;
476 quote_tick.ask_price = ask_bar.close;
477 self.book.update_quote_tick("e_tick).unwrap();
478 self.iterate(quote_tick.ts_init);
479
480 self.last_bar_bid = None;
482 self.last_bar_ask = None;
483 }
484
485 pub fn process_trade_tick(&mut self, trade: &TradeTick) {
489 log::debug!("Processing {trade}");
490
491 if self.book_type == BookType::L1_MBP {
492 self.book.update_trade_tick(trade).unwrap();
493 }
494 self.core.set_last_raw(trade.price);
495
496 self.iterate(trade.ts_init);
497 }
498
499 pub fn process_status(&mut self, action: MarketStatusAction) {
500 log::debug!("Processing {action}");
501
502 if self.market_status == MarketStatus::Closed
504 && (action == MarketStatusAction::Trading || action == MarketStatusAction::PreOpen)
505 {
506 self.market_status = MarketStatus::Open;
507 }
508 if self.market_status == MarketStatus::Open && action == MarketStatusAction::Pause {
510 self.market_status = MarketStatus::Paused;
511 }
512 if self.market_status == MarketStatus::Open && action == MarketStatusAction::Suspend {
514 self.market_status = MarketStatus::Suspended;
515 }
516 if self.market_status == MarketStatus::Open
518 && (action == MarketStatusAction::Halt || action == MarketStatusAction::Close)
519 {
520 self.market_status = MarketStatus::Closed;
521 }
522 }
523
524 #[allow(clippy::needless_return)]
530 pub fn process_order(&mut self, order: &mut OrderAny, account_id: AccountId) {
531 {
533 let cache_borrow = self.cache.as_ref().borrow();
534
535 if self.core.order_exists(order.client_order_id()) {
536 self.generate_order_rejected(order, "Order already exists".into());
537 return;
538 }
539
540 self.account_ids.insert(order.trader_id(), account_id);
542
543 if EXPIRING_INSTRUMENT_TYPES.contains(&self.instrument.instrument_class()) {
545 if let Some(activation_ns) = self.instrument.activation_ns()
546 && self.clock.borrow().timestamp_ns() < activation_ns
547 {
548 self.generate_order_rejected(
549 order,
550 format!(
551 "Contract {} is not yet active, activation {}",
552 self.instrument.id(),
553 self.instrument.activation_ns().unwrap()
554 )
555 .into(),
556 );
557 return;
558 }
559 if let Some(expiration_ns) = self.instrument.expiration_ns()
560 && self.clock.borrow().timestamp_ns() >= expiration_ns
561 {
562 self.generate_order_rejected(
563 order,
564 format!(
565 "Contract {} has expired, expiration {}",
566 self.instrument.id(),
567 self.instrument.expiration_ns().unwrap()
568 )
569 .into(),
570 );
571 return;
572 }
573 }
574
575 if self.config.support_contingent_orders {
577 if let Some(parent_order_id) = order.parent_order_id() {
578 let parent_order = cache_borrow.order(&parent_order_id);
579 if parent_order.is_none()
580 || parent_order.unwrap().contingency_type().unwrap() != ContingencyType::Oto
581 {
582 panic!("OTO parent not found");
583 }
584 if let Some(parent_order) = parent_order {
585 let parent_order_status = parent_order.status();
586 let order_is_open = order.is_open();
587 if parent_order.status() == OrderStatus::Rejected && order.is_open() {
588 self.generate_order_rejected(
589 order,
590 format!("Rejected OTO order from {parent_order_id}").into(),
591 );
592 return;
593 } else if parent_order.status() == OrderStatus::Accepted
594 && parent_order.status() == OrderStatus::Triggered
595 {
596 log::info!(
597 "Pending OTO order {} triggers from {parent_order_id}",
598 order.client_order_id(),
599 );
600 return;
601 }
602 }
603 }
604
605 if let Some(linked_order_ids) = order.linked_order_ids() {
606 for client_order_id in linked_order_ids {
607 match cache_borrow.order(client_order_id) {
608 Some(contingent_order)
609 if (order.contingency_type().unwrap() == ContingencyType::Oco
610 || order.contingency_type().unwrap()
611 == ContingencyType::Ouo)
612 && !order.is_closed()
613 && contingent_order.is_closed() =>
614 {
615 self.generate_order_rejected(
616 order,
617 format!("Contingent order {client_order_id} already closed")
618 .into(),
619 );
620 return;
621 }
622 None => panic!("Cannot find contingent order for {client_order_id}"),
623 _ => {}
624 }
625 }
626 }
627 }
628
629 if order.quantity().precision != self.instrument.size_precision() {
631 self.generate_order_rejected(
632 order,
633 format!(
634 "Invalid order quantity precision for order {}, was {} when {} size precision is {}",
635 order.client_order_id(),
636 order.quantity().precision,
637 self.instrument.id(),
638 self.instrument.size_precision()
639 )
640 .into(),
641 );
642 return;
643 }
644
645 if let Some(price) = order.price()
647 && price.precision != self.instrument.price_precision()
648 {
649 self.generate_order_rejected(
650 order,
651 format!(
652 "Invalid order price precision for order {}, was {} when {} price precision is {}",
653 order.client_order_id(),
654 price.precision,
655 self.instrument.id(),
656 self.instrument.price_precision()
657 )
658 .into(),
659 );
660 return;
661 }
662
663 if let Some(trigger_price) = order.trigger_price()
665 && trigger_price.precision != self.instrument.price_precision()
666 {
667 self.generate_order_rejected(
668 order,
669 format!(
670 "Invalid order trigger price precision for order {}, was {} when {} price precision is {}",
671 order.client_order_id(),
672 trigger_price.precision,
673 self.instrument.id(),
674 self.instrument.price_precision()
675 )
676 .into(),
677 );
678 return;
679 }
680
681 let position: Option<&Position> = cache_borrow
683 .position_for_order(&order.client_order_id())
684 .or_else(|| {
685 if self.oms_type == OmsType::Netting {
686 let position_id = PositionId::new(
687 format!("{}-{}", order.instrument_id(), order.strategy_id()).as_str(),
688 );
689 cache_borrow.position(&position_id)
690 } else {
691 None
692 }
693 });
694
695 if order.order_side() == OrderSide::Sell
697 && self.account_type != AccountType::Margin
698 && matches!(self.instrument, InstrumentAny::Equity(_))
699 && (position.is_none()
700 || !order.would_reduce_only(position.unwrap().side, position.unwrap().quantity))
701 {
702 let position_string = position.map_or("None".to_string(), |pos| pos.id.to_string());
703 self.generate_order_rejected(
704 order,
705 format!(
706 "Short selling not permitted on a CASH account with position {position_string} and order {order}",
707 )
708 .into(),
709 );
710 return;
711 }
712
713 if self.config.use_reduce_only
715 && order.is_reduce_only()
716 && !order.is_closed()
717 && position.is_none_or(|pos| {
718 pos.is_closed()
719 || (order.is_buy() && pos.is_long())
720 || (order.is_sell() && pos.is_short())
721 })
722 {
723 self.generate_order_rejected(
724 order,
725 format!(
726 "Reduce-only order {} ({}-{}) would have increased position",
727 order.client_order_id(),
728 order.order_type().to_string().to_uppercase(),
729 order.order_side().to_string().to_uppercase()
730 )
731 .into(),
732 );
733 return;
734 }
735 }
736
737 match order.order_type() {
738 OrderType::Market => self.process_market_order(order),
739 OrderType::Limit => self.process_limit_order(order),
740 OrderType::MarketToLimit => self.process_market_to_limit_order(order),
741 OrderType::StopMarket => self.process_stop_market_order(order),
742 OrderType::StopLimit => self.process_stop_limit_order(order),
743 OrderType::MarketIfTouched => self.process_market_if_touched_order(order),
744 OrderType::LimitIfTouched => self.process_limit_if_touched_order(order),
745 OrderType::TrailingStopMarket => self.process_trailing_stop_order(order),
746 OrderType::TrailingStopLimit => self.process_trailing_stop_order(order),
747 }
748 }
749
750 pub fn process_modify(&mut self, command: &ModifyOrder, account_id: AccountId) {
751 if let Some(order) = self.core.get_order(command.client_order_id) {
752 self.update_order(
753 &mut order.to_any(),
754 command.quantity,
755 command.price,
756 command.trigger_price,
757 None,
758 );
759 } else {
760 self.generate_order_modify_rejected(
761 command.trader_id,
762 command.strategy_id,
763 command.instrument_id,
764 command.client_order_id,
765 Ustr::from(format!("Order {} not found", command.client_order_id).as_str()),
766 Some(command.venue_order_id),
767 Some(account_id),
768 );
769 }
770 }
771
772 pub fn process_cancel(&mut self, command: &CancelOrder, account_id: AccountId) {
773 match self.core.get_order(command.client_order_id) {
774 Some(passive_order) => {
775 if passive_order.is_inflight() || passive_order.is_open() {
776 self.cancel_order(&OrderAny::from(passive_order.to_owned()), None);
777 }
778 }
779 None => self.generate_order_cancel_rejected(
780 command.trader_id,
781 command.strategy_id,
782 account_id,
783 command.instrument_id,
784 command.client_order_id,
785 command.venue_order_id,
786 Ustr::from(format!("Order {} not found", command.client_order_id).as_str()),
787 ),
788 }
789 }
790
791 pub fn process_cancel_all(&mut self, command: &CancelAllOrders, account_id: AccountId) {
792 let instrument_id = command.instrument_id;
793 let open_orders = self
794 .cache
795 .borrow()
796 .orders_open(None, Some(&instrument_id), None, None)
797 .into_iter()
798 .cloned()
799 .collect::<Vec<OrderAny>>();
800 for order in open_orders {
801 if command.order_side != OrderSide::NoOrderSide
802 && command.order_side != order.order_side()
803 {
804 continue;
805 }
806 if order.is_inflight() || order.is_open() {
807 self.cancel_order(&order, None);
808 }
809 }
810 }
811
812 pub fn process_batch_cancel(&mut self, command: &BatchCancelOrders, account_id: AccountId) {
813 for order in &command.cancels {
814 self.process_cancel(order, account_id);
815 }
816 }
817
818 fn process_market_order(&mut self, order: &mut OrderAny) {
819 if order.time_in_force() == TimeInForce::AtTheOpen
820 || order.time_in_force() == TimeInForce::AtTheClose
821 {
822 log::error!(
823 "Market auction for the time in force {} is currently not supported",
824 order.time_in_force()
825 );
826 return;
827 }
828
829 let order_side = order.order_side();
831 let is_ask_initialized = self.core.is_ask_initialized;
832 let is_bid_initialized = self.core.is_bid_initialized;
833 if (order.order_side() == OrderSide::Buy && !self.core.is_ask_initialized)
834 || (order.order_side() == OrderSide::Sell && !self.core.is_bid_initialized)
835 {
836 self.generate_order_rejected(
837 order,
838 format!("No market for {}", order.instrument_id()).into(),
839 );
840 return;
841 }
842
843 self.fill_market_order(order);
844 }
845
846 fn process_limit_order(&mut self, order: &mut OrderAny) {
847 let limit_px = order.price().expect("Limit order must have a price");
848 if order.is_post_only()
849 && self
850 .core
851 .is_limit_matched(order.order_side_specified(), limit_px)
852 {
853 self.generate_order_rejected(
854 order,
855 format!(
856 "POST_ONLY {} {} order limit px of {} would have been a TAKER: bid={}, ask={}",
857 order.order_type(),
858 order.order_side(),
859 order.price().unwrap(),
860 self.core
861 .bid
862 .map_or_else(|| "None".to_string(), |p| p.to_string()),
863 self.core
864 .ask
865 .map_or_else(|| "None".to_string(), |p| p.to_string())
866 )
867 .into(),
868 );
869 return;
870 }
871
872 self.accept_order(order);
874
875 if self
877 .core
878 .is_limit_matched(order.order_side_specified(), limit_px)
879 {
880 if order.liquidity_side().is_some()
882 && order.liquidity_side().unwrap() == LiquiditySide::NoLiquiditySide
883 {
884 order.set_liquidity_side(LiquiditySide::Taker);
885 }
886 self.fill_limit_order(order);
887 } else if matches!(order.time_in_force(), TimeInForce::Fok | TimeInForce::Ioc) {
888 self.cancel_order(order, None);
889 }
890 }
891
892 fn process_market_to_limit_order(&mut self, order: &mut OrderAny) {
893 if (order.order_side() == OrderSide::Buy && !self.core.is_ask_initialized)
895 || (order.order_side() == OrderSide::Sell && !self.core.is_bid_initialized)
896 {
897 self.generate_order_rejected(
898 order,
899 format!("No market for {}", order.instrument_id()).into(),
900 );
901 return;
902 }
903
904 self.fill_market_order(order);
906
907 if order.is_open() {
908 self.accept_order(order);
909 }
910 }
911
912 fn process_stop_market_order(&mut self, order: &mut OrderAny) {
913 let stop_px = order
914 .trigger_price()
915 .expect("Stop order must have a trigger price");
916 if self
917 .core
918 .is_stop_matched(order.order_side_specified(), stop_px)
919 {
920 if self.config.reject_stop_orders {
921 self.generate_order_rejected(
922 order,
923 format!(
924 "{} {} order stop px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
925 order.order_type(),
926 order.order_side(),
927 order.trigger_price().unwrap(),
928 self.core
929 .bid
930 .map_or_else(|| "None".to_string(), |p| p.to_string()),
931 self.core
932 .ask
933 .map_or_else(|| "None".to_string(), |p| p.to_string())
934 ).into(),
935 );
936 return;
937 }
938 self.fill_market_order(order);
939 return;
940 }
941
942 self.accept_order(order);
944 }
945
946 fn process_stop_limit_order(&mut self, order: &mut OrderAny) {
947 let stop_px = order
948 .trigger_price()
949 .expect("Stop order must have a trigger price");
950 if self
951 .core
952 .is_stop_matched(order.order_side_specified(), stop_px)
953 {
954 if self.config.reject_stop_orders {
955 self.generate_order_rejected(
956 order,
957 format!(
958 "{} {} order stop px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
959 order.order_type(),
960 order.order_side(),
961 order.trigger_price().unwrap(),
962 self.core
963 .bid
964 .map_or_else(|| "None".to_string(), |p| p.to_string()),
965 self.core
966 .ask
967 .map_or_else(|| "None".to_string(), |p| p.to_string())
968 ).into(),
969 );
970 return;
971 }
972
973 self.accept_order(order);
974 self.generate_order_triggered(order);
975
976 let limit_px = order.price().expect("Stop limit order must have a price");
978 if self
979 .core
980 .is_limit_matched(order.order_side_specified(), limit_px)
981 {
982 order.set_liquidity_side(LiquiditySide::Taker);
983 self.fill_limit_order(order);
984 }
985 }
986
987 self.accept_order(order);
989 }
990
991 fn process_market_if_touched_order(&mut self, order: &mut OrderAny) {
992 if self
993 .core
994 .is_touch_triggered(order.order_side_specified(), order.trigger_price().unwrap())
995 {
996 if self.config.reject_stop_orders {
997 self.generate_order_rejected(
998 order,
999 format!(
1000 "{} {} order trigger px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
1001 order.order_type(),
1002 order.order_side(),
1003 order.trigger_price().unwrap(),
1004 self.core
1005 .bid
1006 .map_or_else(|| "None".to_string(), |p| p.to_string()),
1007 self.core
1008 .ask
1009 .map_or_else(|| "None".to_string(), |p| p.to_string())
1010 ).into(),
1011 );
1012 return;
1013 }
1014 self.fill_market_order(order);
1015 return;
1016 }
1017
1018 self.accept_order(order);
1020 }
1021
1022 fn process_limit_if_touched_order(&mut self, order: &mut OrderAny) {
1023 if self
1024 .core
1025 .is_touch_triggered(order.order_side_specified(), order.trigger_price().unwrap())
1026 {
1027 if self.config.reject_stop_orders {
1028 self.generate_order_rejected(
1029 order,
1030 format!(
1031 "{} {} order trigger px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
1032 order.order_type(),
1033 order.order_side(),
1034 order.trigger_price().unwrap(),
1035 self.core
1036 .bid
1037 .map_or_else(|| "None".to_string(), |p| p.to_string()),
1038 self.core
1039 .ask
1040 .map_or_else(|| "None".to_string(), |p| p.to_string())
1041 ).into(),
1042 );
1043 return;
1044 }
1045 self.accept_order(order);
1046 self.generate_order_triggered(order);
1047
1048 if self
1050 .core
1051 .is_limit_matched(order.order_side_specified(), order.price().unwrap())
1052 {
1053 order.set_liquidity_side(LiquiditySide::Taker);
1054 self.fill_limit_order(order);
1055 }
1056 return;
1057 }
1058
1059 self.accept_order(order);
1061 }
1062
1063 fn process_trailing_stop_order(&mut self, order: &mut OrderAny) {
1064 if let Some(trigger_price) = order.trigger_price()
1065 && self
1066 .core
1067 .is_stop_matched(order.order_side_specified(), trigger_price)
1068 {
1069 self.generate_order_rejected(
1070 order,
1071 format!(
1072 "{} {} order trigger px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
1073 order.order_type(),
1074 order.order_side(),
1075 trigger_price,
1076 self.core
1077 .bid
1078 .map_or_else(|| "None".to_string(), |p| p.to_string()),
1079 self.core
1080 .ask
1081 .map_or_else(|| "None".to_string(), |p| p.to_string())
1082 ).into(),
1083 );
1084 return;
1085 }
1086
1087 self.accept_order(order);
1089 }
1090
1091 pub fn iterate(&mut self, timestamp_ns: UnixNanos) {
1100 if self.book.has_bid() {
1104 self.core.set_bid_raw(self.book.best_bid_price().unwrap());
1105 }
1106 if self.book.has_ask() {
1107 self.core.set_ask_raw(self.book.best_ask_price().unwrap());
1108 }
1109 self.core.iterate();
1110
1111 self.core.bid = self.book.best_bid_price();
1112 self.core.ask = self.book.best_ask_price();
1113
1114 let orders_bid = self.core.get_orders_bid().to_vec();
1115 let orders_ask = self.core.get_orders_ask().to_vec();
1116
1117 self.iterate_orders(timestamp_ns, &orders_bid);
1118 self.iterate_orders(timestamp_ns, &orders_ask);
1119 }
1120
1121 fn maybe_activate_trailing_stop(
1122 &mut self,
1123 order: &mut OrderAny,
1124 bid: Option<Price>,
1125 ask: Option<Price>,
1126 ) -> bool {
1127 match order {
1128 OrderAny::TrailingStopMarket(inner) => {
1129 if inner.is_activated {
1130 return true;
1131 }
1132
1133 if inner.activation_price.is_none() {
1134 let px = match inner.order_side() {
1135 OrderSide::Buy => ask,
1136 OrderSide::Sell => bid,
1137 _ => None,
1138 };
1139 if let Some(p) = px {
1140 inner.activation_price = Some(p);
1141 inner.set_activated();
1142 if let Err(e) = self.cache.borrow_mut().update_order(order) {
1143 log::error!("Failed to update order: {e}");
1144 }
1145 return true;
1146 }
1147 return false;
1148 }
1149
1150 let activation_price = inner.activation_price.unwrap();
1151 let hit = match inner.order_side() {
1152 OrderSide::Buy => ask.is_some_and(|a| a <= activation_price),
1153 OrderSide::Sell => bid.is_some_and(|b| b >= activation_price),
1154 _ => false,
1155 };
1156 if hit {
1157 inner.set_activated();
1158 if let Err(e) = self.cache.borrow_mut().update_order(order) {
1159 log::error!("Failed to update order: {e}");
1160 }
1161 }
1162 hit
1163 }
1164 OrderAny::TrailingStopLimit(inner) => {
1165 if inner.is_activated {
1166 return true;
1167 }
1168
1169 if inner.activation_price.is_none() {
1170 let px = match inner.order_side() {
1171 OrderSide::Buy => ask,
1172 OrderSide::Sell => bid,
1173 _ => None,
1174 };
1175 if let Some(p) = px {
1176 inner.activation_price = Some(p);
1177 inner.set_activated();
1178 if let Err(e) = self.cache.borrow_mut().update_order(order) {
1179 log::error!("Failed to update order: {e}");
1180 }
1181 return true;
1182 }
1183 return false;
1184 }
1185
1186 let activation_price = inner.activation_price.unwrap();
1187 let hit = match inner.order_side() {
1188 OrderSide::Buy => ask.is_some_and(|a| a <= activation_price),
1189 OrderSide::Sell => bid.is_some_and(|b| b >= activation_price),
1190 _ => false,
1191 };
1192 if hit {
1193 inner.set_activated();
1194 if let Err(e) = self.cache.borrow_mut().update_order(order) {
1195 log::error!("Failed to update order: {e}");
1196 }
1197 }
1198 hit
1199 }
1200 _ => true,
1201 }
1202 }
1203
1204 fn iterate_orders(&mut self, timestamp_ns: UnixNanos, orders: &[PassiveOrderAny]) {
1205 for order in orders {
1206 if order.is_closed() {
1207 continue;
1208 }
1209
1210 if self.config.support_gtd_orders
1211 && order
1212 .expire_time()
1213 .is_some_and(|expire_timestamp_ns| timestamp_ns >= expire_timestamp_ns)
1214 {
1215 let _ = self.core.delete_order(order);
1216 self.cached_filled_qty.remove(&order.client_order_id());
1217 self.expire_order(order);
1218 continue;
1219 }
1220
1221 if matches!(
1222 order,
1223 PassiveOrderAny::Stop(
1224 StopOrderAny::TrailingStopMarket(_) | StopOrderAny::TrailingStopLimit(_)
1225 )
1226 ) {
1227 let mut any = OrderAny::from(order.clone());
1228
1229 if !self.maybe_activate_trailing_stop(&mut any, self.core.bid, self.core.ask) {
1230 continue;
1231 }
1232
1233 self.update_trailing_stop_order(&mut any);
1234 }
1235
1236 if let Some(target_bid) = self.target_bid {
1238 self.core.bid = Some(target_bid);
1239 self.target_bid = None;
1240 }
1241 if let Some(target_bid) = self.target_bid.take() {
1242 self.core.bid = Some(target_bid);
1243 self.target_bid = None;
1244 }
1245 if let Some(target_ask) = self.target_ask.take() {
1246 self.core.ask = Some(target_ask);
1247 self.target_ask = None;
1248 }
1249 if let Some(target_last) = self.target_last.take() {
1250 self.core.last = Some(target_last);
1251 self.target_last = None;
1252 }
1253 }
1254
1255 self.target_bid = None;
1257 self.target_ask = None;
1258 self.target_last = None;
1259 }
1260
1261 fn determine_limit_price_and_volume(&mut self, order: &OrderAny) -> Vec<(Price, Quantity)> {
1262 match order.price() {
1263 Some(order_price) => {
1264 let book_order =
1266 BookOrder::new(order.order_side(), order_price, order.quantity(), 1);
1267
1268 let mut fills = self.book.simulate_fills(&book_order);
1269
1270 if fills.is_empty() {
1272 return fills;
1273 }
1274
1275 if let Some(triggered_price) = order.trigger_price() {
1277 if order
1279 .liquidity_side()
1280 .is_some_and(|liquidity_side| liquidity_side == LiquiditySide::Taker)
1281 {
1282 if order.order_side() == OrderSide::Sell && order_price > triggered_price {
1283 let first_fill = fills.first().unwrap();
1285 let triggered_qty = first_fill.1;
1286 fills[0] = (triggered_price, triggered_qty);
1287 self.target_bid = self.core.bid;
1288 self.target_ask = self.core.ask;
1289 self.target_last = self.core.last;
1290 self.core.set_ask_raw(order_price);
1291 self.core.set_last_raw(order_price);
1292 } else if order.order_side() == OrderSide::Buy
1293 && order_price < triggered_price
1294 {
1295 let first_fill = fills.first().unwrap();
1297 let triggered_qty = first_fill.1;
1298 fills[0] = (triggered_price, triggered_qty);
1299 self.target_bid = self.core.bid;
1300 self.target_ask = self.core.ask;
1301 self.target_last = self.core.last;
1302 self.core.set_bid_raw(order_price);
1303 self.core.set_last_raw(order_price);
1304 }
1305 }
1306 }
1307
1308 if order
1310 .liquidity_side()
1311 .is_some_and(|liquidity_side| liquidity_side == LiquiditySide::Maker)
1312 {
1313 match order.order_side().as_specified() {
1314 OrderSideSpecified::Buy => {
1315 let target_price = if order
1316 .trigger_price()
1317 .is_some_and(|trigger_price| order_price > trigger_price)
1318 {
1319 order.trigger_price().unwrap()
1320 } else {
1321 order_price
1322 };
1323 for fill in &fills {
1324 let last_px = fill.0;
1325 if last_px < order_price {
1326 self.target_bid = self.core.bid;
1328 self.target_ask = self.core.ask;
1329 self.target_last = self.core.last;
1330 self.core.set_ask_raw(target_price);
1331 self.core.set_last_raw(target_price);
1332 }
1333 }
1334 }
1335 OrderSideSpecified::Sell => {
1336 let target_price = if order
1337 .trigger_price()
1338 .is_some_and(|trigger_price| order_price < trigger_price)
1339 {
1340 order.trigger_price().unwrap()
1341 } else {
1342 order_price
1343 };
1344 for fill in &fills {
1345 let last_px = fill.0;
1346 if last_px > order_price {
1347 self.target_bid = self.core.bid;
1349 self.target_ask = self.core.ask;
1350 self.target_last = self.core.last;
1351 self.core.set_bid_raw(target_price);
1352 self.core.set_last_raw(target_price);
1353 }
1354 }
1355 }
1356 }
1357 }
1358
1359 fills
1360 }
1361 None => panic!("Limit order must have a price"),
1362 }
1363 }
1364
1365 fn determine_market_price_and_volume(&self, order: &OrderAny) -> Vec<(Price, Quantity)> {
1366 let price = match order.order_side().as_specified() {
1368 OrderSideSpecified::Buy => Price::max(FIXED_PRECISION),
1369 OrderSideSpecified::Sell => Price::min(FIXED_PRECISION),
1370 };
1371
1372 let book_order = BookOrder::new(order.order_side(), price, order.quantity(), 0);
1374 self.book.simulate_fills(&book_order)
1375 }
1376
1377 pub fn fill_market_order(&mut self, order: &mut OrderAny) {
1378 if let Some(filled_qty) = self.cached_filled_qty.get(&order.client_order_id())
1379 && filled_qty >= &order.quantity()
1380 {
1381 log::info!(
1382 "Ignoring fill as already filled pending application of events: {:?}, {:?}, {:?}, {:?}",
1383 filled_qty,
1384 order.quantity(),
1385 order.filled_qty(),
1386 order.quantity()
1387 );
1388 return;
1389 }
1390
1391 let venue_position_id = self.ids_generator.get_position_id(order, Some(true));
1392 let position: Option<Position> = if let Some(venue_position_id) = venue_position_id {
1393 let cache = self.cache.as_ref().borrow();
1394 cache.position(&venue_position_id).cloned()
1395 } else {
1396 None
1397 };
1398
1399 if self.config.use_reduce_only && order.is_reduce_only() && position.is_none() {
1400 log::warn!(
1401 "Canceling REDUCE_ONLY {} as would increase position",
1402 order.order_type()
1403 );
1404 self.cancel_order(order, None);
1405 return;
1406 }
1407 order.set_liquidity_side(LiquiditySide::Taker);
1409 let fills = self.determine_market_price_and_volume(order);
1410 self.apply_fills(order, fills, LiquiditySide::Taker, None, position);
1411 }
1412
1413 pub fn fill_limit_order(&mut self, order: &mut OrderAny) {
1417 match order.price() {
1418 Some(order_price) => {
1419 let cached_filled_qty = self.cached_filled_qty.get(&order.client_order_id());
1420 if cached_filled_qty.is_some() && *cached_filled_qty.unwrap() >= order.quantity() {
1421 log::debug!(
1422 "Ignoring fill as already filled pending pending application of events: {}, {}, {}, {}",
1423 cached_filled_qty.unwrap(),
1424 order.quantity(),
1425 order.filled_qty(),
1426 order.leaves_qty(),
1427 );
1428 return;
1429 }
1430
1431 if order
1432 .liquidity_side()
1433 .is_some_and(|liquidity_side| liquidity_side == LiquiditySide::Maker)
1434 {
1435 if order.order_side() == OrderSide::Buy
1436 && self.core.bid.is_some_and(|bid| bid == order_price)
1437 && !self.fill_model.is_limit_filled()
1438 {
1439 return;
1441 }
1442 if order.order_side() == OrderSide::Sell
1443 && self.core.ask.is_some_and(|ask| ask == order_price)
1444 && !self.fill_model.is_limit_filled()
1445 {
1446 return;
1448 }
1449 }
1450
1451 let venue_position_id = self.ids_generator.get_position_id(order, None);
1452 let position = if let Some(venue_position_id) = venue_position_id {
1453 let cache = self.cache.as_ref().borrow();
1454 cache.position(&venue_position_id).cloned()
1455 } else {
1456 None
1457 };
1458
1459 if self.config.use_reduce_only && order.is_reduce_only() && position.is_none() {
1460 log::warn!(
1461 "Canceling REDUCE_ONLY {} as would increase position",
1462 order.order_type()
1463 );
1464 self.cancel_order(order, None);
1465 return;
1466 }
1467
1468 let fills = self.determine_limit_price_and_volume(order);
1469
1470 self.apply_fills(
1471 order,
1472 fills,
1473 order.liquidity_side().unwrap(),
1474 venue_position_id,
1475 position,
1476 );
1477 }
1478 None => panic!("Limit order must have a price"),
1479 }
1480 }
1481
1482 fn apply_fills(
1483 &mut self,
1484 order: &mut OrderAny,
1485 fills: Vec<(Price, Quantity)>,
1486 liquidity_side: LiquiditySide,
1487 venue_position_id: Option<PositionId>,
1488 position: Option<Position>,
1489 ) {
1490 if order.time_in_force() == TimeInForce::Fok {
1491 let mut total_size = Quantity::zero(order.quantity().precision);
1492 for (fill_px, fill_qty) in &fills {
1493 total_size = total_size.add(*fill_qty);
1494 }
1495
1496 if order.leaves_qty() > total_size {
1497 self.cancel_order(order, None);
1498 return;
1499 }
1500 }
1501
1502 if fills.is_empty() {
1503 if order.status() == OrderStatus::Submitted {
1504 self.generate_order_rejected(
1505 order,
1506 format!("No market for {}", order.instrument_id()).into(),
1507 );
1508 } else {
1509 log::error!(
1510 "Cannot fill order: no fills from book when fills were expected (check size in data)"
1511 );
1512 return;
1513 }
1514 }
1515
1516 if self.oms_type == OmsType::Netting {
1517 let venue_position_id: Option<PositionId> = None;
1518 }
1519
1520 let mut initial_market_to_limit_fill = false;
1521 for &(mut fill_px, ref fill_qty) in &fills {
1522 assert!(
1524 (fill_px.precision == self.instrument.price_precision()),
1525 "Invalid price precision for fill price {} when instrument price precision is {}.\
1526 Check that the data price precision matches the {} instrument",
1527 fill_px.precision,
1528 self.instrument.price_precision(),
1529 self.instrument.id()
1530 );
1531
1532 assert!(
1534 (fill_qty.precision == self.instrument.size_precision()),
1535 "Invalid quantity precision for fill quantity {} when instrument size precision is {}.\
1536 Check that the data quantity precision matches the {} instrument",
1537 fill_qty.precision,
1538 self.instrument.size_precision(),
1539 self.instrument.id()
1540 );
1541
1542 if order.filled_qty() == Quantity::zero(order.filled_qty().precision)
1543 && order.order_type() == OrderType::MarketToLimit
1544 {
1545 self.generate_order_updated(order, order.quantity(), Some(fill_px), None);
1546 initial_market_to_limit_fill = true;
1547 }
1548
1549 if self.book_type == BookType::L1_MBP && self.fill_model.is_slipped() {
1550 fill_px = match order.order_side().as_specified() {
1551 OrderSideSpecified::Buy => fill_px.add(self.instrument.price_increment()),
1552 OrderSideSpecified::Sell => fill_px.sub(self.instrument.price_increment()),
1553 }
1554 }
1555
1556 let mut effective_fill_qty = *fill_qty;
1560
1561 if self.config.use_reduce_only
1562 && order.is_reduce_only()
1563 && let Some(position) = &position
1564 && *fill_qty > position.quantity
1565 {
1566 if position.quantity == Quantity::zero(position.quantity.precision) {
1567 return;
1569 }
1570
1571 let adjusted_fill_qty =
1573 Quantity::from_raw(position.quantity.raw, fill_qty.precision);
1574
1575 effective_fill_qty = std::cmp::min(effective_fill_qty, adjusted_fill_qty);
1577
1578 if order.quantity() != adjusted_fill_qty {
1580 self.generate_order_updated(order, adjusted_fill_qty, None, None);
1581 }
1582 }
1583
1584 if fill_qty.is_zero() {
1585 if fills.len() == 1 && order.status() == OrderStatus::Submitted {
1586 self.generate_order_rejected(
1587 order,
1588 format!("No market for {}", order.instrument_id()).into(),
1589 );
1590 }
1591 return;
1592 }
1593
1594 self.fill_order(
1595 order,
1596 fill_px,
1597 effective_fill_qty,
1598 liquidity_side,
1599 venue_position_id,
1600 position.clone(),
1601 );
1602
1603 if order.order_type() == OrderType::MarketToLimit && initial_market_to_limit_fill {
1604 return;
1606 }
1607 }
1608
1609 if order.time_in_force() == TimeInForce::Ioc && order.is_open() {
1610 self.cancel_order(order, None);
1612 return;
1613 }
1614
1615 if order.is_open()
1616 && self.book_type == BookType::L1_MBP
1617 && matches!(
1618 order.order_type(),
1619 OrderType::Market
1620 | OrderType::MarketIfTouched
1621 | OrderType::StopMarket
1622 | OrderType::TrailingStopMarket
1623 )
1624 {
1625 todo!("Exhausted simulated book volume")
1629 }
1630 }
1631
1632 fn fill_order(
1633 &mut self,
1634 order: &mut OrderAny,
1635 last_px: Price,
1636 last_qty: Quantity,
1637 liquidity_side: LiquiditySide,
1638 venue_position_id: Option<PositionId>,
1639 position: Option<Position>,
1640 ) {
1641 match self.cached_filled_qty.get(&order.client_order_id()) {
1642 Some(filled_qty) => {
1643 let leaves_qty = order.quantity() - *filled_qty;
1644 let last_qty = min(last_qty, leaves_qty);
1645 let new_filled_qty = *filled_qty + last_qty;
1646 self.cached_filled_qty
1648 .insert(order.client_order_id(), new_filled_qty);
1649 }
1650 None => {
1651 self.cached_filled_qty
1652 .insert(order.client_order_id(), last_qty);
1653 }
1654 }
1655
1656 let commission = self
1658 .fee_model
1659 .get_commission(order, last_qty, last_px, &self.instrument)
1660 .unwrap();
1661
1662 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
1663 self.generate_order_filled(
1664 order,
1665 venue_order_id,
1666 venue_position_id,
1667 last_qty,
1668 last_px,
1669 self.instrument.quote_currency(),
1670 commission,
1671 liquidity_side,
1672 );
1673
1674 if order.is_passive() && order.is_closed() {
1675 if self.core.order_exists(order.client_order_id()) {
1677 let _ = self.core.delete_order(
1678 &PassiveOrderAny::try_from(order.clone()).expect("passive order conversion"),
1679 );
1680 }
1681 self.cached_filled_qty.remove(&order.client_order_id());
1682 }
1683
1684 if !self.config.support_contingent_orders {
1685 return;
1686 }
1687
1688 if let Some(contingency_type) = order.contingency_type() {
1689 match contingency_type {
1690 ContingencyType::Oto => {
1691 if let Some(linked_orders_ids) = order.linked_order_ids() {
1692 for client_order_id in linked_orders_ids {
1693 let mut child_order = match self.cache.borrow().order(client_order_id) {
1694 Some(child_order) => child_order.clone(),
1695 None => panic!("Order {client_order_id} not found in cache"),
1696 };
1697
1698 if child_order.is_closed() || child_order.is_active_local() {
1699 continue;
1700 }
1701
1702 if let (None, Some(position_id)) =
1704 (child_order.position_id(), order.position_id())
1705 {
1706 self.cache
1707 .borrow_mut()
1708 .add_position_id(
1709 &position_id,
1710 &self.venue,
1711 client_order_id,
1712 &child_order.strategy_id(),
1713 )
1714 .unwrap();
1715 log::debug!(
1716 "Added position id {position_id} to cache for order {client_order_id}"
1717 );
1718 }
1719
1720 if (!child_order.is_open())
1721 || (matches!(child_order.status(), OrderStatus::PendingUpdate)
1722 && child_order
1723 .previous_status()
1724 .is_some_and(|s| matches!(s, OrderStatus::Submitted)))
1725 {
1726 let account_id = order.account_id().unwrap_or_else(|| {
1727 *self.account_ids.get(&order.trader_id()).unwrap_or_else(|| {
1728 panic!(
1729 "Account ID not found for trader {}",
1730 order.trader_id()
1731 )
1732 })
1733 });
1734 self.process_order(&mut child_order, account_id);
1735 }
1736 }
1737 } else {
1738 log::error!(
1739 "OTO order {} does not have linked orders",
1740 order.client_order_id()
1741 );
1742 }
1743 }
1744 ContingencyType::Oco => {
1745 if let Some(linked_orders_ids) = order.linked_order_ids() {
1746 for client_order_id in linked_orders_ids {
1747 let child_order = match self.cache.borrow().order(client_order_id) {
1748 Some(child_order) => child_order.clone(),
1749 None => panic!("Order {client_order_id} not found in cache"),
1750 };
1751
1752 if child_order.is_closed() || child_order.is_active_local() {
1753 continue;
1754 }
1755
1756 self.cancel_order(&child_order, None);
1757 }
1758 } else {
1759 log::error!(
1760 "OCO order {} does not have linked orders",
1761 order.client_order_id()
1762 );
1763 }
1764 }
1765 ContingencyType::Ouo => {
1766 if let Some(linked_orders_ids) = order.linked_order_ids() {
1767 for client_order_id in linked_orders_ids {
1768 let mut child_order = match self.cache.borrow().order(client_order_id) {
1769 Some(child_order) => child_order.clone(),
1770 None => panic!("Order {client_order_id} not found in cache"),
1771 };
1772
1773 if child_order.is_active_local() {
1774 continue;
1775 }
1776
1777 if order.is_closed() && child_order.is_open() {
1778 self.cancel_order(&child_order, None);
1779 } else if !order.leaves_qty().is_zero()
1780 && order.leaves_qty() != child_order.leaves_qty()
1781 {
1782 let price = child_order.price();
1783 let trigger_price = child_order.trigger_price();
1784 self.update_order(
1785 &mut child_order,
1786 Some(order.leaves_qty()),
1787 price,
1788 trigger_price,
1789 Some(false),
1790 );
1791 }
1792 }
1793 } else {
1794 log::error!(
1795 "OUO order {} does not have linked orders",
1796 order.client_order_id()
1797 );
1798 }
1799 }
1800 _ => {}
1801 }
1802 }
1803 }
1804
1805 fn update_limit_order(&mut self, order: &mut OrderAny, quantity: Quantity, price: Price) {
1806 if self
1807 .core
1808 .is_limit_matched(order.order_side_specified(), price)
1809 {
1810 if order.is_post_only() {
1811 self.generate_order_modify_rejected(
1812 order.trader_id(),
1813 order.strategy_id(),
1814 order.instrument_id(),
1815 order.client_order_id(),
1816 Ustr::from(format!(
1817 "POST_ONLY {} {} order with new limit px of {} would have been a TAKER: bid={}, ask={}",
1818 order.order_type(),
1819 order.order_side(),
1820 price,
1821 self.core.bid.map_or_else(|| "None".to_string(), |p| p.to_string()),
1822 self.core.ask.map_or_else(|| "None".to_string(), |p| p.to_string())
1823 ).as_str()),
1824 order.venue_order_id(),
1825 order.account_id(),
1826 );
1827 return;
1828 }
1829
1830 self.generate_order_updated(order, quantity, Some(price), None);
1831 order.set_liquidity_side(LiquiditySide::Taker);
1832 self.fill_limit_order(order);
1833 return;
1834 }
1835 self.generate_order_updated(order, quantity, Some(price), None);
1836 }
1837
1838 fn update_stop_market_order(
1839 &mut self,
1840 order: &mut OrderAny,
1841 quantity: Quantity,
1842 trigger_price: Price,
1843 ) {
1844 if self
1845 .core
1846 .is_stop_matched(order.order_side_specified(), trigger_price)
1847 {
1848 self.generate_order_modify_rejected(
1849 order.trader_id(),
1850 order.strategy_id(),
1851 order.instrument_id(),
1852 order.client_order_id(),
1853 Ustr::from(
1854 format!(
1855 "{} {} order new stop px of {} was in the market: bid={}, ask={}",
1856 order.order_type(),
1857 order.order_side(),
1858 trigger_price,
1859 self.core
1860 .bid
1861 .map_or_else(|| "None".to_string(), |p| p.to_string()),
1862 self.core
1863 .ask
1864 .map_or_else(|| "None".to_string(), |p| p.to_string())
1865 )
1866 .as_str(),
1867 ),
1868 order.venue_order_id(),
1869 order.account_id(),
1870 );
1871 return;
1872 }
1873
1874 self.generate_order_updated(order, quantity, None, Some(trigger_price));
1875 }
1876
1877 fn update_stop_limit_order(
1878 &mut self,
1879 order: &mut OrderAny,
1880 quantity: Quantity,
1881 price: Price,
1882 trigger_price: Price,
1883 ) {
1884 if order.is_triggered().is_some_and(|t| t) {
1885 if self
1887 .core
1888 .is_limit_matched(order.order_side_specified(), price)
1889 {
1890 if order.is_post_only() {
1891 self.generate_order_modify_rejected(
1892 order.trader_id(),
1893 order.strategy_id(),
1894 order.instrument_id(),
1895 order.client_order_id(),
1896 Ustr::from(format!(
1897 "POST_ONLY {} {} order with new limit px of {} would have been a TAKER: bid={}, ask={}",
1898 order.order_type(),
1899 order.order_side(),
1900 price,
1901 self.core.bid.map_or_else(|| "None".to_string(), |p| p.to_string()),
1902 self.core.ask.map_or_else(|| "None".to_string(), |p| p.to_string())
1903 ).as_str()),
1904 order.venue_order_id(),
1905 order.account_id(),
1906 );
1907 return;
1908 }
1909 self.generate_order_updated(order, quantity, Some(price), None);
1910 order.set_liquidity_side(LiquiditySide::Taker);
1911 self.fill_limit_order(order);
1912 return; }
1914 } else {
1915 if self
1917 .core
1918 .is_stop_matched(order.order_side_specified(), trigger_price)
1919 {
1920 self.generate_order_modify_rejected(
1921 order.trader_id(),
1922 order.strategy_id(),
1923 order.instrument_id(),
1924 order.client_order_id(),
1925 Ustr::from(
1926 format!(
1927 "{} {} order new stop px of {} was in the market: bid={}, ask={}",
1928 order.order_type(),
1929 order.order_side(),
1930 trigger_price,
1931 self.core
1932 .bid
1933 .map_or_else(|| "None".to_string(), |p| p.to_string()),
1934 self.core
1935 .ask
1936 .map_or_else(|| "None".to_string(), |p| p.to_string())
1937 )
1938 .as_str(),
1939 ),
1940 order.venue_order_id(),
1941 order.account_id(),
1942 );
1943 return;
1944 }
1945 }
1946
1947 self.generate_order_updated(order, quantity, Some(price), Some(trigger_price));
1948 }
1949
1950 fn update_market_if_touched_order(
1951 &mut self,
1952 order: &mut OrderAny,
1953 quantity: Quantity,
1954 trigger_price: Price,
1955 ) {
1956 if self
1957 .core
1958 .is_touch_triggered(order.order_side_specified(), trigger_price)
1959 {
1960 self.generate_order_modify_rejected(
1961 order.trader_id(),
1962 order.strategy_id(),
1963 order.instrument_id(),
1964 order.client_order_id(),
1965 Ustr::from(
1966 format!(
1967 "{} {} order new trigger px of {} was in the market: bid={}, ask={}",
1968 order.order_type(),
1969 order.order_side(),
1970 trigger_price,
1971 self.core
1972 .bid
1973 .map_or_else(|| "None".to_string(), |p| p.to_string()),
1974 self.core
1975 .ask
1976 .map_or_else(|| "None".to_string(), |p| p.to_string())
1977 )
1978 .as_str(),
1979 ),
1980 order.venue_order_id(),
1981 order.account_id(),
1982 );
1983 return;
1985 }
1986
1987 self.generate_order_updated(order, quantity, None, Some(trigger_price));
1988 }
1989
1990 fn update_limit_if_touched_order(
1991 &mut self,
1992 order: &mut OrderAny,
1993 quantity: Quantity,
1994 price: Price,
1995 trigger_price: Price,
1996 ) {
1997 if order.is_triggered().is_some_and(|t| t) {
1998 if self
2000 .core
2001 .is_limit_matched(order.order_side_specified(), price)
2002 {
2003 if order.is_post_only() {
2004 self.generate_order_modify_rejected(
2005 order.trader_id(),
2006 order.strategy_id(),
2007 order.instrument_id(),
2008 order.client_order_id(),
2009 Ustr::from(format!(
2010 "POST_ONLY {} {} order with new limit px of {} would have been a TAKER: bid={}, ask={}",
2011 order.order_type(),
2012 order.order_side(),
2013 price,
2014 self.core.bid.map_or_else(|| "None".to_string(), |p| p.to_string()),
2015 self.core.ask.map_or_else(|| "None".to_string(), |p| p.to_string())
2016 ).as_str()),
2017 order.venue_order_id(),
2018 order.account_id(),
2019 );
2020 return;
2022 }
2023 self.generate_order_updated(order, quantity, Some(price), None);
2024 order.set_liquidity_side(LiquiditySide::Taker);
2025 self.fill_limit_order(order);
2026 return;
2027 }
2028 } else {
2029 if self
2031 .core
2032 .is_touch_triggered(order.order_side_specified(), trigger_price)
2033 {
2034 self.generate_order_modify_rejected(
2035 order.trader_id(),
2036 order.strategy_id(),
2037 order.instrument_id(),
2038 order.client_order_id(),
2039 Ustr::from(
2040 format!(
2041 "{} {} order new trigger px of {} was in the market: bid={}, ask={}",
2042 order.order_type(),
2043 order.order_side(),
2044 trigger_price,
2045 self.core
2046 .bid
2047 .map_or_else(|| "None".to_string(), |p| p.to_string()),
2048 self.core
2049 .ask
2050 .map_or_else(|| "None".to_string(), |p| p.to_string())
2051 )
2052 .as_str(),
2053 ),
2054 order.venue_order_id(),
2055 order.account_id(),
2056 );
2057 return;
2058 }
2059 }
2060
2061 self.generate_order_updated(order, quantity, Some(price), Some(trigger_price));
2062 }
2063
2064 fn update_trailing_stop_order(&mut self, order: &mut OrderAny) {
2065 let (new_trigger_price, new_price) = trailing_stop_calculate(
2066 self.instrument.price_increment(),
2067 order.trigger_price(),
2068 order.activation_price(),
2069 order,
2070 self.core.bid,
2071 self.core.ask,
2072 self.core.last,
2073 )
2074 .unwrap();
2075
2076 if new_trigger_price.is_none() && new_price.is_none() {
2077 return;
2078 }
2079
2080 self.generate_order_updated(order, order.quantity(), new_price, new_trigger_price);
2081 }
2082
2083 fn accept_order(&mut self, order: &mut OrderAny) {
2086 if order.is_closed() {
2087 return;
2089 }
2090 if order.status() != OrderStatus::Accepted {
2091 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
2092 self.generate_order_accepted(order, venue_order_id);
2093
2094 if matches!(
2095 order.order_type(),
2096 OrderType::TrailingStopLimit | OrderType::TrailingStopMarket
2097 ) && order.trigger_price().is_none()
2098 {
2099 self.update_trailing_stop_order(order);
2100 }
2101 }
2102
2103 let _ = self.core.add_order(
2104 PassiveOrderAny::try_from(order.to_owned()).expect("passive order conversion"),
2105 );
2106 }
2107
2108 fn expire_order(&mut self, order: &PassiveOrderAny) {
2109 if self.config.support_contingent_orders
2110 && order
2111 .contingency_type()
2112 .is_some_and(|c| c != ContingencyType::NoContingency)
2113 {
2114 self.cancel_contingent_orders(&OrderAny::from(order.clone()));
2115 }
2116
2117 self.generate_order_expired(&order.to_any());
2118 }
2119
2120 fn cancel_order(&mut self, order: &OrderAny, cancel_contingencies: Option<bool>) {
2121 let cancel_contingencies = cancel_contingencies.unwrap_or(true);
2122 if order.is_active_local() {
2123 log::error!(
2124 "Cannot cancel an order with {} from the matching engine",
2125 order.status()
2126 );
2127 return;
2128 }
2129
2130 if self.core.order_exists(order.client_order_id()) {
2132 let _ = self.core.delete_order(
2133 &PassiveOrderAny::try_from(order.clone()).expect("passive order conversion"),
2134 );
2135 }
2136 self.cached_filled_qty.remove(&order.client_order_id());
2137
2138 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
2139 self.generate_order_canceled(order, venue_order_id);
2140
2141 if self.config.support_contingent_orders
2142 && order.contingency_type().is_some()
2143 && order.contingency_type().unwrap() != ContingencyType::NoContingency
2144 && cancel_contingencies
2145 {
2146 self.cancel_contingent_orders(order);
2147 }
2148 }
2149
2150 fn update_order(
2151 &mut self,
2152 order: &mut OrderAny,
2153 quantity: Option<Quantity>,
2154 price: Option<Price>,
2155 trigger_price: Option<Price>,
2156 update_contingencies: Option<bool>,
2157 ) {
2158 let update_contingencies = update_contingencies.unwrap_or(true);
2159 let quantity = quantity.unwrap_or(order.quantity());
2160
2161 match order {
2162 OrderAny::Limit(_) | OrderAny::MarketToLimit(_) => {
2163 let price = price.unwrap_or(order.price().unwrap());
2164 self.update_limit_order(order, quantity, price);
2165 }
2166 OrderAny::StopMarket(_) => {
2167 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
2168 self.update_stop_market_order(order, quantity, trigger_price);
2169 }
2170 OrderAny::StopLimit(_) => {
2171 let price = price.unwrap_or(order.price().unwrap());
2172 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
2173 self.update_stop_limit_order(order, quantity, price, trigger_price);
2174 }
2175 OrderAny::MarketIfTouched(_) => {
2176 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
2177 self.update_market_if_touched_order(order, quantity, trigger_price);
2178 }
2179 OrderAny::LimitIfTouched(_) => {
2180 let price = price.unwrap_or(order.price().unwrap());
2181 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
2182 self.update_limit_if_touched_order(order, quantity, price, trigger_price);
2183 }
2184 OrderAny::TrailingStopMarket(_) => {
2185 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
2186 self.update_market_if_touched_order(order, quantity, trigger_price);
2187 }
2188 OrderAny::TrailingStopLimit(trailing_stop_limit_order) => {
2189 let price = price.unwrap_or(trailing_stop_limit_order.price().unwrap());
2190 let trigger_price =
2191 trigger_price.unwrap_or(trailing_stop_limit_order.trigger_price().unwrap());
2192 self.update_limit_if_touched_order(order, quantity, price, trigger_price);
2193 }
2194 _ => {
2195 panic!(
2196 "Unsupported order type {} for update_order",
2197 order.order_type()
2198 );
2199 }
2200 }
2201
2202 if self.config.support_contingent_orders
2203 && order
2204 .contingency_type()
2205 .is_some_and(|c| c != ContingencyType::NoContingency)
2206 && update_contingencies
2207 {
2208 self.update_contingent_order(order);
2209 }
2210 }
2211
2212 pub fn trigger_stop_order(&mut self, order: &mut OrderAny) {
2213 todo!("trigger_stop_order")
2214 }
2215
2216 fn update_contingent_order(&mut self, order: &OrderAny) {
2217 log::debug!("Updating OUO orders from {}", order.client_order_id());
2218 if let Some(linked_order_ids) = order.linked_order_ids() {
2219 for client_order_id in linked_order_ids {
2220 let mut child_order = match self.cache.borrow().order(client_order_id) {
2221 Some(order) => order.clone(),
2222 None => panic!("Order {client_order_id} not found in cache."),
2223 };
2224
2225 if child_order.is_active_local() {
2226 continue;
2227 }
2228
2229 if order.leaves_qty().is_zero() {
2230 self.cancel_order(&child_order, None);
2231 } else if child_order.leaves_qty() != order.leaves_qty() {
2232 let price = child_order.price();
2233 let trigger_price = child_order.trigger_price();
2234 self.update_order(
2235 &mut child_order,
2236 Some(order.leaves_qty()),
2237 price,
2238 trigger_price,
2239 Some(false),
2240 );
2241 }
2242 }
2243 }
2244 }
2245
2246 fn cancel_contingent_orders(&mut self, order: &OrderAny) {
2247 if let Some(linked_order_ids) = order.linked_order_ids() {
2248 for client_order_id in linked_order_ids {
2249 let contingent_order = match self.cache.borrow().order(client_order_id) {
2250 Some(order) => order.clone(),
2251 None => panic!("Cannot find contingent order for {client_order_id}"),
2252 };
2253 if contingent_order.is_active_local() {
2254 continue;
2256 }
2257 if !contingent_order.is_closed() {
2258 self.cancel_order(&contingent_order, Some(false));
2259 }
2260 }
2261 }
2262 }
2263
2264 fn generate_order_rejected(&self, order: &OrderAny, reason: Ustr) {
2267 let ts_now = self.clock.borrow().timestamp_ns();
2268 let account_id = order
2269 .account_id()
2270 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
2271
2272 let due_post_only = reason.as_str().starts_with("POST_ONLY");
2274
2275 let event = OrderEventAny::Rejected(OrderRejected::new(
2276 order.trader_id(),
2277 order.strategy_id(),
2278 order.instrument_id(),
2279 order.client_order_id(),
2280 account_id,
2281 reason,
2282 UUID4::new(),
2283 ts_now,
2284 ts_now,
2285 false,
2286 due_post_only,
2287 ));
2288 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2289 }
2290
2291 fn generate_order_accepted(&self, order: &mut OrderAny, venue_order_id: VenueOrderId) {
2292 let ts_now = self.clock.borrow().timestamp_ns();
2293 let account_id = order
2294 .account_id()
2295 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
2296 let event = OrderEventAny::Accepted(OrderAccepted::new(
2297 order.trader_id(),
2298 order.strategy_id(),
2299 order.instrument_id(),
2300 order.client_order_id(),
2301 venue_order_id,
2302 account_id,
2303 UUID4::new(),
2304 ts_now,
2305 ts_now,
2306 false,
2307 ));
2308 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2309
2310 order.apply(event).expect("Failed to apply order event");
2312 }
2313
2314 #[allow(clippy::too_many_arguments)]
2315 fn generate_order_modify_rejected(
2316 &self,
2317 trader_id: TraderId,
2318 strategy_id: StrategyId,
2319 instrument_id: InstrumentId,
2320 client_order_id: ClientOrderId,
2321 reason: Ustr,
2322 venue_order_id: Option<VenueOrderId>,
2323 account_id: Option<AccountId>,
2324 ) {
2325 let ts_now = self.clock.borrow().timestamp_ns();
2326 let event = OrderEventAny::ModifyRejected(OrderModifyRejected::new(
2327 trader_id,
2328 strategy_id,
2329 instrument_id,
2330 client_order_id,
2331 reason,
2332 UUID4::new(),
2333 ts_now,
2334 ts_now,
2335 false,
2336 venue_order_id,
2337 account_id,
2338 ));
2339 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2340 }
2341
2342 #[allow(clippy::too_many_arguments)]
2343 fn generate_order_cancel_rejected(
2344 &self,
2345 trader_id: TraderId,
2346 strategy_id: StrategyId,
2347 account_id: AccountId,
2348 instrument_id: InstrumentId,
2349 client_order_id: ClientOrderId,
2350 venue_order_id: VenueOrderId,
2351 reason: Ustr,
2352 ) {
2353 let ts_now = self.clock.borrow().timestamp_ns();
2354 let event = OrderEventAny::CancelRejected(OrderCancelRejected::new(
2355 trader_id,
2356 strategy_id,
2357 instrument_id,
2358 client_order_id,
2359 reason,
2360 UUID4::new(),
2361 ts_now,
2362 ts_now,
2363 false,
2364 Some(venue_order_id),
2365 Some(account_id),
2366 ));
2367 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2368 }
2369
2370 fn generate_order_updated(
2371 &self,
2372 order: &mut OrderAny,
2373 quantity: Quantity,
2374 price: Option<Price>,
2375 trigger_price: Option<Price>,
2376 ) {
2377 let ts_now = self.clock.borrow().timestamp_ns();
2378 let event = OrderEventAny::Updated(OrderUpdated::new(
2379 order.trader_id(),
2380 order.strategy_id(),
2381 order.instrument_id(),
2382 order.client_order_id(),
2383 quantity,
2384 UUID4::new(),
2385 ts_now,
2386 ts_now,
2387 false,
2388 order.venue_order_id(),
2389 order.account_id(),
2390 price,
2391 trigger_price,
2392 ));
2393 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2394
2395 order.apply(event).expect("Failed to apply order event");
2397 }
2398
2399 fn generate_order_canceled(&self, order: &OrderAny, venue_order_id: VenueOrderId) {
2400 let ts_now = self.clock.borrow().timestamp_ns();
2401 let event = OrderEventAny::Canceled(OrderCanceled::new(
2402 order.trader_id(),
2403 order.strategy_id(),
2404 order.instrument_id(),
2405 order.client_order_id(),
2406 UUID4::new(),
2407 ts_now,
2408 ts_now,
2409 false,
2410 Some(venue_order_id),
2411 order.account_id(),
2412 ));
2413 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2414 }
2415
2416 fn generate_order_triggered(&self, order: &OrderAny) {
2417 let ts_now = self.clock.borrow().timestamp_ns();
2418 let event = OrderEventAny::Triggered(OrderTriggered::new(
2419 order.trader_id(),
2420 order.strategy_id(),
2421 order.instrument_id(),
2422 order.client_order_id(),
2423 UUID4::new(),
2424 ts_now,
2425 ts_now,
2426 false,
2427 order.venue_order_id(),
2428 order.account_id(),
2429 ));
2430 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2431 }
2432
2433 fn generate_order_expired(&self, order: &OrderAny) {
2434 let ts_now = self.clock.borrow().timestamp_ns();
2435 let event = OrderEventAny::Expired(OrderExpired::new(
2436 order.trader_id(),
2437 order.strategy_id(),
2438 order.instrument_id(),
2439 order.client_order_id(),
2440 UUID4::new(),
2441 ts_now,
2442 ts_now,
2443 false,
2444 order.venue_order_id(),
2445 order.account_id(),
2446 ));
2447 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2448 }
2449
2450 #[allow(clippy::too_many_arguments)]
2451 fn generate_order_filled(
2452 &mut self,
2453 order: &mut OrderAny,
2454 venue_order_id: VenueOrderId,
2455 venue_position_id: Option<PositionId>,
2456 last_qty: Quantity,
2457 last_px: Price,
2458 quote_currency: Currency,
2459 commission: Money,
2460 liquidity_side: LiquiditySide,
2461 ) {
2462 let ts_now = self.clock.borrow().timestamp_ns();
2463 let account_id = order
2464 .account_id()
2465 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
2466 let event = OrderEventAny::Filled(OrderFilled::new(
2467 order.trader_id(),
2468 order.strategy_id(),
2469 order.instrument_id(),
2470 order.client_order_id(),
2471 venue_order_id,
2472 account_id,
2473 self.ids_generator.generate_trade_id(),
2474 order.order_side(),
2475 order.order_type(),
2476 last_qty,
2477 last_px,
2478 quote_currency,
2479 liquidity_side,
2480 UUID4::new(),
2481 ts_now,
2482 ts_now,
2483 false,
2484 venue_position_id,
2485 Some(commission),
2486 ));
2487 msgbus::send_any("ExecEngine.process".into(), &event as &dyn Any);
2488
2489 order.apply(event).expect("Failed to apply order event");
2491 }
2492}