1#![allow(dead_code)]
18#![allow(unused_variables)]
19
20pub mod config;
21pub mod ids_generator;
22
23#[cfg(test)]
24mod tests;
25
26use std::{
27 any::Any,
28 cell::RefCell,
29 cmp::min,
30 collections::HashMap,
31 ops::{Add, Sub},
32 rc::Rc,
33};
34
35use chrono::TimeDelta;
36use nautilus_common::{cache::Cache, msgbus::MessageBus};
37use nautilus_core::{AtomicTime, UnixNanos, UUID4};
38use nautilus_execution::{
39 matching_core::OrderMatchingCore,
40 messages::{BatchCancelOrders, CancelAllOrders, CancelOrder, ModifyOrder, QueryOrder},
41};
42use nautilus_model::{
43 data::{order::BookOrder, Bar, BarType, OrderBookDelta, OrderBookDeltas, QuoteTick, TradeTick},
44 enums::{
45 AccountType, AggregationSource, AggressorSide, BarAggregation, BookType, ContingencyType,
46 LiquiditySide, MarketStatus, MarketStatusAction, OmsType, OrderSide, OrderSideSpecified,
47 OrderStatus, OrderType, PriceType, TimeInForce,
48 },
49 events::{
50 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderEventAny, OrderExpired,
51 OrderFilled, OrderModifyRejected, OrderRejected, OrderTriggered, OrderUpdated,
52 },
53 identifiers::{
54 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TraderId, Venue,
55 VenueOrderId,
56 },
57 instruments::{InstrumentAny, EXPIRING_INSTRUMENT_TYPES},
58 orderbook::OrderBook,
59 orders::{
60 LimitOrderAny, Order, OrderAny, PassiveOrderAny, StopLimitOrder, StopMarketOrder,
61 StopOrderAny, TrailingStopLimitOrder, TrailingStopMarketOrder,
62 },
63 position::Position,
64 types::{fixed::FIXED_PRECISION, Currency, Money, Price, Quantity},
65};
66use ustr::Ustr;
67
68use crate::{
69 matching_engine::{config::OrderMatchingEngineConfig, ids_generator::IdsGenerator},
70 models::{
71 fee::{FeeModel, FeeModelAny},
72 fill::FillModel,
73 },
74};
75
76pub struct OrderMatchingEngine {
78 pub venue: Venue,
80 pub instrument: InstrumentAny,
82 pub raw_id: u32,
84 pub book_type: BookType,
86 pub oms_type: OmsType,
88 pub account_type: AccountType,
90 pub market_status: MarketStatus,
92 pub config: OrderMatchingEngineConfig,
94 clock: &'static AtomicTime,
95 msgbus: Rc<RefCell<MessageBus>>,
96 cache: Rc<RefCell<Cache>>,
97 book: OrderBook,
98 core: OrderMatchingCore,
99 fill_model: FillModel,
100 fee_model: FeeModelAny,
101 target_bid: Option<Price>,
102 target_ask: Option<Price>,
103 target_last: Option<Price>,
104 last_bar_bid: Option<Bar>,
105 last_bar_ask: Option<Bar>,
106 execution_bar_types: HashMap<InstrumentId, BarType>,
107 execution_bar_deltas: HashMap<BarType, TimeDelta>,
108 account_ids: HashMap<TraderId, AccountId>,
109 cached_filled_qty: HashMap<ClientOrderId, Quantity>,
110 ids_generator: IdsGenerator,
111}
112
113impl OrderMatchingEngine {
114 #[allow(clippy::too_many_arguments)]
116 pub fn new(
117 instrument: InstrumentAny,
118 raw_id: u32,
119 fill_model: FillModel,
120 fee_model: FeeModelAny,
121 book_type: BookType,
122 oms_type: OmsType,
123 account_type: AccountType,
124 clock: &'static AtomicTime,
125 msgbus: Rc<RefCell<MessageBus>>,
126 cache: Rc<RefCell<Cache>>,
127 config: OrderMatchingEngineConfig,
128 ) -> Self {
129 let book = OrderBook::new(instrument.id(), book_type);
130 let core = OrderMatchingCore::new(
131 instrument.id(),
132 instrument.price_increment(),
133 None, None, None, );
137 let ids_generator = IdsGenerator::new(
138 instrument.id().venue,
139 oms_type,
140 raw_id,
141 config.use_random_ids,
142 config.use_position_ids,
143 cache.clone(),
144 );
145
146 Self {
147 venue: instrument.id().venue,
148 instrument,
149 raw_id,
150 fill_model,
151 fee_model,
152 book_type,
153 oms_type,
154 account_type,
155 clock,
156 msgbus,
157 cache,
158 book,
159 core,
160 market_status: MarketStatus::Open,
161 config,
162 target_bid: None,
163 target_ask: None,
164 target_last: None,
165 last_bar_bid: None,
166 last_bar_ask: None,
167 execution_bar_types: HashMap::new(),
168 execution_bar_deltas: HashMap::new(),
169 account_ids: HashMap::new(),
170 cached_filled_qty: HashMap::new(),
171 ids_generator,
172 }
173 }
174
175 pub fn reset(&mut self) {
176 self.book.clear(0, UnixNanos::default());
177 self.execution_bar_types.clear();
178 self.execution_bar_deltas.clear();
179 self.account_ids.clear();
180 self.cached_filled_qty.clear();
181 self.core.reset();
182 self.target_bid = None;
183 self.target_ask = None;
184 self.target_last = None;
185 self.ids_generator.reset();
186
187 log::info!("Reset {}", self.instrument.id());
188 }
189
190 pub const fn set_fill_model(&mut self, fill_model: FillModel) {
191 self.fill_model = fill_model;
192 }
193
194 #[must_use]
195 pub fn best_bid_price(&self) -> Option<Price> {
196 self.book.best_bid_price()
197 }
198
199 #[must_use]
200 pub fn best_ask_price(&self) -> Option<Price> {
201 self.book.best_ask_price()
202 }
203
204 #[must_use]
205 pub const fn get_book(&self) -> &OrderBook {
206 &self.book
207 }
208
209 #[must_use]
210 pub fn get_open_bid_orders(&self) -> &[PassiveOrderAny] {
211 self.core.get_orders_bid()
212 }
213
214 #[must_use]
215 pub fn get_open_ask_orders(&self) -> &[PassiveOrderAny] {
216 self.core.get_orders_ask()
217 }
218
219 #[must_use]
220 pub fn get_open_orders(&self) -> Vec<PassiveOrderAny> {
221 let mut orders = Vec::new();
223 orders.extend_from_slice(self.core.get_orders_bid());
224 orders.extend_from_slice(self.core.get_orders_ask());
225 orders
226 }
227
228 #[must_use]
229 pub fn order_exists(&self, client_order_id: ClientOrderId) -> bool {
230 self.core.order_exists(client_order_id)
231 }
232
233 pub fn process_order_book_delta(&mut self, delta: &OrderBookDelta) {
237 log::debug!("Processing {delta}");
238
239 if self.book_type == BookType::L2_MBP || self.book_type == BookType::L3_MBO {
240 self.book.apply_delta(delta);
241 }
242
243 self.iterate(delta.ts_event);
244 }
245
246 pub fn process_order_book_deltas(&mut self, deltas: &OrderBookDeltas) {
247 log::debug!("Processing {deltas}");
248
249 if self.book_type == BookType::L2_MBP || self.book_type == BookType::L3_MBO {
250 self.book.apply_deltas(deltas);
251 }
252
253 self.iterate(deltas.ts_event);
254 }
255
256 pub fn process_quote_tick(&mut self, quote: &QuoteTick) {
257 log::debug!("Processing {quote}");
258
259 if self.book_type == BookType::L1_MBP {
260 self.book.update_quote_tick(quote).unwrap();
261 }
262
263 self.iterate(quote.ts_event);
264 }
265
266 pub fn process_bar(&mut self, bar: &Bar) {
267 log::debug!("Processing {bar}");
268
269 if !self.config.bar_execution || self.book_type != BookType::L1_MBP {
271 return;
272 }
273
274 let bar_type = bar.bar_type;
275 if bar_type.aggregation_source() == AggregationSource::Internal {
277 return;
278 }
279
280 if bar_type.spec().aggregation == BarAggregation::Month {
282 return;
283 }
284
285 let execution_bar_type =
286 if let Some(execution_bar_type) = self.execution_bar_types.get(&bar.instrument_id()) {
287 execution_bar_type.to_owned()
288 } else {
289 self.execution_bar_types
290 .insert(bar.instrument_id(), bar_type);
291 self.execution_bar_deltas
292 .insert(bar_type, bar_type.spec().timedelta());
293 bar_type
294 };
295
296 if execution_bar_type != bar_type {
297 let mut bar_type_timedelta = self.execution_bar_deltas.get(&bar_type).copied();
298 if bar_type_timedelta.is_none() {
299 bar_type_timedelta = Some(bar_type.spec().timedelta());
300 self.execution_bar_deltas
301 .insert(bar_type, bar_type_timedelta.unwrap());
302 }
303 if self.execution_bar_deltas.get(&execution_bar_type).unwrap()
304 >= &bar_type_timedelta.unwrap()
305 {
306 self.execution_bar_types
307 .insert(bar_type.instrument_id(), bar_type);
308 } else {
309 return;
310 }
311 }
312
313 match bar_type.spec().price_type {
314 PriceType::Last | PriceType::Mid => self.process_trade_ticks_from_bar(bar),
315 PriceType::Bid => {
316 self.last_bar_bid = Some(bar.to_owned());
317 self.process_quote_ticks_from_bar(bar);
318 }
319 PriceType::Ask => {
320 self.last_bar_ask = Some(bar.to_owned());
321 self.process_quote_ticks_from_bar(bar);
322 }
323 }
324 }
325
326 fn process_trade_ticks_from_bar(&mut self, bar: &Bar) {
327 let size = Quantity::new(bar.volume.as_f64() / 4.0, bar.volume.precision);
329 let aggressor_side = if !self.core.is_last_initialized || bar.open > self.core.last.unwrap()
330 {
331 AggressorSide::Buyer
332 } else {
333 AggressorSide::Seller
334 };
335
336 let mut trade_tick = TradeTick::new(
338 bar.instrument_id(),
339 bar.open,
340 size,
341 aggressor_side,
342 self.ids_generator.generate_trade_id(),
343 bar.ts_event,
344 bar.ts_event,
345 );
346
347 if !self.core.is_last_initialized {
350 self.book.update_trade_tick(&trade_tick).unwrap();
351 self.iterate(trade_tick.ts_init);
352 self.core.set_last_raw(trade_tick.price);
353 }
354
355 if self.core.last.is_some_and(|last| bar.high > last) {
358 trade_tick.price = bar.high;
359 trade_tick.aggressor_side = AggressorSide::Buyer;
360 trade_tick.trade_id = self.ids_generator.generate_trade_id();
361
362 self.book.update_trade_tick(&trade_tick).unwrap();
363 self.iterate(trade_tick.ts_init);
364
365 self.core.set_last_raw(trade_tick.price);
366 }
367
368 if self.core.last.is_some_and(|last| bar.low < last) {
372 trade_tick.price = bar.low;
373 trade_tick.aggressor_side = AggressorSide::Seller;
374 trade_tick.trade_id = self.ids_generator.generate_trade_id();
375
376 self.book.update_trade_tick(&trade_tick).unwrap();
377 self.iterate(trade_tick.ts_init);
378
379 self.core.set_last_raw(trade_tick.price);
380 }
381
382 if self.core.last.is_some_and(|last| bar.close != last) {
387 trade_tick.price = bar.close;
388 trade_tick.aggressor_side = if bar.close > self.core.last.unwrap() {
389 AggressorSide::Buyer
390 } else {
391 AggressorSide::Seller
392 };
393 trade_tick.trade_id = self.ids_generator.generate_trade_id();
394
395 self.book.update_trade_tick(&trade_tick).unwrap();
396 self.iterate(trade_tick.ts_init);
397
398 self.core.set_last_raw(trade_tick.price);
399 }
400 }
401
402 fn process_quote_ticks_from_bar(&mut self, bar: &Bar) {
403 if self.last_bar_bid.is_none()
405 || self.last_bar_ask.is_none()
406 || self.last_bar_bid.unwrap().ts_event != self.last_bar_ask.unwrap().ts_event
407 {
408 return;
409 }
410 let bid_bar = self.last_bar_bid.unwrap();
411 let ask_bar = self.last_bar_ask.unwrap();
412 let bid_size = Quantity::new(bid_bar.volume.as_f64() / 4.0, bar.volume.precision);
413 let ask_size = Quantity::new(ask_bar.volume.as_f64() / 4.0, bar.volume.precision);
414
415 let mut quote_tick = QuoteTick::new(
417 self.book.instrument_id,
418 bid_bar.open,
419 ask_bar.open,
420 bid_size,
421 ask_size,
422 bid_bar.ts_init,
423 bid_bar.ts_init,
424 );
425
426 self.book.update_quote_tick("e_tick).unwrap();
428 self.iterate(quote_tick.ts_init);
429
430 quote_tick.bid_price = bid_bar.high;
432 quote_tick.ask_price = ask_bar.high;
433 self.book.update_quote_tick("e_tick).unwrap();
434 self.iterate(quote_tick.ts_init);
435
436 quote_tick.bid_price = bid_bar.low;
438 quote_tick.ask_price = ask_bar.low;
439 self.book.update_quote_tick("e_tick).unwrap();
440 self.iterate(quote_tick.ts_init);
441
442 quote_tick.bid_price = bid_bar.close;
444 quote_tick.ask_price = ask_bar.close;
445 self.book.update_quote_tick("e_tick).unwrap();
446 self.iterate(quote_tick.ts_init);
447
448 self.last_bar_bid = None;
450 self.last_bar_ask = None;
451 }
452
453 pub fn process_trade_tick(&mut self, trade: &TradeTick) {
454 log::debug!("Processing {trade}");
455
456 if self.book_type == BookType::L1_MBP {
457 self.book.update_trade_tick(trade).unwrap();
458 }
459 self.core.set_last_raw(trade.price);
460
461 self.iterate(trade.ts_event);
462 }
463
464 pub fn process_status(&mut self, action: MarketStatusAction) {
465 log::debug!("Processing {action}");
466
467 if self.market_status == MarketStatus::Closed
469 && (action == MarketStatusAction::Trading || action == MarketStatusAction::PreOpen)
470 {
471 self.market_status = MarketStatus::Open;
472 }
473 if self.market_status == MarketStatus::Open && action == MarketStatusAction::Pause {
475 self.market_status = MarketStatus::Paused;
476 }
477 if self.market_status == MarketStatus::Open && action == MarketStatusAction::Suspend {
479 self.market_status = MarketStatus::Suspended;
480 }
481 if self.market_status == MarketStatus::Open
483 && (action == MarketStatusAction::Halt || action == MarketStatusAction::Close)
484 {
485 self.market_status = MarketStatus::Closed;
486 }
487 }
488
489 #[allow(clippy::needless_return)]
492 pub fn process_order(&mut self, order: &mut OrderAny, account_id: AccountId) {
493 {
495 let cache_borrow = self.cache.as_ref().borrow();
496
497 if self.core.order_exists(order.client_order_id()) {
498 self.generate_order_rejected(order, "Order already exists".into());
499 return;
500 }
501
502 self.account_ids.insert(order.trader_id(), account_id);
504
505 if EXPIRING_INSTRUMENT_TYPES.contains(&self.instrument.instrument_class()) {
507 if let Some(activation_ns) = self.instrument.activation_ns() {
508 if self.clock.get_time_ns() < activation_ns {
509 self.generate_order_rejected(
510 order,
511 format!(
512 "Contract {} is not yet active, activation {}",
513 self.instrument.id(),
514 self.instrument.activation_ns().unwrap()
515 )
516 .into(),
517 );
518 return;
519 }
520 }
521 if let Some(expiration_ns) = self.instrument.expiration_ns() {
522 if self.clock.get_time_ns() >= expiration_ns {
523 self.generate_order_rejected(
524 order,
525 format!(
526 "Contract {} has expired, expiration {}",
527 self.instrument.id(),
528 self.instrument.expiration_ns().unwrap()
529 )
530 .into(),
531 );
532 return;
533 }
534 }
535 }
536
537 if self.config.support_contingent_orders {
539 if let Some(parent_order_id) = order.parent_order_id() {
540 println!("Search for parent order {parent_order_id}");
541 let parent_order = cache_borrow.order(&parent_order_id);
542 if parent_order.is_none()
543 || parent_order.unwrap().contingency_type().unwrap() != ContingencyType::Oto
544 {
545 panic!("OTO parent not found");
546 }
547 if let Some(parent_order) = parent_order {
548 let parent_order_status = parent_order.status();
549 let order_is_open = order.is_open();
550 if parent_order.status() == OrderStatus::Rejected && order.is_open() {
551 self.generate_order_rejected(
552 order,
553 format!("Rejected OTO order from {parent_order_id}").into(),
554 );
555 return;
556 } else if parent_order.status() == OrderStatus::Accepted
557 && parent_order.status() == OrderStatus::Triggered
558 {
559 log::info!(
560 "Pending OTO order {} triggers from {parent_order_id}",
561 order.client_order_id(),
562 );
563 return;
564 }
565 }
566 }
567
568 if let Some(linked_order_ids) = order.linked_order_ids() {
569 for client_order_id in linked_order_ids {
570 match cache_borrow.order(&client_order_id) {
571 Some(contingent_order)
572 if (order.contingency_type().unwrap() == ContingencyType::Oco
573 || order.contingency_type().unwrap()
574 == ContingencyType::Ouo)
575 && !order.is_closed()
576 && contingent_order.is_closed() =>
577 {
578 self.generate_order_rejected(
579 order,
580 format!("Contingent order {client_order_id} already closed")
581 .into(),
582 );
583 return;
584 }
585 None => panic!("Cannot find contingent order for {client_order_id}"),
586 _ => {}
587 }
588 }
589 }
590 }
591
592 if order.quantity().precision != self.instrument.size_precision() {
594 self.generate_order_rejected(
595 order,
596 format!(
597 "Invalid order quantity precision for order {}, was {} when {} size precision is {}",
598 order.client_order_id(),
599 order.quantity().precision,
600 self.instrument.id(),
601 self.instrument.size_precision()
602 )
603 .into(),
604 );
605 return;
606 }
607
608 if let Some(price) = order.price() {
610 if price.precision != self.instrument.price_precision() {
611 self.generate_order_rejected(
612 order,
613 format!(
614 "Invalid order price precision for order {}, was {} when {} price precision is {}",
615 order.client_order_id(),
616 price.precision,
617 self.instrument.id(),
618 self.instrument.price_precision()
619 )
620 .into(),
621 );
622 return;
623 }
624 }
625
626 if let Some(trigger_price) = order.trigger_price() {
628 if trigger_price.precision != self.instrument.price_precision() {
629 self.generate_order_rejected(
630 order,
631 format!(
632 "Invalid order trigger price precision for order {}, was {} when {} price precision is {}",
633 order.client_order_id(),
634 trigger_price.precision,
635 self.instrument.id(),
636 self.instrument.price_precision()
637 )
638 .into(),
639 );
640 return;
641 }
642 }
643
644 let position: Option<&Position> = cache_borrow
646 .position_for_order(&order.client_order_id())
647 .or_else(|| {
648 if self.oms_type == OmsType::Netting {
649 let position_id = PositionId::new(
650 format!("{}-{}", order.instrument_id(), order.strategy_id()).as_str(),
651 );
652 cache_borrow.position(&position_id)
653 } else {
654 None
655 }
656 });
657
658 if order.order_side() == OrderSide::Sell
660 && self.account_type != AccountType::Margin
661 && matches!(self.instrument, InstrumentAny::Equity(_))
662 && (position.is_none()
663 || !order.would_reduce_only(position.unwrap().side, position.unwrap().quantity))
664 {
665 let position_string = position.map_or("None".to_string(), |pos| pos.id.to_string());
666 self.generate_order_rejected(
667 order,
668 format!(
669 "Short selling not permitted on a CASH account with position {position_string} and order {order}",
670 )
671 .into(),
672 );
673 return;
674 }
675
676 if self.config.use_reduce_only
678 && order.is_reduce_only()
679 && !order.is_closed()
680 && position.is_none_or(|pos| {
681 pos.is_closed()
682 || (order.is_buy() && pos.is_long())
683 || (order.is_sell() && pos.is_short())
684 })
685 {
686 self.generate_order_rejected(
687 order,
688 format!(
689 "Reduce-only order {} ({}-{}) would have increased position",
690 order.client_order_id(),
691 order.order_type().to_string().to_uppercase(),
692 order.order_side().to_string().to_uppercase()
693 )
694 .into(),
695 );
696 return;
697 }
698 }
699
700 match order.order_type() {
701 OrderType::Market => self.process_market_order(order),
702 OrderType::Limit => self.process_limit_order(order),
703 OrderType::MarketToLimit => self.process_market_to_limit_order(order),
704 OrderType::StopMarket => self.process_stop_market_order(order),
705 OrderType::StopLimit => self.process_stop_limit_order(order),
706 OrderType::MarketIfTouched => self.process_market_if_touched_order(order),
707 OrderType::LimitIfTouched => self.process_limit_if_touched_order(order),
708 OrderType::TrailingStopMarket => self.process_trailing_stop_market_order(order),
709 OrderType::TrailingStopLimit => self.process_trailing_stop_limit_order(order),
710 }
711 }
712
713 pub fn process_modify(&mut self, command: &ModifyOrder, account_id: AccountId) {
714 if let Some(order) = self.core.get_order(command.client_order_id) {
715 self.update_order(
716 &order.to_any(),
717 command.quantity,
718 command.price,
719 command.trigger_price,
720 None,
721 );
722 } else {
723 self.generate_order_modify_rejected(
724 command.trader_id,
725 command.strategy_id,
726 account_id,
727 command.instrument_id,
728 command.client_order_id,
729 command.venue_order_id,
730 Ustr::from(format!("Order {} not found", command.client_order_id).as_str()),
731 );
732 }
733 }
734
735 pub fn process_cancel(&mut self, command: &CancelOrder, account_id: AccountId) {
736 match self.core.get_order(command.client_order_id) {
737 Some(passive_order) => {
738 if passive_order.is_inflight() || passive_order.is_open() {
739 self.cancel_order(&OrderAny::from(passive_order.to_owned()), None);
740 }
741 }
742 None => self.generate_order_cancel_rejected(
743 command.trader_id,
744 command.strategy_id,
745 account_id,
746 command.instrument_id,
747 command.client_order_id,
748 command.venue_order_id,
749 Ustr::from(format!("Order {} not found", command.client_order_id).as_str()),
750 ),
751 }
752 }
753
754 pub fn process_cancel_all(&mut self, command: &CancelAllOrders, account_id: AccountId) {
755 let open_orders = self
756 .cache
757 .borrow()
758 .orders_open(None, Some(&command.instrument_id), None, None)
759 .into_iter()
760 .cloned()
761 .collect::<Vec<OrderAny>>();
762 for order in open_orders {
763 if command.order_side != OrderSide::NoOrderSide
764 && command.order_side != order.order_side()
765 {
766 continue;
767 }
768 if order.is_inflight() || order.is_open() {
769 self.cancel_order(&order, None);
770 }
771 }
772 }
773
774 pub fn process_batch_cancel(&mut self, command: &BatchCancelOrders, account_id: AccountId) {
775 for order in &command.cancels {
776 self.process_cancel(order, account_id);
777 }
778 }
779
780 pub fn process_query_order(&self, command: &QueryOrder, account_id: AccountId) {
781 todo!("implement process_query_order")
782 }
783
784 fn process_market_order(&mut self, order: &mut OrderAny) {
785 if order.time_in_force() == TimeInForce::AtTheOpen
786 || order.time_in_force() == TimeInForce::AtTheClose
787 {
788 log::error!(
789 "Market auction for the time in force {} is currently not supported",
790 order.time_in_force()
791 );
792 return;
793 }
794
795 let order_side = order.order_side();
797 let is_ask_initialized = self.core.is_ask_initialized;
798 let is_bid_initialized = self.core.is_bid_initialized;
799 if (order.order_side() == OrderSide::Buy && !self.core.is_ask_initialized)
800 || (order.order_side() == OrderSide::Sell && !self.core.is_bid_initialized)
801 {
802 self.generate_order_rejected(
803 order,
804 format!("No market for {}", order.instrument_id()).into(),
805 );
806 return;
807 }
808
809 self.fill_market_order(order);
810 }
811
812 fn process_limit_order(&mut self, order: &mut OrderAny) {
813 if order.is_post_only()
814 && self
815 .core
816 .is_limit_matched(&LimitOrderAny::from(order.to_owned()))
817 {
818 self.generate_order_rejected(
819 order,
820 format!(
821 "POST_ONLY {} {} order limit px of {} would have been a TAKER: bid={}, ask={}",
822 order.order_type(),
823 order.order_side(),
824 order.price().unwrap(),
825 self.core
826 .bid
827 .map_or_else(|| "None".to_string(), |p| p.to_string()),
828 self.core
829 .ask
830 .map_or_else(|| "None".to_string(), |p| p.to_string())
831 )
832 .into(),
833 );
834 return;
835 }
836
837 self.accept_order(order);
839
840 if self
842 .core
843 .is_limit_matched(&LimitOrderAny::from(order.to_owned()))
844 {
845 if order.liquidity_side().is_some()
847 && order.liquidity_side().unwrap() == LiquiditySide::NoLiquiditySide
848 {
849 order.set_liquidity_side(LiquiditySide::Taker);
850 }
851 self.fill_limit_order(order);
852 } else if matches!(order.time_in_force(), TimeInForce::Fok | TimeInForce::Ioc) {
853 self.cancel_order(order, None);
854 }
855 }
856
857 fn process_market_to_limit_order(&mut self, order: &OrderAny) {
858 todo!("process_market_to_limit_order")
859 }
860
861 fn process_stop_market_order(&mut self, order: &mut OrderAny) {
862 if self
863 .core
864 .is_stop_matched(&StopOrderAny::from(order.to_owned()))
865 {
866 if self.config.reject_stop_orders {
867 self.generate_order_rejected(
868 order,
869 format!(
870 "{} {} order stop px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
871 order.order_type(),
872 order.order_side(),
873 order.trigger_price().unwrap(),
874 self.core
875 .bid
876 .map_or_else(|| "None".to_string(), |p| p.to_string()),
877 self.core
878 .ask
879 .map_or_else(|| "None".to_string(), |p| p.to_string())
880 ).into(),
881 );
882 return;
883 }
884 self.fill_market_order(order);
885 return;
886 }
887
888 self.accept_order(order);
890 }
891
892 fn process_stop_limit_order(&mut self, order: &mut OrderAny) {
893 if self
894 .core
895 .is_stop_matched(&StopOrderAny::from(order.to_owned()))
896 {
897 if self.config.reject_stop_orders {
898 self.generate_order_rejected(
899 order,
900 format!(
901 "{} {} order stop px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
902 order.order_type(),
903 order.order_side(),
904 order.trigger_price().unwrap(),
905 self.core
906 .bid
907 .map_or_else(|| "None".to_string(), |p| p.to_string()),
908 self.core
909 .ask
910 .map_or_else(|| "None".to_string(), |p| p.to_string())
911 ).into(),
912 );
913 return;
914 }
915
916 self.accept_order(order);
917 self.generate_order_triggered(order);
918
919 if self
921 .core
922 .is_limit_matched(&LimitOrderAny::from(order.to_owned()))
923 {
924 order.set_liquidity_side(LiquiditySide::Taker);
925 self.fill_limit_order(order);
926 }
927 }
928
929 self.accept_order(order);
931 }
932
933 fn process_market_if_touched_order(&mut self, order: &OrderAny) {
934 todo!("process_market_if_touched_order")
935 }
936
937 fn process_limit_if_touched_order(&mut self, order: &OrderAny) {
938 todo!("process_limit_if_touched_order")
939 }
940
941 fn process_trailing_stop_market_order(&mut self, order: &OrderAny) {
942 todo!("process_trailing_stop_market_order")
943 }
944
945 fn process_trailing_stop_limit_order(&mut self, order: &OrderAny) {
946 todo!("process_trailing_stop_limit_order")
947 }
948
949 pub fn iterate(&mut self, timestamp_ns: UnixNanos) {
954 self.clock.set_time(timestamp_ns);
955
956 if self.book.has_bid() {
958 self.core.set_bid_raw(self.book.best_bid_price().unwrap());
959 }
960 if self.book.has_ask() {
961 self.core.set_ask_raw(self.book.best_ask_price().unwrap());
962 }
963 self.core.iterate();
964
965 self.core.bid = self.book.best_bid_price();
966 self.core.ask = self.book.best_ask_price();
967
968 let orders_bid = self.core.get_orders_bid().to_vec();
969 let orders_ask = self.core.get_orders_ask().to_vec();
970
971 self.iterate_orders(timestamp_ns, &orders_bid);
972 self.iterate_orders(timestamp_ns, &orders_ask);
973 }
974
975 fn iterate_orders(&mut self, timestamp_ns: UnixNanos, orders: &[PassiveOrderAny]) {
976 for order in orders {
977 if order.is_closed() {
978 continue;
979 }
980
981 if self.config.support_gtd_orders {
983 if let Some(expire_time) = order.expire_time() {
984 if timestamp_ns >= expire_time {
985 self.core.delete_order(order).unwrap();
987 self.cached_filled_qty.remove(&order.client_order_id());
988 self.expire_order(order);
989 }
990 }
991 }
992
993 if let PassiveOrderAny::Stop(o) = order {
995 match o {
996 StopOrderAny::TrailingStopMarket(o) => self.update_trailing_stop_market(o),
997 StopOrderAny::TrailingStopLimit(o) => self.update_trailing_stop_limit(o),
998 _ => {}
999 }
1000 }
1001
1002 if let Some(target_bid) = self.target_bid {
1004 self.core.bid = Some(target_bid);
1005 self.target_bid = None;
1006 }
1007 if let Some(target_ask) = self.target_ask {
1008 self.core.ask = Some(target_ask);
1009 self.target_ask = None;
1010 }
1011 if let Some(target_last) = self.target_last {
1012 self.core.last = Some(target_last);
1013 self.target_last = None;
1014 }
1015 }
1016
1017 self.target_bid = None;
1019 self.target_ask = None;
1020 self.target_last = None;
1021 }
1022
1023 fn determine_limit_price_and_volume(&mut self, order: &OrderAny) -> Vec<(Price, Quantity)> {
1024 match order.price() {
1025 Some(order_price) => {
1026 let book_order =
1028 BookOrder::new(order.order_side(), order_price, order.quantity(), 1);
1029
1030 let mut fills = self.book.simulate_fills(&book_order);
1031
1032 if fills.is_empty() {
1034 return fills;
1035 }
1036
1037 if let Some(triggered_price) = order.trigger_price() {
1039 if order
1041 .liquidity_side()
1042 .is_some_and(|liquidity_side| liquidity_side == LiquiditySide::Taker)
1043 {
1044 if order.order_side() == OrderSide::Sell && order_price > triggered_price {
1045 let first_fill = fills.first().unwrap();
1047 let triggered_qty = first_fill.1;
1048 fills[0] = (triggered_price, triggered_qty);
1049 self.target_bid = self.core.bid;
1050 self.target_ask = self.core.ask;
1051 self.target_last = self.core.last;
1052 self.core.set_ask_raw(order_price);
1053 self.core.set_last_raw(order_price);
1054 } else if order.order_side() == OrderSide::Buy
1055 && order_price < triggered_price
1056 {
1057 let first_fill = fills.first().unwrap();
1059 let triggered_qty = first_fill.1;
1060 fills[0] = (triggered_price, triggered_qty);
1061 self.target_bid = self.core.bid;
1062 self.target_ask = self.core.ask;
1063 self.target_last = self.core.last;
1064 self.core.set_bid_raw(order_price);
1065 self.core.set_last_raw(order_price);
1066 }
1067 }
1068 }
1069
1070 if order
1072 .liquidity_side()
1073 .is_some_and(|liquidity_side| liquidity_side == LiquiditySide::Maker)
1074 {
1075 match order.order_side().as_specified() {
1076 OrderSideSpecified::Buy => {
1077 let target_price = if order
1078 .trigger_price()
1079 .is_some_and(|trigger_price| order_price > trigger_price)
1080 {
1081 order.trigger_price().unwrap()
1082 } else {
1083 order_price
1084 };
1085 for fill in &fills {
1086 let last_px = fill.0;
1087 if last_px < order_price {
1088 self.target_bid = self.core.bid;
1090 self.target_ask = self.core.ask;
1091 self.target_last = self.core.last;
1092 self.core.set_ask_raw(target_price);
1093 self.core.set_last_raw(target_price);
1094 }
1095 }
1096 }
1097 OrderSideSpecified::Sell => {
1098 let target_price = if order
1099 .trigger_price()
1100 .is_some_and(|trigger_price| order_price < trigger_price)
1101 {
1102 order.trigger_price().unwrap()
1103 } else {
1104 order_price
1105 };
1106 for fill in &fills {
1107 let last_px = fill.0;
1108 if last_px > order_price {
1109 self.target_bid = self.core.bid;
1111 self.target_ask = self.core.ask;
1112 self.target_last = self.core.last;
1113 self.core.set_bid_raw(target_price);
1114 self.core.set_last_raw(target_price);
1115 }
1116 }
1117 }
1118 }
1119 }
1120
1121 fills
1122 }
1123 None => panic!("Limit order must have a price"),
1124 }
1125 }
1126
1127 fn determine_market_price_and_volume(&self, order: &OrderAny) -> Vec<(Price, Quantity)> {
1128 let price = match order.order_side().as_specified() {
1130 OrderSideSpecified::Buy => Price::max(FIXED_PRECISION),
1131 OrderSideSpecified::Sell => Price::min(FIXED_PRECISION),
1132 };
1133
1134 let book_order = BookOrder::new(order.order_side(), price, order.quantity(), 0);
1136 self.book.simulate_fills(&book_order)
1137 }
1138
1139 fn fill_market_order(&mut self, order: &mut OrderAny) {
1140 if let Some(filled_qty) = self.cached_filled_qty.get(&order.client_order_id()) {
1141 if filled_qty >= &order.quantity() {
1142 log::info!(
1143 "Ignoring fill as already filled pending application of events: {:?}, {:?}, {:?}, {:?}",
1144 filled_qty,
1145 order.quantity(),
1146 order.filled_qty(),
1147 order.quantity()
1148 );
1149 return;
1150 }
1151 }
1152
1153 let venue_position_id = self.ids_generator.get_position_id(order, Some(true));
1154 let position: Option<Position> = if let Some(venue_position_id) = venue_position_id {
1155 let cache = self.cache.as_ref().borrow();
1156 cache.position(&venue_position_id).cloned()
1157 } else {
1158 None
1159 };
1160
1161 if self.config.use_reduce_only && order.is_reduce_only() && position.is_none() {
1162 log::warn!(
1163 "Canceling REDUCE_ONLY {} as would increase position",
1164 order.order_type()
1165 );
1166 self.cancel_order(order, None);
1167 return;
1168 }
1169 order.set_liquidity_side(LiquiditySide::Taker);
1171 let fills = self.determine_market_price_and_volume(order);
1172 self.apply_fills(order, fills, LiquiditySide::Taker, None, position);
1173 }
1174
1175 fn fill_limit_order(&mut self, order: &OrderAny) {
1176 match order.price() {
1177 Some(order_price) => {
1178 let cached_filled_qty = self.cached_filled_qty.get(&order.client_order_id());
1179 if cached_filled_qty.is_some() && *cached_filled_qty.unwrap() >= order.quantity() {
1180 log::debug!(
1181 "Ignoring fill as already filled pending pending application of events: {}, {}, {}, {}",
1182 cached_filled_qty.unwrap(),
1183 order.quantity(),
1184 order.filled_qty(),
1185 order.leaves_qty(),
1186 );
1187 return;
1188 }
1189
1190 if order
1191 .liquidity_side()
1192 .is_some_and(|liquidity_side| liquidity_side == LiquiditySide::Maker)
1193 {
1194 if order.order_side() == OrderSide::Buy
1195 && self.core.bid.is_some_and(|bid| bid == order_price)
1196 && !self.fill_model.is_limit_filled()
1197 {
1198 return;
1200 }
1201 if order.order_side() == OrderSide::Sell
1202 && self.core.ask.is_some_and(|ask| ask == order_price)
1203 && !self.fill_model.is_limit_filled()
1204 {
1205 return;
1207 }
1208 }
1209
1210 let venue_position_id = self.ids_generator.get_position_id(order, None);
1211 let position = if let Some(venue_position_id) = venue_position_id {
1212 let cache = self.cache.as_ref().borrow();
1213 cache.position(&venue_position_id).cloned()
1214 } else {
1215 None
1216 };
1217
1218 if self.config.use_reduce_only && order.is_reduce_only() && position.is_none() {
1219 log::warn!(
1220 "Canceling REDUCE_ONLY {} as would increase position",
1221 order.order_type()
1222 );
1223 self.cancel_order(order, None);
1224 return;
1225 }
1226
1227 let fills = self.determine_limit_price_and_volume(order);
1228
1229 self.apply_fills(
1230 order,
1231 fills,
1232 order.liquidity_side().unwrap(),
1233 venue_position_id,
1234 position,
1235 );
1236 }
1237 None => panic!("Limit order must have a price"),
1238 }
1239 }
1240
1241 fn apply_fills(
1242 &mut self,
1243 order: &OrderAny,
1244 fills: Vec<(Price, Quantity)>,
1245 liquidity_side: LiquiditySide,
1246 venue_position_id: Option<PositionId>,
1247 position: Option<Position>,
1248 ) {
1249 if order.time_in_force() == TimeInForce::Fok {
1250 let mut total_size = Quantity::zero(order.quantity().precision);
1251 for (fill_px, fill_qty) in &fills {
1252 total_size = total_size.add(*fill_qty);
1253 }
1254
1255 if order.leaves_qty() > total_size {
1256 if order.is_active_local() {
1259 self.generate_order_rejected(
1260 order,
1261 "Fill or kill order cannot be filled at full amount".into(),
1262 );
1263 } else {
1264 self.cancel_order(order, None);
1265 }
1266 return;
1267 }
1268 }
1269
1270 if fills.is_empty() {
1271 if order.status() == OrderStatus::Submitted {
1272 self.generate_order_rejected(
1273 order,
1274 format!("No market for {}", order.instrument_id()).into(),
1275 );
1276 } else {
1277 log::error!("Cannot fill order: no fills from book when fills were expected (check size in data)");
1278 return;
1279 }
1280 }
1281
1282 if self.oms_type == OmsType::Netting {
1283 let venue_position_id: Option<PositionId> = None;
1284 }
1285
1286 let mut initial_market_to_limit_fill = false;
1287 for (mut fill_px, fill_qty) in &fills {
1288 assert!(
1290 (fill_px.precision == self.instrument.price_precision()),
1291 "Invalid price precision for fill price {} when instrument price precision is {}.\
1292 Check that the data price precision matches the {} instrument",
1293 fill_px.precision,
1294 self.instrument.price_precision(),
1295 self.instrument.id()
1296 );
1297
1298 assert!((fill_qty.precision == self.instrument.size_precision()),
1300 "Invalid quantity precision for fill quantity {} when instrument size precision is {}.\
1301 Check that the data quantity precision matches the {} instrument",
1302 fill_qty.precision,
1303 self.instrument.size_precision(),
1304 self.instrument.id()
1305 );
1306
1307 if order.filled_qty() == Quantity::zero(order.filled_qty().precision)
1308 && order.order_type() == OrderType::MarketToLimit
1309 {
1310 self.generate_order_updated(order, order.quantity(), Some(fill_px), None);
1311 initial_market_to_limit_fill = true;
1312 }
1313
1314 if self.book_type == BookType::L1_MBP && self.fill_model.is_slipped() {
1315 fill_px = match order.order_side().as_specified() {
1316 OrderSideSpecified::Buy => fill_px.add(self.instrument.price_increment()),
1317 OrderSideSpecified::Sell => fill_px.sub(self.instrument.price_increment()),
1318 }
1319 }
1320
1321 if self.config.use_reduce_only && order.is_reduce_only() {
1323 if let Some(position) = &position {
1324 if *fill_qty > position.quantity {
1325 if position.quantity == Quantity::zero(position.quantity.precision) {
1326 return;
1328 }
1329
1330 let adjusted_fill_qty =
1332 Quantity::from_raw(position.quantity.raw, fill_qty.precision);
1333
1334 self.generate_order_updated(order, adjusted_fill_qty, None, None);
1335 }
1336 }
1337 }
1338
1339 if fill_qty.is_zero() {
1340 if fills.len() == 1 && order.status() == OrderStatus::Submitted {
1341 self.generate_order_rejected(
1342 order,
1343 format!("No market for {}", order.instrument_id()).into(),
1344 );
1345 }
1346 return;
1347 }
1348
1349 self.fill_order(
1350 order,
1351 fill_px,
1352 *fill_qty,
1353 liquidity_side,
1354 venue_position_id,
1355 position.clone(),
1356 );
1357
1358 if order.order_type() == OrderType::MarketToLimit && initial_market_to_limit_fill {
1359 return;
1361 }
1362 }
1363
1364 if order.time_in_force() == TimeInForce::Ioc && order.is_open() {
1365 self.cancel_order(order, None);
1367 return;
1368 }
1369
1370 if order.is_open()
1371 && self.book_type == BookType::L1_MBP
1372 && matches!(
1373 order.order_type(),
1374 OrderType::Market | OrderType::MarketIfTouched | OrderType::StopMarket
1375 )
1376 {
1377 todo!("Exhausted simulated book volume")
1381 }
1382 }
1383
1384 fn fill_order(
1385 &mut self,
1386 order: &OrderAny,
1387 last_px: Price,
1388 last_qty: Quantity,
1389 liquidity_side: LiquiditySide,
1390 venue_position_id: Option<PositionId>,
1391 position: Option<Position>,
1392 ) {
1393 match self.cached_filled_qty.get(&order.client_order_id()) {
1394 Some(filled_qty) => {
1395 let leaves_qty = order.quantity() - *filled_qty;
1396 let last_qty = min(last_qty, leaves_qty);
1397 let new_filled_qty = *filled_qty + last_qty;
1398 self.cached_filled_qty
1400 .insert(order.client_order_id(), new_filled_qty);
1401 }
1402 None => {
1403 self.cached_filled_qty
1404 .insert(order.client_order_id(), last_qty);
1405 }
1406 }
1407
1408 let commission = self
1410 .fee_model
1411 .get_commission(order, last_qty, last_px, &self.instrument)
1412 .unwrap();
1413
1414 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
1415 self.generate_order_filled(
1416 order,
1417 venue_order_id,
1418 venue_position_id,
1419 last_qty,
1420 last_px,
1421 self.instrument.quote_currency(),
1422 commission,
1423 liquidity_side,
1424 );
1425
1426 if order.is_aggressive() && order.is_closed() {
1427 let passive_order = PassiveOrderAny::from(order.clone());
1429 self.core.delete_order(&passive_order).unwrap();
1430 self.cached_filled_qty.remove(&order.client_order_id());
1431 }
1432
1433 if !self.config.support_contingent_orders {
1434 return;
1435 }
1436
1437 todo!("Check for contingent orders")
1438 }
1439
1440 fn update_limit_order(&mut self, order: &OrderAny, quantity: Quantity, price: Price) {
1441 todo!("update_limit_order")
1442 }
1443
1444 fn update_stop_market_order(
1445 &mut self,
1446 order: &StopMarketOrder,
1447 quantity: Quantity,
1448 trigger_price: Price,
1449 ) {
1450 todo!("update_stop_market_order")
1451 }
1452
1453 fn update_stop_limit_order(
1454 &mut self,
1455 order: &StopLimitOrder,
1456 quantity: Quantity,
1457 price: Price,
1458 trigger_price: Price,
1459 ) {
1460 todo!("update_stop_limit_order")
1461 }
1462
1463 fn update_market_if_touched_order(
1464 &mut self,
1465 order: &OrderAny,
1466 quantity: Quantity,
1467 price: Price,
1468 ) {
1469 todo!("update_market_if_touched_order")
1470 }
1471
1472 fn update_limit_if_touched_order(
1473 &self,
1474 order: &OrderAny,
1475 quantity: Quantity,
1476 price: Price,
1477 trigger_price: Price,
1478 ) {
1479 todo!("update_limit_if_touched_order")
1480 }
1481
1482 fn update_trailing_stop_market(&mut self, order: &TrailingStopMarketOrder) {
1483 todo!()
1484 }
1485
1486 fn update_trailing_stop_limit(&mut self, order: &TrailingStopLimitOrder) {
1487 todo!()
1488 }
1489
1490 fn accept_order(&mut self, order: &mut OrderAny) {
1493 if order.is_closed() {
1494 return;
1496 }
1497 if order.status() != OrderStatus::Accepted {
1498 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
1499 self.generate_order_accepted(order, venue_order_id);
1500
1501 if matches!(
1502 order.order_type(),
1503 OrderType::TrailingStopLimit | OrderType::TrailingStopMarket
1504 ) && order.trigger_price().is_none()
1505 {
1506 match order.order_type() {
1507 OrderType::TrailingStopLimit => self
1508 .update_trailing_stop_limit(&TrailingStopLimitOrder::from(order.clone())),
1509 OrderType::TrailingStopMarket => self
1510 .update_trailing_stop_market(&TrailingStopMarketOrder::from(order.clone())),
1511 _ => {}
1512 }
1513 }
1514 }
1515
1516 let _ = self.core.add_order(order.to_owned().into());
1517 }
1518
1519 fn expire_order(&mut self, order: &PassiveOrderAny) {
1520 if self.config.support_contingent_orders
1521 && order
1522 .contingency_type()
1523 .is_some_and(|c| c != ContingencyType::NoContingency)
1524 {
1525 self.cancel_contingent_orders(&OrderAny::from(order.clone()));
1526 }
1527
1528 self.generate_order_expired(&order.to_any());
1529 }
1530
1531 fn cancel_order(&mut self, order: &OrderAny, cancel_contingencies: Option<bool>) {
1532 let cancel_contingencies = cancel_contingencies.unwrap_or(true);
1533 if order.is_active_local() {
1534 log::error!(
1535 "Cannot cancel an order with {} from the matching engine",
1536 order.status()
1537 );
1538 return;
1539 }
1540
1541 let _ = self
1543 .core
1544 .delete_order(&PassiveOrderAny::from(order.clone()));
1545 self.cached_filled_qty.remove(&order.client_order_id());
1546
1547 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
1548 self.generate_order_canceled(order, venue_order_id);
1549
1550 if self.config.support_contingent_orders
1551 && order.contingency_type().is_some()
1552 && order.contingency_type().unwrap() != ContingencyType::NoContingency
1553 && cancel_contingencies
1554 {
1555 self.cancel_contingent_orders(order);
1556 }
1557 }
1558
1559 fn update_order(
1560 &mut self,
1561 order: &OrderAny,
1562 quantity: Option<Quantity>,
1563 price: Option<Price>,
1564 trigger_price: Option<Price>,
1565 update_contingencies: Option<bool>,
1566 ) {
1567 let update_contingencies = update_contingencies.unwrap_or(true);
1568 let quantity = quantity.unwrap_or(order.quantity());
1569
1570 match order {
1571 OrderAny::Limit(_) | OrderAny::MarketToLimit(_) => {
1572 let price = price.unwrap_or(order.price().unwrap());
1573 self.update_limit_order(order, quantity, price);
1574 }
1575 OrderAny::StopMarket(stop_market_order) => {
1576 let trigger_price =
1577 trigger_price.unwrap_or(stop_market_order.trigger_price().unwrap());
1578 self.update_stop_market_order(stop_market_order, quantity, trigger_price);
1579 }
1580 OrderAny::StopLimit(stop_limit_order) => {
1581 let price = price.unwrap_or(stop_limit_order.price().unwrap());
1582 let trigger_price =
1583 trigger_price.unwrap_or(stop_limit_order.trigger_price().unwrap());
1584 self.update_stop_limit_order(stop_limit_order, quantity, price, trigger_price);
1585 }
1586 OrderAny::MarketIfTouched(_) => {
1587 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
1588 self.update_market_if_touched_order(order, quantity, trigger_price);
1589 }
1590 OrderAny::LimitIfTouched(_) => {
1591 let price = price.unwrap_or(order.price().unwrap());
1592 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
1593 self.update_limit_if_touched_order(order, quantity, price, trigger_price);
1594 }
1595 OrderAny::TrailingStopMarket(_) => {
1596 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
1597 self.update_market_if_touched_order(order, quantity, trigger_price);
1598 }
1599 OrderAny::TrailingStopLimit(trailing_stop_limit_order) => {
1600 let price = price.unwrap_or(trailing_stop_limit_order.price().unwrap());
1601 let trigger_price =
1602 trigger_price.unwrap_or(trailing_stop_limit_order.trigger_price().unwrap());
1603 self.update_limit_if_touched_order(order, quantity, price, trigger_price);
1604 }
1605 _ => {
1606 panic!(
1607 "Unsupported order type {} for update_order",
1608 order.order_type()
1609 );
1610 }
1611 }
1612
1613 if self.config.support_contingent_orders
1614 && order
1615 .contingency_type()
1616 .is_some_and(|c| c != ContingencyType::NoContingency)
1617 && update_contingencies
1618 {
1619 self.update_contingent_order(order);
1620 }
1621 }
1622
1623 fn trigger_stop_order(&mut self, order: &OrderAny) {
1624 todo!("trigger_stop_order")
1625 }
1626
1627 fn update_contingent_order(&mut self, order: &OrderAny) {
1628 todo!("update_contingent_order")
1629 }
1630
1631 fn cancel_contingent_orders(&mut self, order: &OrderAny) {
1632 if let Some(linked_order_ids) = order.linked_order_ids() {
1633 for client_order_id in &linked_order_ids {
1634 let contingent_order = match self.cache.borrow().order(client_order_id) {
1635 Some(order) => order.clone(),
1636 None => panic!("Cannot find contingent order for {client_order_id}"),
1637 };
1638 if contingent_order.is_active_local() {
1639 continue;
1641 }
1642 if !contingent_order.is_closed() {
1643 self.cancel_order(&contingent_order, Some(false));
1644 }
1645 }
1646 }
1647 }
1648
1649 fn generate_order_rejected(&self, order: &OrderAny, reason: Ustr) {
1652 let ts_now = self.clock.get_time_ns();
1653 let account_id = order
1654 .account_id()
1655 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
1656
1657 let event = OrderEventAny::Rejected(OrderRejected::new(
1658 order.trader_id(),
1659 order.strategy_id(),
1660 order.instrument_id(),
1661 order.client_order_id(),
1662 account_id,
1663 reason,
1664 UUID4::new(),
1665 ts_now,
1666 ts_now,
1667 false,
1668 ));
1669 let msgbus = self.msgbus.as_ref().borrow();
1670 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1671 }
1672
1673 fn generate_order_accepted(&self, order: &mut OrderAny, venue_order_id: VenueOrderId) {
1674 let ts_now = self.clock.get_time_ns();
1675 let account_id = order
1676 .account_id()
1677 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
1678 let event = OrderEventAny::Accepted(OrderAccepted::new(
1679 order.trader_id(),
1680 order.strategy_id(),
1681 order.instrument_id(),
1682 order.client_order_id(),
1683 venue_order_id,
1684 account_id,
1685 UUID4::new(),
1686 ts_now,
1687 ts_now,
1688 false,
1689 ));
1690 let msgbus = self.msgbus.as_ref().borrow();
1691 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1692
1693 order.apply(event).expect("Failed to apply order event");
1694 }
1695
1696 #[allow(clippy::too_many_arguments)]
1697 fn generate_order_modify_rejected(
1698 &self,
1699 trader_id: TraderId,
1700 strategy_id: StrategyId,
1701 account_id: AccountId,
1702 instrument_id: InstrumentId,
1703 client_order_id: ClientOrderId,
1704 venue_order_id: VenueOrderId,
1705 reason: Ustr,
1706 ) {
1707 let ts_now = self.clock.get_time_ns();
1708 let event = OrderEventAny::ModifyRejected(OrderModifyRejected::new(
1709 trader_id,
1710 strategy_id,
1711 instrument_id,
1712 client_order_id,
1713 reason,
1714 UUID4::new(),
1715 ts_now,
1716 ts_now,
1717 false,
1718 Some(venue_order_id),
1719 Some(account_id),
1720 ));
1721 let msgbus = self.msgbus.as_ref().borrow();
1722 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1723 }
1724
1725 #[allow(clippy::too_many_arguments)]
1726 fn generate_order_cancel_rejected(
1727 &self,
1728 trader_id: TraderId,
1729 strategy_id: StrategyId,
1730 account_id: AccountId,
1731 instrument_id: InstrumentId,
1732 client_order_id: ClientOrderId,
1733 venue_order_id: VenueOrderId,
1734 reason: Ustr,
1735 ) {
1736 let ts_now = self.clock.get_time_ns();
1737 let event = OrderEventAny::CancelRejected(OrderCancelRejected::new(
1738 trader_id,
1739 strategy_id,
1740 instrument_id,
1741 client_order_id,
1742 reason,
1743 UUID4::new(),
1744 ts_now,
1745 ts_now,
1746 false,
1747 Some(venue_order_id),
1748 Some(account_id),
1749 ));
1750 let msgbus = self.msgbus.as_ref().borrow();
1751 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1752 }
1753
1754 fn generate_order_updated(
1755 &self,
1756 order: &OrderAny,
1757 quantity: Quantity,
1758 price: Option<Price>,
1759 trigger_price: Option<Price>,
1760 ) {
1761 let ts_now = self.clock.get_time_ns();
1762 let event = OrderEventAny::Updated(OrderUpdated::new(
1763 order.trader_id(),
1764 order.strategy_id(),
1765 order.instrument_id(),
1766 order.client_order_id(),
1767 quantity,
1768 UUID4::new(),
1769 ts_now,
1770 ts_now,
1771 false,
1772 order.venue_order_id(),
1773 order.account_id(),
1774 price,
1775 trigger_price,
1776 ));
1777 let msgbus = self.msgbus.as_ref().borrow();
1778 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1779 }
1780
1781 fn generate_order_canceled(&self, order: &OrderAny, venue_order_id: VenueOrderId) {
1782 let ts_now = self.clock.get_time_ns();
1783 let event = OrderEventAny::Canceled(OrderCanceled::new(
1784 order.trader_id(),
1785 order.strategy_id(),
1786 order.instrument_id(),
1787 order.client_order_id(),
1788 UUID4::new(),
1789 ts_now,
1790 ts_now,
1791 false,
1792 Some(venue_order_id),
1793 order.account_id(),
1794 ));
1795 let msgbus = self.msgbus.as_ref().borrow();
1796 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1797 }
1798
1799 fn generate_order_triggered(&self, order: &OrderAny) {
1800 let ts_now = self.clock.get_time_ns();
1801 let event = OrderEventAny::Triggered(OrderTriggered::new(
1802 order.trader_id(),
1803 order.strategy_id(),
1804 order.instrument_id(),
1805 order.client_order_id(),
1806 UUID4::new(),
1807 ts_now,
1808 ts_now,
1809 false,
1810 order.venue_order_id(),
1811 order.account_id(),
1812 ));
1813 let msgbus = self.msgbus.as_ref().borrow();
1814 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1815 }
1816
1817 fn generate_order_expired(&self, order: &OrderAny) {
1818 let ts_now = self.clock.get_time_ns();
1819 let event = OrderEventAny::Expired(OrderExpired::new(
1820 order.trader_id(),
1821 order.strategy_id(),
1822 order.instrument_id(),
1823 order.client_order_id(),
1824 UUID4::new(),
1825 ts_now,
1826 ts_now,
1827 false,
1828 order.venue_order_id(),
1829 order.account_id(),
1830 ));
1831 let msgbus = self.msgbus.as_ref().borrow();
1832 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1833 }
1834
1835 #[allow(clippy::too_many_arguments)]
1836 fn generate_order_filled(
1837 &mut self,
1838 order: &OrderAny,
1839 venue_order_id: VenueOrderId,
1840 venue_position_id: Option<PositionId>,
1841 last_qty: Quantity,
1842 last_px: Price,
1843 quote_currency: Currency,
1844 commission: Money,
1845 liquidity_side: LiquiditySide,
1846 ) {
1847 let ts_now = self.clock.get_time_ns();
1848 let account_id = order
1849 .account_id()
1850 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
1851 let event = OrderEventAny::Filled(OrderFilled::new(
1852 order.trader_id(),
1853 order.strategy_id(),
1854 order.instrument_id(),
1855 order.client_order_id(),
1856 venue_order_id,
1857 account_id,
1858 self.ids_generator.generate_trade_id(),
1859 order.order_side(),
1860 order.order_type(),
1861 last_qty,
1862 last_px,
1863 quote_currency,
1864 liquidity_side,
1865 UUID4::new(),
1866 ts_now,
1867 ts_now,
1868 false,
1869 venue_position_id,
1870 Some(commission),
1871 ));
1872 let msgbus = self.msgbus.as_ref().borrow();
1873 msgbus.send(&msgbus.switchboard.exec_engine_process, &event as &dyn Any);
1874 }
1875}