1pub mod config;
19pub mod ids_generator;
20pub mod inflight;
21pub mod orders;
22
23mod settlement;
24
25#[cfg(test)]
26mod tests;
27
28use std::{
29 cell::RefCell,
30 cmp::min,
31 fmt::Debug,
32 mem,
33 ops::{Add, Sub},
34 rc::Rc,
35};
36
37use indexmap::{IndexMap, IndexSet};
38use jiff::SignedDuration;
39use nautilus_common::{
40 cache::Cache,
41 clock::Clock,
42 messages::execution::{
43 BatchCancelOrders, BatchModifyOrders, CancelAllOrders, CancelOrder, ModifyOrder,
44 },
45 msgbus::{self, MessagingSwitchboard},
46};
47use nautilus_core::{UUID4, UnixNanos, correctness::CorrectnessResult};
48use nautilus_model::{
49 data::{
50 Bar, BarType, InstrumentClose, MarkPriceUpdate, OrderBookDelta, OrderBookDeltas,
51 OrderBookDepth, QuoteTick, TradeTick,
52 order::{BookOrder, OrderId},
53 },
54 enums::{
55 AccountType, AggregationSource, AggressorSide, BookAction, BookType, ContingencyType,
56 InstrumentCloseType, LiquiditySide, MarketStatus, MarketStatusAction, OmsType, OrderSide,
57 OrderStatus, OrderType, PositionSide, PriceType, RecordFlag, TimeInForce, TriggerType,
58 },
59 events::{
60 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderEventAny, OrderExpired,
61 OrderFilled, OrderModifyRejected, OrderRejected, OrderSubmitted, OrderTriggered,
62 OrderUpdated,
63 },
64 identifiers::{
65 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId, Venue,
66 VenueOrderId,
67 },
68 instruments::{Instrument, InstrumentAny},
69 orderbook::{BookLevel, OrderBook},
70 orders::{MarketOrder, Order, OrderAny, OrderCore},
71 position::{Position, PositionReplayEvent},
72 types::{
73 Currency, Money, Price, Quantity,
74 fixed::{FIXED_PRECISION, raw_scales_match},
75 price::PriceRaw,
76 quantity::QuantityRaw,
77 },
78};
79use rust_decimal::{Decimal, RoundingStrategy};
80use ustr::Ustr;
81
82use self::{
83 config::OrderMatchingEngineConfig,
84 ids_generator::IdsGenerator,
85 inflight::InflightOrders,
86 orders::{MatchingOrders, OtoCommand},
87};
88use crate::{
89 matching::{MatchAction, OrderMatchingCore, RestingOrder},
90 models::{
91 fee::{FeeModel, FeeModelHandle},
92 fill::{FillModel, FillModelHandle, is_price_tradable},
93 },
94 protection::protection_price_calculate,
95 trailing::{store_trailing_stop_activation, trailing_stop_calculate},
96};
97
98pub struct OrderMatchingEngine {
100 pub venue: Venue,
102 pub instrument: InstrumentAny,
104 pub raw_id: u32,
106 pub book_type: BookType,
108 pub oms_type: OmsType,
110 pub account_type: AccountType,
112 pub market_status: MarketStatus,
114 pub config: OrderMatchingEngineConfig,
116 core: OrderMatchingCore,
117 clock: Rc<RefCell<dyn Clock>>,
118 cache: Rc<RefCell<Cache>>,
119 book: OrderBook,
120 fill_model: FillModelHandle,
121 fee_model: FeeModelHandle,
122 event_handler: Option<Rc<dyn Fn(OrderEventAny)>>,
123 inflight_orders: InflightOrders,
124 target_bid: Option<Price>,
125 target_ask: Option<Price>,
126 target_last: Option<Price>,
127 last_bar_bid: Option<Bar>,
128 last_bar_ask: Option<Bar>,
129 fill_at_market: bool,
130 collapse_bar_trades: bool,
131 last_bar_trade: Option<(Price, Quantity, AggressorSide)>,
132 execution_bar_types: IndexMap<InstrumentId, BarType>,
133 execution_bar_deltas: IndexMap<BarType, SignedDuration>,
134 account_ids: IndexMap<TraderId, AccountId>,
135 pending_fills: IndexMap<TradeId, PendingFill>,
136 post_match_order_ids: IndexSet<ClientOrderId>,
137 orders: MatchingOrders,
138 ids_generator: IdsGenerator,
139 last_trade_size: Option<Quantity>,
140 l1_trade_size: Option<QuantityRaw>,
141 trade_consumption: QuantityRaw,
142 bid_consumption: IndexMap<PriceRaw, (QuantityRaw, QuantityRaw)>,
143 ask_consumption: IndexMap<PriceRaw, (QuantityRaw, QuantityRaw)>,
144 queue_pending: IndexMap<ClientOrderId, PriceRaw>,
145 queue_ahead_orders: IndexMap<ClientOrderId, IndexMap<OrderId, QuantityRaw>>,
146 queue_ahead_total: IndexMap<ClientOrderId, (PriceRaw, QuantityRaw)>,
147 queue_snapshot_in_progress: bool,
148 queue_ids_by_price: IndexMap<PriceRaw, IndexSet<ClientOrderId>>,
149 queue_excess: IndexMap<ClientOrderId, QuantityRaw>,
150 queue_id_scratch: Vec<ClientOrderId>,
151 queue_pending_scratch: Vec<(ClientOrderId, PriceRaw)>,
152 queue_stale_scratch: Vec<ClientOrderId>,
153 queue_entry_scratch: Vec<(ClientOrderId, QuantityRaw, QuantityRaw)>,
154 prev_bid_price_raw: PriceRaw,
155 prev_ask_price_raw: PriceRaw,
156 tob_initialized: bool,
157 last_quote_bid: Option<Price>,
158 last_quote_ask: Option<Price>,
159 precision_mismatch_streak: u32,
160 instrument_close: Option<InstrumentClose>,
161 pending_resolution: bool,
162 expiration_processed: bool,
163 option_settlement_failed: bool,
164 option_settlement_warning: Option<&'static str>,
165}
166
167impl Debug for OrderMatchingEngine {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 f.debug_struct(stringify!(OrderMatchingEngine))
170 .field("venue", &self.venue)
171 .field("instrument", &self.instrument.id())
172 .finish()
173 }
174}
175
176impl OrderMatchingEngine {
177 #[expect(clippy::too_many_arguments)]
179 pub fn new(
180 instrument: InstrumentAny,
181 raw_id: u32,
182 fill_model: FillModelHandle,
183 fee_model: FeeModelHandle,
184 book_type: BookType,
185 oms_type: OmsType,
186 account_type: AccountType,
187 clock: Rc<RefCell<dyn Clock>>,
188 cache: Rc<RefCell<Cache>>,
189 config: OrderMatchingEngineConfig,
190 ) -> Self {
191 let book = OrderBook::new(instrument.id(), book_type);
192 let mut core = OrderMatchingCore::new(instrument.id(), instrument.price_increment());
193 core.set_fill_limit_inside_spread(Self::fill_limit_inside_spread_or_false(&fill_model));
194
195 let ids_generator = IdsGenerator::new(
196 instrument.id().venue,
197 oms_type,
198 raw_id,
199 config.use_random_ids,
200 config.use_position_ids,
201 cache.clone(),
202 );
203
204 Self {
205 venue: instrument.id().venue,
206 instrument,
207 raw_id,
208 fill_model,
209 fee_model,
210 event_handler: None,
211 inflight_orders: InflightOrders::default(),
212 book_type,
213 oms_type,
214 account_type,
215 clock,
216 cache,
217 book,
218 market_status: MarketStatus::Open,
219 config,
220 core,
221 target_bid: None,
222 target_ask: None,
223 target_last: None,
224 last_bar_bid: None,
225 last_bar_ask: None,
226 fill_at_market: true,
227 collapse_bar_trades: false,
228 last_bar_trade: None,
229 execution_bar_types: IndexMap::new(),
230 execution_bar_deltas: IndexMap::new(),
231 account_ids: IndexMap::new(),
232 pending_fills: IndexMap::new(),
233 post_match_order_ids: IndexSet::new(),
234 orders: MatchingOrders::default(),
235 ids_generator,
236 last_trade_size: None,
237 l1_trade_size: None,
238 trade_consumption: 0,
239 bid_consumption: IndexMap::new(),
240 ask_consumption: IndexMap::new(),
241 queue_pending: IndexMap::new(),
242 queue_ahead_orders: IndexMap::new(),
243 queue_ahead_total: IndexMap::new(),
244 queue_snapshot_in_progress: false,
245 queue_ids_by_price: IndexMap::new(),
246 queue_excess: IndexMap::new(),
247 queue_id_scratch: Vec::new(),
248 queue_pending_scratch: Vec::new(),
249 queue_stale_scratch: Vec::new(),
250 queue_entry_scratch: Vec::new(),
251 prev_bid_price_raw: 0,
252 prev_ask_price_raw: 0,
253 tob_initialized: false,
254 last_quote_bid: None,
255 last_quote_ask: None,
256 precision_mismatch_streak: 0,
257 instrument_close: None,
258 pending_resolution: false,
259 expiration_processed: false,
260 option_settlement_failed: false,
261 option_settlement_warning: None,
262 }
263 }
264
265 pub fn set_event_handler(&mut self, handler: Rc<dyn Fn(OrderEventAny)>) {
272 self.event_handler = Some(handler);
273 }
274
275 pub fn set_inflight_orders(&mut self, orders: InflightOrders) {
277 self.inflight_orders = orders;
278 }
279
280 pub fn set_matching_orders(&mut self, orders: MatchingOrders) {
285 self.orders = orders;
286 }
287
288 fn dispatch_order_event(&self, event: OrderEventAny) {
289 if let Some(handler) = &self.event_handler {
290 handler(event);
291 } else {
292 let endpoint = MessagingSwitchboard::exec_engine_process();
293 msgbus::send_order_event(endpoint, event);
294 }
295 }
296
297 pub fn reset(&mut self) {
303 self.book.reset();
304 self.execution_bar_types.clear();
305 self.execution_bar_deltas.clear();
306 self.account_ids.clear();
307 self.pending_fills.clear();
308 self.post_match_order_ids.clear();
309 self.orders
310 .reset(self.instrument.id(), &self.cache.borrow());
311 self.core.reset();
312 self.target_bid = None;
313 self.target_ask = None;
314 self.target_last = None;
315 self.last_trade_size = None;
316 self.l1_trade_size = None;
317 self.trade_consumption = 0;
318 self.bid_consumption.clear();
319 self.ask_consumption.clear();
320 self.queue_pending.clear();
321 self.queue_ahead_orders.clear();
322 self.queue_ahead_total.clear();
323 self.queue_snapshot_in_progress = false;
324 self.queue_ids_by_price.clear();
325 self.queue_excess.clear();
326 self.queue_id_scratch.clear();
327 self.queue_pending_scratch.clear();
328 self.queue_stale_scratch.clear();
329 self.queue_entry_scratch.clear();
330 self.prev_bid_price_raw = 0;
331 self.prev_ask_price_raw = 0;
332 self.tob_initialized = false;
333 self.last_quote_bid = None;
334 self.last_quote_ask = None;
335 self.last_bar_bid = None;
336 self.last_bar_ask = None;
337 self.precision_mismatch_streak = 0;
338 self.instrument_close = None;
339 self.market_status = MarketStatus::Open;
340 self.pending_resolution = false;
341 self.expiration_processed = false;
342 self.option_settlement_failed = false;
343 self.option_settlement_warning = None;
344 self.fill_at_market = true;
345 self.ids_generator.reset();
346
347 log::info!("Reset {}", self.instrument.id());
348 }
349
350 fn apply_liquidity_consumption(
351 &mut self,
352 mut fills: Vec<(Price, Quantity)>,
353 order: &OrderAny,
354 leaves_qty: Quantity,
355 book_prices: Option<&[Price]>,
356 trade_size: Option<QuantityRaw>,
357 mut journal: Option<&mut ConsumptionJournal>,
358 ) -> Vec<(Price, Quantity)> {
359 if !self.config.liquidity_consumption {
360 return fills;
361 }
362
363 let mut adjusted_len = 0;
364
365 let mut remaining_qty = trade_size.map_or(leaves_qty.raw(), |trade_size| {
366 leaves_qty
367 .raw()
368 .min(trade_size.saturating_sub(self.trade_consumption))
369 });
370
371 for fill_idx in 0..fills.len() {
372 if remaining_qty == 0 {
373 break;
374 }
375
376 let (price, qty) = fills[fill_idx];
377
378 let book_price = book_prices
381 .and_then(|bp| bp.get(fill_idx).copied())
382 .unwrap_or(price);
383
384 let adjusted_qty_raw = self.consume_book_level(
385 order.order_side(),
386 book_price,
387 qty,
388 remaining_qty,
389 journal.as_deref_mut(),
390 );
391
392 if adjusted_qty_raw == 0 {
393 continue;
394 }
395
396 remaining_qty -= adjusted_qty_raw;
397 if trade_size.is_some() {
398 self.trade_consumption += adjusted_qty_raw;
399 }
400
401 let adjusted_qty = Quantity::from_raw(adjusted_qty_raw, qty.precision);
402 fills[adjusted_len] = (price, adjusted_qty);
403 adjusted_len += 1;
404
405 if order.order_type() == OrderType::MarketToLimit && order.filled_qty().is_zero() {
406 break;
407 }
408 }
409
410 fills.truncate(adjusted_len);
411 fills
412 }
413
414 fn consume_book_level(
415 &mut self,
416 order_side: OrderSide,
417 book_price: Price,
418 qty: Quantity,
419 remaining_qty: QuantityRaw,
420 journal: Option<&mut ConsumptionJournal>,
421 ) -> QuantityRaw {
422 let book_price_raw = book_price.raw();
423 let level_size = self
424 .book
425 .get_quantity_at_level(book_price, order_side, qty.precision);
426
427 let consumption = match order_side {
428 OrderSide::Buy => &mut self.ask_consumption,
429 OrderSide::Sell => &mut self.bid_consumption,
430 };
431
432 if let Some(journal) = journal {
433 journal
434 .levels
435 .push((book_price_raw, consumption.get(&book_price_raw).copied()));
436 }
437
438 let (original_size, consumed) = consumption
439 .entry(book_price_raw)
440 .or_insert((level_size.raw(), 0));
441
442 if *original_size != level_size.raw() {
444 *original_size = level_size.raw();
445 *consumed = 0;
446 }
447
448 let available = original_size.saturating_sub(*consumed);
449 let adjusted_qty_raw = min(min(qty.raw(), available), remaining_qty);
450 *consumed += adjusted_qty_raw;
451 adjusted_qty_raw
452 }
453
454 fn fok_consumption_journal(&self, order: &OrderAny) -> Option<ConsumptionJournal> {
461 if !self.config.liquidity_consumption || order.time_in_force() != TimeInForce::Fok {
462 return None;
463 }
464
465 Some(ConsumptionJournal {
466 order_side: order.order_side(),
467 trade_consumption: self.trade_consumption,
468 levels: Vec::new(),
469 })
470 }
471
472 fn revert_consumption_journal(&mut self, journal: ConsumptionJournal) {
473 self.trade_consumption = journal.trade_consumption;
474
475 let consumption = match journal.order_side {
476 OrderSide::Buy => &mut self.ask_consumption,
477 OrderSide::Sell => &mut self.bid_consumption,
478 };
479
480 for (price_raw, prior) in journal.levels.into_iter().rev() {
483 match prior {
484 Some(prior) => {
485 consumption.insert(price_raw, prior);
486 }
487 None => {
488 consumption.shift_remove(&price_raw);
489 }
490 }
491 }
492 }
493
494 fn is_fok_unfillable(
495 &self,
496 order: &OrderAny,
497 fills: &[(Price, Quantity)],
498 required_qty: Quantity,
499 ) -> bool {
500 if order.time_in_force() != TimeInForce::Fok {
501 return false;
502 }
503
504 let mut total_size = Quantity::zero(order.quantity().precision);
505 let initial_market_to_limit =
506 order.order_type() == OrderType::MarketToLimit && order.filled_qty().is_zero();
507 let mut first_price = None;
508
509 for &(fill_px, fill_qty) in fills {
510 if let Some(fill_px) = self.normalize_price_for_current_instrument(fill_px)
511 && let Some(fill_qty) = self.normalize_quantity_for_current_instrument(fill_qty)
512 {
513 if initial_market_to_limit {
514 if fill_qty.is_zero() {
515 continue;
516 }
517
518 if first_price.is_some_and(|price| price != fill_px) {
519 break;
520 }
521
522 first_price = Some(fill_px);
523 }
524
525 total_size = total_size.add(fill_qty);
526 }
527 }
528
529 required_qty > total_size
530 }
531
532 fn seed_trade_consumption(
533 &mut self,
534 trade_price_raw: PriceRaw,
535 trade_size_raw: QuantityRaw,
536 trade_ts_event: UnixNanos,
537 aggressor_side: AggressorSide,
538 ) {
539 if trade_size_raw == 0 {
540 return;
541 }
542
543 if self.book.ts_last > trade_ts_event {
546 return;
547 }
548
549 let book = &self.book;
550
551 let consumption = match aggressor_side {
552 AggressorSide::Buy => &mut self.ask_consumption,
553 AggressorSide::Sell => &mut self.bid_consumption,
554 AggressorSide::NoAggressor => return,
555 };
556
557 let mut remaining = trade_size_raw;
558
559 match aggressor_side {
560 AggressorSide::Buy => {
561 for level in book
562 .asks(None)
563 .take_while(|level| level.price.value.raw() <= trade_price_raw)
564 {
565 Self::consume_trade_level(consumption, &mut remaining, level);
566 if remaining == 0 {
567 break;
568 }
569 }
570 }
571 AggressorSide::Sell => {
572 for level in book
573 .bids(None)
574 .take_while(|level| level.price.value.raw() >= trade_price_raw)
575 {
576 Self::consume_trade_level(consumption, &mut remaining, level);
577 if remaining == 0 {
578 break;
579 }
580 }
581 }
582 AggressorSide::NoAggressor => unreachable!(),
583 }
584 }
585
586 fn consume_trade_level(
587 consumption: &mut IndexMap<PriceRaw, (QuantityRaw, QuantityRaw)>,
588 remaining: &mut QuantityRaw,
589 level: &BookLevel,
590 ) {
591 let level_size = level.size_raw();
592 let entry = consumption
593 .entry(level.price.value.raw())
594 .or_insert((level_size, 0));
595
596 if entry.0 != level_size {
598 entry.0 = level_size;
599 entry.1 = 0;
600 }
601
602 let available = level_size.saturating_sub(entry.1);
603 let consume = min(*remaining, available);
604 entry.1 += consume;
605 *remaining -= consume;
606 }
607
608 pub fn set_fill_model(&mut self, fill_model: FillModelHandle) {
610 self.core
611 .set_fill_limit_inside_spread(Self::fill_limit_inside_spread_or_false(&fill_model));
612 self.fill_model = fill_model;
613 }
614
615 fn fill_limit_inside_spread_or_false(fill_model: &FillModelHandle) -> bool {
616 fill_model.fill_limit_inside_spread().unwrap_or_else(|e| {
617 log::error!("Failed to query fill model spread behavior: {e}");
618 false
619 })
620 }
621
622 fn snapshot_queue_position(&mut self, order: &OrderAny, price: Price) {
623 if !self.config.queue_position {
624 return;
625 }
626
627 let size_prec = self.instrument.size_precision();
628
629 let qty_ahead = self.book.get_quantity_at_level(
632 price,
633 OrderCore::opposite_side(order.order_side()),
634 size_prec,
635 );
636
637 let client_order_id = order.client_order_id();
638
639 self.remove_queue_position(client_order_id);
640 self.queue_ids_by_price
641 .entry(price.raw())
642 .or_default()
643 .insert(client_order_id);
644
645 if self.book_type == BookType::L1_MBP && qty_ahead.is_zero() {
650 let behind_bbo = match order.order_side() {
651 OrderSide::Buy => self.book.best_bid_price().is_some_and(|bid| price < bid),
652 OrderSide::Sell => self.book.best_ask_price().is_some_and(|ask| price > ask),
653 };
654
655 if behind_bbo {
656 self.queue_pending.insert(client_order_id, price.raw());
657 return;
658 }
659 }
660
661 self.queue_ahead_total
662 .insert(client_order_id, (price.raw(), qty_ahead.raw()));
663
664 if self.book_type == BookType::L3_MBO {
666 let orders_ahead: IndexMap<OrderId, QuantityRaw> = self
667 .book
668 .get_orders_at_level(price, OrderCore::opposite_side(order.order_side()))
669 .iter()
670 .map(|book_order| (book_order.order_id, book_order.size.raw()))
671 .collect();
672 self.queue_ahead_orders
673 .insert(client_order_id, orders_ahead);
674 }
675 }
676
677 fn remove_queue_position(&mut self, client_order_id: ClientOrderId) {
678 let pending_price = self.queue_pending.shift_remove(&client_order_id);
679 let ahead_price = self
680 .queue_ahead_total
681 .shift_remove(&client_order_id)
682 .map(|(price_raw, _)| price_raw);
683 self.queue_ahead_orders.shift_remove(&client_order_id);
684 self.queue_excess.shift_remove(&client_order_id);
685
686 for price_raw in [pending_price, ahead_price].into_iter().flatten() {
687 let remove_price = self
688 .queue_ids_by_price
689 .get_mut(&price_raw)
690 .is_some_and(|ids| {
691 ids.shift_remove(&client_order_id);
692 ids.is_empty()
693 });
694
695 if remove_price {
696 self.queue_ids_by_price.shift_remove(&price_raw);
697 }
698 }
699 }
700
701 fn take_queue_ids_at_price(&mut self, price_raw: PriceRaw) -> Vec<ClientOrderId> {
702 let mut ids = Self::take_cleared(&mut self.queue_id_scratch);
703 if let Some(tracked_ids) = self.queue_ids_by_price.get(&price_raw) {
704 ids.extend(tracked_ids.iter().copied());
705 }
706
707 ids
708 }
709
710 fn decrement_queue_on_trade(
711 &mut self,
712 price_raw: PriceRaw,
713 trade_size_raw: QuantityRaw,
714 aggressor_side: AggressorSide,
715 ) {
716 if !self.config.queue_position {
717 return;
718 }
719
720 self.queue_excess.clear();
721
722 let keys = self.take_queue_ids_at_price(price_raw);
723 let mut entries = Self::take_cleared(&mut self.queue_entry_scratch);
724 let mut stale = Self::take_cleared(&mut self.queue_stale_scratch);
725
726 for client_order_id in keys.iter().copied() {
727 let (order_price_raw, ahead_raw) =
728 match self.queue_ahead_total.get(&client_order_id).copied() {
729 Some(v) => v,
730 None => continue,
731 };
732
733 let cache = self.cache.borrow();
734
735 let order_info = cache.order(&client_order_id).and_then(|order| {
736 if order.is_closed() {
737 return None;
738 }
739
740 let has_pending_updates = self
741 .orders
742 .pending_updates
743 .borrow()
744 .contains_key(&client_order_id);
745 let has_pending_fills = self
746 .orders
747 .cached_filled_qty
748 .borrow()
749 .get(&client_order_id)
750 .is_some_and(|filled_qty| *filled_qty != order.filled_qty());
751 let snapshot;
752 let order = if has_pending_updates || has_pending_fills {
753 snapshot = self.order_snapshot(client_order_id)?;
754 &snapshot
755 } else {
756 &order
757 };
758
759 Some((order.order_side(), order.leaves_qty().raw()))
760 });
761
762 drop(cache);
763
764 let Some((order_side, leaves_raw)) = order_info else {
765 stale.push(client_order_id);
766 continue;
767 };
768
769 if order_price_raw != price_raw || ahead_raw == 0 {
770 continue;
771 }
772
773 let should_decrement = matches!(aggressor_side, AggressorSide::NoAggressor)
774 || (aggressor_side == AggressorSide::Buy && order_side == OrderSide::Sell)
775 || (aggressor_side == AggressorSide::Sell && order_side == OrderSide::Buy);
776
777 if should_decrement {
778 entries.push((client_order_id, ahead_raw, leaves_raw));
779 }
780 }
781
782 for id in stale.drain(..) {
783 self.remove_queue_position(id);
784 }
785
786 entries.sort_by_key(|&(_, ahead, _)| ahead);
788
789 let mut remaining = trade_size_raw;
790 let mut prev_position: QuantityRaw = 0;
791
792 for (client_order_id, ahead_raw, leaves_raw) in &entries {
793 if remaining == 0 {
794 let new_ahead = ahead_raw.saturating_sub(trade_size_raw);
795 self.reduce_queue_ahead(*client_order_id, price_raw, *ahead_raw, new_ahead);
796 if new_ahead == 0 {
797 self.queue_excess.insert(*client_order_id, 0);
799 }
800
801 continue;
802 }
803
804 let gap = ahead_raw.saturating_sub(prev_position);
806 let queue_consumed = remaining.min(gap);
807 remaining -= queue_consumed;
808
809 if remaining == 0 && queue_consumed < gap {
810 let new_ahead = ahead_raw.saturating_sub(trade_size_raw);
811 self.reduce_queue_ahead(*client_order_id, price_raw, *ahead_raw, new_ahead);
812 continue;
813 }
814
815 self.reduce_queue_ahead(*client_order_id, price_raw, *ahead_raw, 0);
816 let excess = remaining.min(*leaves_raw);
817 self.queue_excess.insert(*client_order_id, excess);
818 remaining -= excess;
819 prev_position = ahead_raw + excess;
820 }
821
822 self.queue_id_scratch = keys;
823 self.queue_entry_scratch = entries;
824 self.queue_stale_scratch = stale;
825 }
826
827 fn reduce_queue_ahead(
831 &mut self,
832 client_order_id: ClientOrderId,
833 price_raw: PriceRaw,
834 ahead_raw: QuantityRaw,
835 new_ahead_raw: QuantityRaw,
836 ) {
837 self.queue_ahead_total
838 .insert(client_order_id, (price_raw, new_ahead_raw));
839 self.consume_queue_ahead_orders(client_order_id, ahead_raw.saturating_sub(new_ahead_raw));
840 }
841
842 fn consume_queue_ahead_orders(
844 &mut self,
845 client_order_id: ClientOrderId,
846 mut amount_raw: QuantityRaw,
847 ) {
848 let Some(orders_ahead) = self.queue_ahead_orders.get_mut(&client_order_id) else {
849 return;
850 };
851
852 while amount_raw > 0 {
853 let Some((&book_order_id, &size_raw)) = orders_ahead.get_index(0) else {
854 break;
855 };
856
857 if size_raw <= amount_raw {
858 orders_ahead.shift_remove(&book_order_id);
859 amount_raw -= size_raw;
860 } else {
861 orders_ahead.insert(book_order_id, size_raw - amount_raw);
862 amount_raw = 0;
863 }
864 }
865 }
866
867 fn determine_trade_fill_qty(&self, order: &OrderAny) -> Option<QuantityRaw> {
868 if !self.config.queue_position {
869 return Some(order.leaves_qty().raw());
870 }
871
872 let client_order_id = order.client_order_id();
873
874 if self.queue_pending.contains_key(&client_order_id) {
876 return None;
877 }
878
879 if let Some(&(tracked_price_raw, ahead_raw)) = self.queue_ahead_total.get(&client_order_id)
880 && let Some(order_price) = order.price()
881 && order_price.raw() == tracked_price_raw
882 && ahead_raw > 0
883 {
884 return None;
885 }
886
887 let leaves_raw = order.leaves_qty().raw();
888 if leaves_raw == 0 {
889 return None;
890 }
891
892 let mut available_raw = leaves_raw;
893
894 if let Some(trade_size) = self.last_trade_size {
896 let remaining = trade_size.raw().saturating_sub(self.trade_consumption);
897 available_raw = available_raw.min(remaining);
898
899 if let Some(&excess_raw) = self.queue_excess.get(&client_order_id) {
900 if excess_raw == 0 {
901 return None;
902 }
903
904 available_raw = available_raw.min(excess_raw);
905 }
906 }
907
908 if available_raw == 0 {
909 return None;
910 }
911
912 Some(available_raw)
913 }
914
915 fn rebase_queue_positions(&mut self) {
922 if !self.config.queue_position {
923 return;
924 }
925
926 let tracked: Vec<_> = self
927 .queue_ahead_total
928 .iter()
929 .map(|(&client_order_id, &(price_raw, ahead_raw))| {
930 (client_order_id, price_raw, ahead_raw)
931 })
932 .collect();
933
934 let mut stale = Self::take_cleared(&mut self.queue_stale_scratch);
935 let size_precision = self.instrument.size_precision();
936 let price_precision = self.instrument.price_precision();
937
938 for (client_order_id, price_raw, ahead_raw) in tracked {
939 let order_side = self
940 .cache
941 .borrow()
942 .order(&client_order_id)
943 .and_then(|order| {
944 if order.is_closed() {
945 None
946 } else {
947 Some(order.order_side())
948 }
949 });
950
951 let Some(order_side) = order_side else {
952 stale.push(client_order_id);
953 continue;
954 };
955
956 let price = Price::from_raw(price_raw, price_precision);
957 let visible_raw = self
958 .book
959 .get_quantity_at_level(price, OrderCore::opposite_side(order_side), size_precision)
960 .raw();
961 let rebased_raw = ahead_raw.min(visible_raw);
962
963 if self.book_type == BookType::L3_MBO {
964 let previous_orders = self
965 .queue_ahead_orders
966 .get(&client_order_id)
967 .cloned()
968 .unwrap_or_default();
969 let mut orders_ahead = IndexMap::new();
970 let mut total_raw = 0;
971
972 for book_order in self
973 .book
974 .get_orders_at_level(price, OrderCore::opposite_side(order_side))
975 {
976 if !previous_orders.contains_key(&book_order.order_id) {
977 continue;
978 }
979
980 let previous_size_raw = previous_orders[&book_order.order_id];
981 let size_raw = previous_size_raw.min(book_order.size.raw());
982 orders_ahead.insert(book_order.order_id, size_raw);
983 total_raw += size_raw;
984 }
985
986 self.queue_ahead_orders
987 .insert(client_order_id, orders_ahead);
988 self.queue_ahead_total
989 .insert(client_order_id, (price_raw, total_raw));
990 } else {
991 self.queue_ahead_total
992 .insert(client_order_id, (price_raw, rebased_raw));
993 }
994 }
995
996 for client_order_id in stale.drain(..) {
997 self.remove_queue_position(client_order_id);
998 }
999
1000 self.queue_stale_scratch = stale;
1001 }
1002
1003 fn adjust_queue_for_delta(&mut self, delta: &OrderBookDelta) {
1004 if delta.action == BookAction::Delete {
1005 if self.is_order_granular_delta(delta.flags) {
1006 self.advance_l3_queue_on_delete(delta.order.order_id);
1007 } else {
1008 self.clear_queue_on_delete(delta.order.price.raw(), delta.order.side);
1009 }
1010 } else if delta.action == BookAction::Update {
1011 if self.is_order_granular_delta(delta.flags) {
1012 self.adjust_l3_queue_on_update(&delta.order);
1013 } else {
1014 self.cap_queue_ahead(
1015 delta.order.price.raw(),
1016 delta.order.size.raw(),
1017 delta.order.side,
1018 );
1019 }
1020 }
1021 }
1022
1023 fn clear_queue_on_delete(
1024 &mut self,
1025 deleted_price_raw: PriceRaw,
1026 deleted_side: Option<OrderSide>,
1027 ) {
1028 let keys = self.take_queue_ids_at_price(deleted_price_raw);
1029 for client_order_id in keys.iter().copied() {
1030 if let Some(&(order_price_raw, ahead_raw)) =
1031 self.queue_ahead_total.get(&client_order_id)
1032 && order_price_raw == deleted_price_raw
1033 {
1034 let matches_side = self
1035 .cache
1036 .borrow()
1037 .order(&client_order_id)
1038 .is_some_and(|o| Some(o.order_side()) == deleted_side);
1039
1040 if matches_side {
1041 self.reduce_queue_ahead(client_order_id, order_price_raw, ahead_raw, 0);
1042 }
1043 }
1044 }
1045
1046 self.queue_id_scratch = keys;
1047 }
1048
1049 fn is_order_granular_delta(&self, flags: u8) -> bool {
1052 self.book_type == BookType::L3_MBO
1053 && !RecordFlag::F_TOB.matches(flags)
1054 && !RecordFlag::F_MBP.matches(flags)
1055 }
1056
1057 fn advance_l3_queue_on_delete(&mut self, book_order_id: OrderId) {
1058 for (client_order_id, orders_ahead) in &mut self.queue_ahead_orders {
1059 let Some(size_raw) = orders_ahead.shift_remove(&book_order_id) else {
1060 continue;
1061 };
1062
1063 if let Some((_, ahead_raw)) = self.queue_ahead_total.get_mut(client_order_id) {
1064 *ahead_raw = ahead_raw.saturating_sub(size_raw);
1065 }
1066 }
1067 }
1068
1069 fn adjust_l3_queue_on_update(&mut self, book_order: &BookOrder) {
1075 for (client_order_id, orders_ahead) in &mut self.queue_ahead_orders {
1076 let Some(&tracked_size_raw) = orders_ahead.get(&book_order.order_id) else {
1077 continue;
1078 };
1079
1080 let Some((tracked_price_raw, ahead_raw)) =
1081 self.queue_ahead_total.get_mut(client_order_id)
1082 else {
1083 continue;
1084 };
1085
1086 if book_order.price.raw() != *tracked_price_raw {
1087 *ahead_raw = ahead_raw.saturating_sub(tracked_size_raw);
1088 orders_ahead.shift_remove(&book_order.order_id);
1089 } else if book_order.size.raw() < tracked_size_raw {
1090 *ahead_raw = ahead_raw.saturating_sub(tracked_size_raw - book_order.size.raw());
1092 orders_ahead.insert(book_order.order_id, book_order.size.raw());
1093 } else if book_order.size.raw() > tracked_size_raw {
1094 *ahead_raw = ahead_raw.saturating_add(book_order.size.raw() - tracked_size_raw);
1095 orders_ahead.insert(book_order.order_id, book_order.size.raw());
1096 }
1097 }
1098 }
1099
1100 fn cap_queue_ahead(
1101 &mut self,
1102 price_raw: PriceRaw,
1103 size_raw: QuantityRaw,
1104 order_side: Option<OrderSide>,
1105 ) {
1106 let keys = self.take_queue_ids_at_price(price_raw);
1107 let mut stale = Self::take_cleared(&mut self.queue_stale_scratch);
1108
1109 for client_order_id in keys.iter().copied() {
1110 let (order_price_raw, ahead_raw) =
1111 match self.queue_ahead_total.get(&client_order_id).copied() {
1112 Some(v) => v,
1113 None => continue,
1114 };
1115
1116 if order_price_raw != price_raw || ahead_raw <= size_raw {
1117 continue;
1118 }
1119
1120 let cache = self.cache.borrow();
1121
1122 let order_info = cache.order(&client_order_id).and_then(|order| {
1123 if order.is_closed() {
1124 None
1125 } else {
1126 Some(order.order_side())
1127 }
1128 });
1129
1130 drop(cache);
1131
1132 let Some(side) = order_info else {
1133 stale.push(client_order_id);
1134 continue;
1135 };
1136
1137 if Some(side) != order_side {
1138 continue;
1139 }
1140
1141 self.reduce_queue_ahead(client_order_id, order_price_raw, ahead_raw, size_raw);
1142 }
1143
1144 for id in stale.drain(..) {
1145 self.remove_queue_position(id);
1146 }
1147
1148 self.queue_id_scratch = keys;
1149 self.queue_stale_scratch = stale;
1150 }
1151
1152 fn seed_tob_baseline(&mut self) {
1153 let bid = self.book.best_bid_price();
1154 let ask = self.book.best_ask_price();
1155 self.prev_bid_price_raw = bid.map_or(0, |p| p.raw());
1156 self.prev_ask_price_raw = ask.map_or(0, |p| p.raw());
1157 self.tob_initialized = bid.is_some() || ask.is_some();
1158 }
1159
1160 fn decrement_l1_queue_on_quote(
1161 &mut self,
1162 bid_price_raw: PriceRaw,
1163 bid_size_raw: QuantityRaw,
1164 ask_price_raw: PriceRaw,
1165 ask_size_raw: QuantityRaw,
1166 ) {
1167 if !self.config.queue_position {
1168 return;
1169 }
1170
1171 if self.tob_initialized {
1173 if bid_price_raw < self.prev_bid_price_raw {
1175 self.adjust_l1_queue_on_price_move(bid_price_raw, bid_size_raw, OrderSide::Buy);
1176 }
1177
1178 if ask_price_raw > self.prev_ask_price_raw {
1180 self.adjust_l1_queue_on_price_move(ask_price_raw, ask_size_raw, OrderSide::Sell);
1181 }
1182 }
1183
1184 self.resolve_pending_l1_snapshots(bid_price_raw, bid_size_raw, ask_price_raw, ask_size_raw);
1186 self.cap_queue_ahead(bid_price_raw, bid_size_raw, Some(OrderSide::Buy));
1187 self.cap_queue_ahead(ask_price_raw, ask_size_raw, Some(OrderSide::Sell));
1188 }
1189
1190 fn adjust_l1_queue_on_price_move(
1191 &mut self,
1192 new_price_raw: PriceRaw,
1193 new_size_raw: QuantityRaw,
1194 order_side: OrderSide,
1195 ) {
1196 let mut keys = Self::take_cleared(&mut self.queue_id_scratch);
1197 keys.extend(self.queue_ahead_total.keys().copied());
1198 let mut stale = Self::take_cleared(&mut self.queue_stale_scratch);
1199
1200 for client_order_id in keys.iter().copied() {
1201 let Some(&(order_price_raw, ahead_raw)) = self.queue_ahead_total.get(&client_order_id)
1202 else {
1203 continue;
1204 };
1205
1206 let cache = self.cache.borrow();
1207
1208 let order_info = cache.order(&client_order_id).and_then(|order| {
1209 if order.is_closed() {
1210 None
1211 } else {
1212 Some(order.order_side())
1213 }
1214 });
1215
1216 drop(cache);
1217
1218 let Some(side) = order_info else {
1219 stale.push(client_order_id);
1220 continue;
1221 };
1222
1223 if side != order_side {
1224 continue;
1225 }
1226
1227 let crossed = match order_side {
1230 OrderSide::Buy => order_price_raw > new_price_raw,
1231 _ => order_price_raw < new_price_raw,
1232 };
1233
1234 if crossed {
1235 self.queue_ahead_total
1236 .insert(client_order_id, (order_price_raw, 0));
1237 } else if order_price_raw == new_price_raw && ahead_raw > new_size_raw {
1238 self.queue_ahead_total
1239 .insert(client_order_id, (order_price_raw, new_size_raw));
1240 }
1241 }
1242
1243 for id in stale.drain(..) {
1244 self.remove_queue_position(id);
1245 }
1246
1247 let mut pending = Self::take_cleared(&mut self.queue_pending_scratch);
1248 pending.extend(
1249 self.queue_pending
1250 .iter()
1251 .map(|(&client_order_id, &price_raw)| (client_order_id, price_raw)),
1252 );
1253
1254 for (client_order_id, order_price_raw) in pending.iter().copied() {
1255 let cache = self.cache.borrow();
1256
1257 let order_info = cache.order(&client_order_id).and_then(|order| {
1258 if order.is_closed() {
1259 None
1260 } else {
1261 Some(order.order_side())
1262 }
1263 });
1264
1265 drop(cache);
1266
1267 let Some(side) = order_info else {
1268 stale.push(client_order_id);
1269 continue;
1270 };
1271
1272 if side != order_side {
1273 continue;
1274 }
1275
1276 let crossed = match order_side {
1277 OrderSide::Buy => order_price_raw > new_price_raw,
1278 _ => order_price_raw < new_price_raw,
1279 };
1280
1281 if crossed {
1282 self.queue_pending.shift_remove(&client_order_id);
1283 self.queue_ahead_total
1284 .insert(client_order_id, (order_price_raw, 0));
1285 } else if order_price_raw == new_price_raw {
1286 self.queue_pending.shift_remove(&client_order_id);
1287 self.queue_ahead_total
1288 .insert(client_order_id, (order_price_raw, new_size_raw));
1289 }
1290 }
1291
1292 for id in stale.drain(..) {
1293 self.remove_queue_position(id);
1294 }
1295
1296 self.queue_id_scratch = keys;
1297 self.queue_pending_scratch = pending;
1298 self.queue_stale_scratch = stale;
1299 }
1300
1301 fn resolve_pending_l1_snapshots(
1302 &mut self,
1303 bid_price_raw: PriceRaw,
1304 bid_size_raw: QuantityRaw,
1305 ask_price_raw: PriceRaw,
1306 ask_size_raw: QuantityRaw,
1307 ) {
1308 let mut keys = self.take_queue_ids_at_price(bid_price_raw);
1309 if ask_price_raw != bid_price_raw
1310 && let Some(ask_ids) = self.queue_ids_by_price.get(&ask_price_raw)
1311 {
1312 keys.extend(ask_ids.iter().copied());
1313 }
1314
1315 let mut stale = Self::take_cleared(&mut self.queue_stale_scratch);
1316
1317 for client_order_id in keys.iter().copied() {
1318 let Some(&order_price_raw) = self.queue_pending.get(&client_order_id) else {
1319 continue;
1320 };
1321
1322 let cache = self.cache.borrow();
1323
1324 let order_info = cache.order(&client_order_id).and_then(|order| {
1325 if order.is_closed() {
1326 None
1327 } else {
1328 Some(order.order_side())
1329 }
1330 });
1331
1332 drop(cache);
1333
1334 let Some(side) = order_info else {
1335 stale.push(client_order_id);
1336 continue;
1337 };
1338
1339 let matched_size = match side {
1341 OrderSide::Buy if order_price_raw == bid_price_raw => Some(bid_size_raw),
1342 OrderSide::Sell if order_price_raw == ask_price_raw => Some(ask_size_raw),
1343 _ => None,
1344 };
1345
1346 if let Some(size) = matched_size {
1347 self.queue_pending.shift_remove(&client_order_id);
1348 self.queue_ahead_total
1349 .insert(client_order_id, (order_price_raw, size));
1350 }
1351 }
1352
1353 for id in stale.drain(..) {
1354 self.remove_queue_position(id);
1355 }
1356
1357 self.queue_id_scratch = keys;
1358 self.queue_stale_scratch = stale;
1359 }
1360
1361 fn resolve_pending_on_trade(&mut self, trade_price_raw: PriceRaw) {
1362 let mut pending = Self::take_cleared(&mut self.queue_pending_scratch);
1363 pending.extend(
1364 self.queue_pending
1365 .iter()
1366 .map(|(&client_order_id, &price_raw)| (client_order_id, price_raw)),
1367 );
1368 let mut stale = Self::take_cleared(&mut self.queue_stale_scratch);
1369
1370 for (client_order_id, order_price_raw) in pending.iter().copied() {
1371 let cache = self.cache.borrow();
1372
1373 let order_side = cache.order(&client_order_id).and_then(|order| {
1374 if order.is_closed() {
1375 None
1376 } else {
1377 Some(order.order_side())
1378 }
1379 });
1380
1381 drop(cache);
1382
1383 let Some(side) = order_side else {
1384 stale.push(client_order_id);
1385 continue;
1386 };
1387
1388 let crossed = match side {
1390 OrderSide::Buy => trade_price_raw < order_price_raw,
1391 OrderSide::Sell => trade_price_raw > order_price_raw,
1392 };
1393
1394 if crossed {
1395 self.queue_pending.shift_remove(&client_order_id);
1396 self.queue_ahead_total
1397 .insert(client_order_id, (order_price_raw, 0));
1398 }
1399 }
1400
1401 for id in stale.drain(..) {
1402 self.remove_queue_position(id);
1403 }
1404
1405 self.queue_pending_scratch = pending;
1406 self.queue_stale_scratch = stale;
1407 }
1408
1409 fn take_cleared<T>(buf: &mut Vec<T>) -> Vec<T> {
1410 let mut items = mem::take(buf);
1411 items.clear();
1412 items
1413 }
1414
1415 #[must_use]
1416 pub fn best_bid_price(&self) -> Option<Price> {
1418 self.book.best_bid_price()
1419 }
1420
1421 #[must_use]
1422 pub fn best_ask_price(&self) -> Option<Price> {
1424 self.book.best_ask_price()
1425 }
1426
1427 #[must_use]
1428 pub const fn get_book(&self) -> &OrderBook {
1430 &self.book
1431 }
1432
1433 #[must_use]
1434 pub fn get_open_bid_orders(&self) -> Vec<RestingOrder> {
1436 self.core.get_orders_bid()
1437 }
1438
1439 #[must_use]
1440 pub fn get_open_ask_orders(&self) -> Vec<RestingOrder> {
1442 self.core.get_orders_ask()
1443 }
1444
1445 #[must_use]
1446 pub fn get_open_orders(&self) -> Vec<RestingOrder> {
1448 self.core.get_orders()
1449 }
1450
1451 #[must_use]
1452 pub fn order_exists(&self, client_order_id: ClientOrderId) -> bool {
1454 self.core.order_exists(client_order_id)
1455 }
1456
1457 #[must_use]
1458 pub fn cached_filled_qty_len(&self) -> usize {
1460 let cache = self.cache.borrow();
1461 self.orders
1462 .cached_filled_qty
1463 .borrow()
1464 .keys()
1465 .filter(|id| {
1466 cache
1467 .order(id)
1468 .is_some_and(|order| order.instrument_id() == self.instrument.id())
1469 })
1470 .count()
1471 }
1472
1473 #[must_use]
1474 pub const fn get_core(&self) -> &OrderMatchingCore {
1475 &self.core
1476 }
1477
1478 pub fn set_fill_at_market(&mut self, value: bool) {
1479 self.fill_at_market = value;
1480 }
1481
1482 pub fn update_instrument(&mut self, instrument: InstrumentAny) -> anyhow::Result<()> {
1488 if instrument.id() != self.instrument.id() {
1489 anyhow::bail!(
1490 "Cannot update instrument {} with {}",
1491 self.instrument.id(),
1492 instrument.id()
1493 );
1494 }
1495
1496 let changed = instrument.price_increment() != self.instrument.price_increment()
1497 || instrument.price_precision() != self.instrument.price_precision()
1498 || instrument.size_precision() != self.instrument.size_precision();
1499
1500 if changed {
1501 self.core
1502 .update_price_increment(instrument.price_increment());
1503 self.book.reset();
1504 self.l1_trade_size = None;
1505 self.trade_consumption = 0;
1506 self.bid_consumption.clear();
1507 self.ask_consumption.clear();
1508 self.queue_pending.clear();
1509 self.queue_ahead_orders.clear();
1510 self.queue_ahead_total.clear();
1511 self.queue_ids_by_price.clear();
1512 self.queue_excess.clear();
1513 self.prev_bid_price_raw = 0;
1514 self.prev_ask_price_raw = 0;
1515 self.tob_initialized = false;
1516 self.last_quote_bid = None;
1517 self.last_quote_ask = None;
1518 self.precision_mismatch_streak = 0;
1519 self.target_bid = None;
1520 self.target_ask = None;
1521 self.target_last = None;
1522 self.last_bar_bid = None;
1523 self.last_bar_ask = None;
1524 self.core.bid = None;
1525 self.core.ask = None;
1526 self.core.last = None;
1527 log::info!(
1528 "Updated instrument {} (price_precision={} size_precision={})",
1529 instrument.id(),
1530 instrument.price_precision(),
1531 instrument.size_precision()
1532 );
1533 }
1534
1535 self.instrument = instrument;
1536
1537 if changed {
1538 self.drop_incompatible_core_orders();
1539 }
1540
1541 Ok(())
1542 }
1543
1544 fn check_price_precision(&self, actual: u8, field: &str) -> anyhow::Result<()> {
1545 let expected = self.instrument.price_precision();
1546 if actual != expected {
1547 anyhow::bail!(
1548 "Invalid {field} precision {actual}, expected {expected} for {}",
1549 self.instrument.id()
1550 );
1551 }
1552
1553 Ok(())
1554 }
1555
1556 fn check_size_precision(&self, actual: u8, field: &str) -> anyhow::Result<()> {
1557 let expected = self.instrument.size_precision();
1558 if actual != expected {
1559 anyhow::bail!(
1560 "Invalid {field} precision {actual}, expected {expected} for {}",
1561 self.instrument.id()
1562 );
1563 }
1564
1565 Ok(())
1566 }
1567
1568 fn log_precision_mismatch(
1569 &mut self,
1570 data_type: &str,
1571 instrument_id: InstrumentId,
1572 err: &anyhow::Error,
1573 ) {
1574 self.precision_mismatch_streak = self.precision_mismatch_streak.saturating_add(1);
1575 let streak = self.precision_mismatch_streak;
1576
1577 if streak <= 3 || streak.is_multiple_of(100) {
1578 log::warn!(
1579 "Skipping {data_type} for {instrument_id}: {err} \
1580 (consecutive_precision_mismatches={streak})"
1581 );
1582 }
1583
1584 if streak == 20 {
1585 log::error!(
1586 "Precision mismatches reached {streak} consecutive events for \
1587 {instrument_id}; check instrument update flow and upstream market data"
1588 );
1589 }
1590 }
1591
1592 fn drop_incompatible_core_orders(&mut self) {
1593 let client_order_ids: Vec<ClientOrderId> = self
1594 .core
1595 .iter_orders()
1596 .filter(|order| {
1597 !self.resting_order_matches_current_instrument(order)
1598 || !self.cached_order_matches_current_instrument(order.client_order_id)
1599 })
1600 .map(|order| order.client_order_id)
1601 .collect();
1602
1603 for client_order_id in client_order_ids {
1604 let order = self
1605 .cache
1606 .borrow()
1607 .order(&client_order_id)
1608 .map(|o| o.clone());
1609
1610 if let Some(order) = order
1611 && (order.is_inflight() || order.is_open())
1612 {
1613 log::warn!(
1614 "Canceling order {client_order_id} after instrument update: \
1615 price, trigger price, or quantity is not compatible with {}",
1616 self.instrument.id()
1617 );
1618 self.cancel_order(&order, None);
1619 } else {
1620 self.delete_core_order(client_order_id);
1621 self.orders
1622 .cached_filled_qty
1623 .borrow_mut()
1624 .swap_remove(&client_order_id);
1625 }
1626 }
1627 }
1628
1629 fn cached_order_matches_current_instrument(&self, client_order_id: ClientOrderId) -> bool {
1630 self.cache
1631 .borrow()
1632 .order(&client_order_id)
1633 .is_none_or(|order| {
1634 Self::quantity_matches_precision(order.quantity(), self.instrument.size_precision())
1635 })
1636 }
1637
1638 fn resting_order_matches_current_instrument(&self, order: &RestingOrder) -> bool {
1639 order
1640 .limit_price
1641 .is_none_or(|price| self.price_matches_current_instrument(price))
1642 && order
1643 .trigger_price
1644 .is_none_or(|price| self.price_matches_current_instrument(price))
1645 }
1646
1647 fn price_matches_current_instrument(&self, price: Price) -> bool {
1648 Self::price_matches_precision(price, self.instrument.price_precision())
1649 && Self::price_matches_tick(price, self.instrument.price_increment())
1650 }
1651
1652 fn price_matches_precision(price: Price, precision: u8) -> bool {
1653 let precision_diff = FIXED_PRECISION.saturating_sub(precision);
1654 let scale = PriceRaw::pow(10, u32::from(precision_diff));
1655 price.raw() % scale == 0
1656 }
1657
1658 fn price_matches_tick(price: Price, increment: Price) -> bool {
1659 let increment_raw = increment.raw().abs();
1660 increment_raw == 0 || price.raw() % increment_raw == 0
1661 }
1662
1663 fn quantity_matches_precision(quantity: Quantity, precision: u8) -> bool {
1664 let precision_diff = FIXED_PRECISION.saturating_sub(precision);
1665 let scale = QuantityRaw::pow(10, u32::from(precision_diff));
1666 quantity.raw().is_multiple_of(scale)
1667 }
1668
1669 fn normalize_price_for_current_instrument(&self, price: Price) -> Option<Price> {
1670 if !self.price_matches_current_instrument(price) {
1671 return None;
1672 }
1673
1674 Some(Price::from_raw(
1675 price.raw(),
1676 self.instrument.price_precision(),
1677 ))
1678 }
1679
1680 fn normalize_quantity_for_current_instrument(&self, quantity: Quantity) -> Option<Quantity> {
1681 let precision = self.instrument.size_precision();
1682 if !Self::quantity_matches_precision(quantity, precision) {
1683 return None;
1684 }
1685
1686 Some(Quantity::from_raw(quantity.raw(), precision))
1687 }
1688
1689 pub fn process_order_book_delta(&mut self, delta: &OrderBookDelta) -> anyhow::Result<()> {
1698 log::debug!("Processing {delta}");
1699
1700 if matches!(delta.action, BookAction::Add | BookAction::Update) {
1702 self.check_price_precision(delta.order.price.precision, "delta order price")?;
1703 self.check_size_precision(delta.order.size.precision, "delta order size")?;
1704 }
1705
1706 if self.book_type == BookType::L1_MBP {
1708 self.iterate(delta.ts_init, AggressorSide::NoAggressor);
1709 return Ok(());
1710 }
1711
1712 self.book.apply_delta(delta)?;
1713
1714 let is_snapshot = RecordFlag::F_SNAPSHOT.matches(delta.flags);
1715 let is_last = RecordFlag::F_LAST.matches(delta.flags);
1716 let is_clear = delta.action == BookAction::Clear;
1717 let snapshot_complete = is_last && (is_snapshot || self.queue_snapshot_in_progress);
1718 if self.config.queue_position {
1719 if is_snapshot && !is_last {
1720 self.queue_snapshot_in_progress = true;
1724 }
1725
1726 if snapshot_complete {
1727 self.queue_snapshot_in_progress = false;
1728 self.rebase_queue_positions();
1729 } else if is_clear && !is_snapshot {
1730 self.rebase_queue_positions();
1731 } else if !self.queue_snapshot_in_progress {
1732 self.adjust_queue_for_delta(delta);
1733 }
1734 }
1735
1736 if self.config.queue_position && (snapshot_complete || (is_clear && !is_snapshot)) {
1737 self.seed_tob_baseline();
1738 }
1739
1740 self.iterate(delta.ts_init, AggressorSide::NoAggressor);
1741 Ok(())
1742 }
1743
1744 pub fn process_order_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
1753 log::debug!("Processing {deltas}");
1754
1755 for delta in &deltas.deltas {
1757 if matches!(delta.action, BookAction::Add | BookAction::Update) {
1758 self.check_price_precision(delta.order.price.precision, "delta order price")?;
1759 self.check_size_precision(delta.order.size.precision, "delta order size")?;
1760 }
1761 }
1762
1763 if self.book_type == BookType::L1_MBP {
1765 self.iterate(deltas.ts_init, AggressorSide::NoAggressor);
1766 return Ok(());
1767 }
1768
1769 self.book.apply_deltas(deltas)?;
1770
1771 let mut has_snapshot_or_clear = false;
1772
1773 if self.config.queue_position {
1774 for delta in &deltas.deltas {
1775 if RecordFlag::F_SNAPSHOT.matches(delta.flags) || delta.action == BookAction::Clear
1776 {
1777 has_snapshot_or_clear = true;
1778 break;
1779 }
1780
1781 self.adjust_queue_for_delta(delta);
1782 }
1783 }
1784
1785 if self.config.queue_position && has_snapshot_or_clear {
1786 self.queue_snapshot_in_progress = false;
1787 self.rebase_queue_positions();
1788 self.seed_tob_baseline();
1789 }
1790
1791 self.iterate(deltas.ts_init, AggressorSide::NoAggressor);
1792 Ok(())
1793 }
1794
1795 pub fn process_order_book_depth(&mut self, depth: &OrderBookDepth) -> anyhow::Result<()> {
1805 log::debug!("Processing OrderBookDepth for {}", depth.instrument_id);
1806
1807 for order in &depth.bids {
1809 if order.side.is_none() || !order.size.is_positive() {
1810 continue;
1811 }
1812
1813 self.check_price_precision(order.price.precision, "bid price")?;
1814 self.check_size_precision(order.size.precision, "bid size")?;
1815 }
1816
1817 for order in &depth.asks {
1818 if order.side.is_none() || !order.size.is_positive() {
1819 continue;
1820 }
1821
1822 self.check_price_precision(order.price.precision, "ask price")?;
1823 self.check_size_precision(order.size.precision, "ask size")?;
1824 }
1825
1826 let top_bid = Self::first_valid_depth_order(&depth.bids, OrderSide::Buy);
1827 let top_ask = Self::first_valid_depth_order(&depth.asks, OrderSide::Sell);
1828
1829 if self.book_type == BookType::L1_MBP {
1832 let quote = QuoteTick::new(
1833 depth.instrument_id,
1834 Self::depth_quote_price(top_bid, self.instrument.price_precision()),
1835 Self::depth_quote_price(top_ask, self.instrument.price_precision()),
1836 Self::depth_quote_size(top_bid, self.instrument.size_precision()),
1837 Self::depth_quote_size(top_ask, self.instrument.size_precision()),
1838 depth.ts_event,
1839 depth.ts_init,
1840 );
1841 self.book.update_quote_tick("e)?;
1842 self.reset_trade_liquidity(quote.ts_event);
1843 self.last_quote_bid = top_bid.map(|order| order.price);
1844 self.last_quote_ask = top_ask.map(|order| order.price);
1845 } else {
1846 self.book.apply_depth(depth)?;
1847 }
1848
1849 if self.config.queue_position {
1851 self.rebase_queue_positions();
1852 let bid_price_raw = top_bid.map_or(0, |order| order.price.raw());
1853 let bid_size_raw = top_bid.map_or(0, |order| order.size.raw());
1854 let ask_price_raw = top_ask.map_or(0, |order| order.price.raw());
1855 let ask_size_raw = top_ask.map_or(0, |order| order.size.raw());
1856
1857 self.decrement_l1_queue_on_quote(
1858 bid_price_raw,
1859 bid_size_raw,
1860 ask_price_raw,
1861 ask_size_raw,
1862 );
1863
1864 self.prev_bid_price_raw = bid_price_raw;
1865 self.prev_ask_price_raw = ask_price_raw;
1866 self.tob_initialized = true;
1867 }
1868
1869 self.iterate(depth.ts_init, AggressorSide::NoAggressor);
1870 Ok(())
1871 }
1872
1873 fn first_valid_depth_order(orders: &[BookOrder], side: OrderSide) -> Option<BookOrder> {
1874 orders
1875 .iter()
1876 .copied()
1877 .find(|order| order.side == Some(side) && order.size.is_positive())
1878 }
1879
1880 fn depth_quote_price(order: Option<BookOrder>, price_precision: u8) -> Price {
1881 order.map_or_else(|| Price::zero(price_precision), |order| order.price)
1882 }
1883
1884 fn depth_quote_size(order: Option<BookOrder>, size_precision: u8) -> Quantity {
1885 order.map_or_else(|| Quantity::zero(size_precision), |order| order.size)
1886 }
1887
1888 pub fn process_mark_price(&mut self, mark: &MarkPriceUpdate) {
1892 self.core.mark = Some(mark.value);
1893 self.iterate_with_mode(
1894 mark.ts_init,
1895 AggressorSide::NoAggressor,
1896 OrderMatchMode::StopTriggers(TriggerType::MarkPrice),
1897 );
1898 }
1899
1900 pub fn process_quote_tick(&mut self, quote: &QuoteTick) {
1902 log::debug!("Processing {quote}");
1903
1904 if let Err(e) = self.check_price_precision(quote.bid_price.precision, "bid_price") {
1905 self.log_precision_mismatch("quote tick", quote.instrument_id, &e);
1906 return;
1907 }
1908
1909 if let Err(e) = self.check_price_precision(quote.ask_price.precision, "ask_price") {
1910 self.log_precision_mismatch("quote tick", quote.instrument_id, &e);
1911 return;
1912 }
1913
1914 if let Err(e) = self.check_size_precision(quote.bid_size.precision, "bid_size") {
1915 self.log_precision_mismatch("quote tick", quote.instrument_id, &e);
1916 return;
1917 }
1918
1919 if let Err(e) = self.check_size_precision(quote.ask_size.precision, "ask_size") {
1920 self.log_precision_mismatch("quote tick", quote.instrument_id, &e);
1921 return;
1922 }
1923
1924 self.precision_mismatch_streak = 0;
1925
1926 if self.book_type == BookType::L1_MBP {
1927 if quote.ts_event < self.book.ts_last {
1929 log::warn!(
1930 "Skipping stale quote: ts_event {} < book.ts_last {} for {}",
1931 quote.ts_event,
1932 self.book.ts_last,
1933 self.book.instrument_id,
1934 );
1935 self.iterate(quote.ts_init, AggressorSide::NoAggressor);
1936 return;
1937 }
1938
1939 if !self.update_quote_tick_or_skip(quote, "quote tick") {
1940 return;
1941 }
1942
1943 if self.config.queue_position {
1944 self.decrement_l1_queue_on_quote(
1945 quote.bid_price.raw(),
1946 quote.bid_size.raw(),
1947 quote.ask_price.raw(),
1948 quote.ask_size.raw(),
1949 );
1950 self.prev_bid_price_raw = quote.bid_price.raw();
1951 self.prev_ask_price_raw = quote.ask_price.raw();
1952 self.tob_initialized = true;
1953 }
1954
1955 self.last_quote_bid = Some(quote.bid_price);
1956 self.last_quote_ask = Some(quote.ask_price);
1957 }
1958
1959 self.iterate(quote.ts_init, AggressorSide::NoAggressor);
1960 }
1961
1962 pub fn process_bar(&mut self, bar: &Bar) {
1972 log::debug!("Processing {bar}");
1973
1974 debug_assert!(
1975 bar.high >= bar.open
1976 && bar.high >= bar.low
1977 && bar.high >= bar.close
1978 && bar.low <= bar.open
1979 && bar.low <= bar.close,
1980 "OHLC invariant violated for {bar}"
1981 );
1982
1983 if !self.config.bar_execution || self.book_type != BookType::L1_MBP {
1985 return;
1986 }
1987
1988 let bar_type = bar.bar_type;
1989
1990 if bar_type.aggregation_source() == AggregationSource::Internal {
1992 return;
1993 }
1994
1995 if let Err(e) = self.check_price_precision(bar.open.precision, "bar open") {
1996 self.log_precision_mismatch("bar", bar.instrument_id(), &e);
1997 return;
1998 }
1999
2000 if let Err(e) = self.check_price_precision(bar.high.precision, "bar high") {
2001 self.log_precision_mismatch("bar", bar.instrument_id(), &e);
2002 return;
2003 }
2004
2005 if let Err(e) = self.check_price_precision(bar.low.precision, "bar low") {
2006 self.log_precision_mismatch("bar", bar.instrument_id(), &e);
2007 return;
2008 }
2009
2010 if let Err(e) = self.check_price_precision(bar.close.precision, "bar close") {
2011 self.log_precision_mismatch("bar", bar.instrument_id(), &e);
2012 return;
2013 }
2014
2015 if let Err(e) = self.check_size_precision(bar.volume.precision, "bar volume") {
2016 self.log_precision_mismatch("bar", bar.instrument_id(), &e);
2017 return;
2018 }
2019
2020 self.precision_mismatch_streak = 0;
2021
2022 let price_type = bar_type.spec().price_type;
2023 if price_type == PriceType::Mark {
2024 log::warn!(
2025 "Cannot process bar for {} with `PriceType::Mark`, mark price bars are not supported for bar execution",
2026 bar.instrument_id(),
2027 );
2028 return;
2029 }
2030
2031 let execution_bar_type =
2032 if let Some(execution_bar_type) = self.execution_bar_types.get(&bar.instrument_id()) {
2033 execution_bar_type.to_owned()
2034 } else {
2035 self.execution_bar_types
2036 .insert(bar.instrument_id(), bar_type);
2037 self.execution_bar_deltas
2038 .insert(bar_type, bar_type.spec().timedelta());
2039 bar_type
2040 };
2041
2042 if execution_bar_type != bar_type {
2043 let bar_type_timedelta = *self
2044 .execution_bar_deltas
2045 .entry(bar_type)
2046 .or_insert_with(|| bar_type.spec().timedelta());
2047
2048 if self.execution_bar_deltas.get(&execution_bar_type).unwrap() >= &bar_type_timedelta {
2049 self.execution_bar_types
2050 .insert(bar_type.instrument_id(), bar_type);
2051 } else {
2052 return;
2053 }
2054 }
2055
2056 match price_type {
2057 PriceType::Last | PriceType::Mid => self.process_trade_ticks_from_bar(bar),
2058 PriceType::Bid => {
2059 self.last_bar_bid = Some(bar.to_owned());
2060 self.process_quote_ticks_from_bar();
2061 }
2062 PriceType::Ask => {
2063 self.last_bar_ask = Some(bar.to_owned());
2064 self.process_quote_ticks_from_bar();
2065 }
2066 PriceType::Mark => {
2067 unreachable!("PriceType::Mark bars return before execution bar state updates")
2068 }
2069 }
2070 }
2071
2072 fn process_trade_ticks_from_bar(&mut self, bar: &Bar) {
2073 self.collapse_bar_trades = self.has_nothing_to_match(bar.ts_init);
2076
2077 let sizes = BarTickSizes::from_volume(bar.volume, self.instrument.size_increment());
2078
2079 let aggressor_side = if self.core.last.is_none_or(|last| bar.open > last) {
2080 AggressorSide::Buy
2081 } else {
2082 AggressorSide::Sell
2083 };
2084
2085 if sizes.open.is_positive() || self.core.last.is_none_or(|last| bar.open != last) {
2087 self.fill_at_market = true;
2088
2089 if !self.process_bar_trade_tick(
2090 bar,
2091 bar.open,
2092 sizes.open,
2093 aggressor_side,
2094 "bar open trade tick",
2095 ) {
2096 return;
2097 }
2098 }
2099
2100 let high_first = self.bar_high_first(bar);
2103 if high_first {
2104 self.process_bar_high(bar, sizes.high);
2105 self.process_bar_low(bar, sizes.low);
2106 } else {
2107 self.process_bar_low(bar, sizes.low);
2108 self.process_bar_high(bar, sizes.high);
2109 }
2110
2111 if sizes.close.is_positive() || self.core.last.is_some_and(|last| bar.close != last) {
2112 let aggressor_side = if bar.close > self.core.last.unwrap() {
2113 AggressorSide::Buy
2114 } else {
2115 AggressorSide::Sell
2116 };
2117
2118 if !self.process_bar_trade_tick(
2119 bar,
2120 bar.close,
2121 sizes.close,
2122 aggressor_side,
2123 "bar close trade tick",
2124 ) {
2125 return;
2126 }
2127 }
2128
2129 self.collapse_bar_trades = false;
2130
2131 if let Some((price, size, aggressor_side)) = self.last_bar_trade.take() {
2132 let last = self.core.last;
2134 self.process_bar_trade_tick(bar, price, size, aggressor_side, "bar trade tick");
2135 self.core.last = last;
2136 }
2137
2138 self.fill_at_market = true;
2139 }
2140
2141 fn process_bar_high(&mut self, bar: &Bar, size: Quantity) {
2142 if size.is_positive() || self.core.last.is_some_and(|last| bar.high > last) {
2143 self.process_bar_trade_tick(
2144 bar,
2145 bar.high,
2146 size,
2147 AggressorSide::Buy,
2148 "bar high trade tick",
2149 );
2150 }
2151 }
2152
2153 fn process_bar_low(&mut self, bar: &Bar, size: Quantity) {
2154 if size.is_positive() || self.core.last.is_some_and(|last| bar.low < last) {
2155 self.process_bar_trade_tick(
2156 bar,
2157 bar.low,
2158 size,
2159 AggressorSide::Sell,
2160 "bar low trade tick",
2161 );
2162 }
2163 }
2164
2165 fn process_bar_trade_tick(
2166 &mut self,
2167 bar: &Bar,
2168 price: Price,
2169 size: Quantity,
2170 aggressor_side: AggressorSide,
2171 context: &str,
2172 ) -> bool {
2173 if price != bar.open {
2175 self.fill_at_market = false;
2176 }
2177
2178 if size.is_zero() {
2179 self.core.set_last_raw(price);
2180 return true;
2181 }
2182
2183 if self.collapse_bar_trades {
2184 if self.last_bar_trade.is_some() {
2186 self.ids_generator.skip_trade_id();
2187 }
2188 self.last_bar_trade = Some((price, size, aggressor_side));
2189 self.core.set_last_raw(price);
2190 return true;
2191 }
2192
2193 let trade_tick = TradeTick::new(
2194 bar.instrument_id(),
2195 price,
2196 size,
2197 aggressor_side,
2198 self.ids_generator.generate_trade_id(bar.ts_init),
2199 bar.ts_init,
2200 bar.ts_init,
2201 );
2202
2203 if !self.update_trade_tick_or_skip(&trade_tick, context) {
2204 return false;
2205 }
2206
2207 self.core.set_last_raw(price);
2209 self.iterate(trade_tick.ts_init, AggressorSide::NoAggressor);
2210 true
2211 }
2212
2213 fn process_quote_ticks_from_bar(&mut self) {
2214 if self.last_bar_bid.is_none()
2216 || self.last_bar_ask.is_none()
2217 || self.last_bar_bid.unwrap().ts_init != self.last_bar_ask.unwrap().ts_init
2218 {
2219 return;
2220 }
2221
2222 let bid_bar = self.last_bar_bid.unwrap();
2223 let ask_bar = self.last_bar_ask.unwrap();
2224
2225 let size_increment = self.instrument.size_increment();
2226 let bid_sizes = BarTickSizes::from_volume(bid_bar.volume, size_increment);
2227 let ask_sizes = BarTickSizes::from_volume(ask_bar.volume, size_increment);
2228 let mut has_current_bid = false;
2229 let mut has_current_ask = false;
2230
2231 let mut quote_tick = QuoteTick::new(
2232 self.book.instrument_id,
2233 bid_bar.open,
2234 ask_bar.open,
2235 bid_sizes.open,
2236 ask_sizes.open,
2237 bid_bar.ts_init,
2238 bid_bar.ts_init,
2239 );
2240
2241 self.fill_at_market = true;
2243
2244 if !self.process_bar_quote_tick(
2245 "e_tick,
2246 "bar open quote tick",
2247 &mut has_current_bid,
2248 &mut has_current_ask,
2249 ) {
2250 return;
2251 }
2252
2253 let high_first = self.bar_high_first(&bid_bar);
2256
2257 let high_leg = (
2258 bid_bar.high,
2259 ask_bar.high,
2260 bid_sizes.high,
2261 ask_sizes.high,
2262 "bar high quote tick",
2263 );
2264 let low_leg = (
2265 bid_bar.low,
2266 ask_bar.low,
2267 bid_sizes.low,
2268 ask_sizes.low,
2269 "bar low quote tick",
2270 );
2271
2272 let legs = if high_first {
2273 [high_leg, low_leg]
2274 } else {
2275 [low_leg, high_leg]
2276 };
2277
2278 for (bid_price, ask_price, bid_size, ask_size, context) in legs {
2280 self.fill_at_market = false;
2281 quote_tick.bid_price = bid_price;
2282 quote_tick.ask_price = ask_price;
2283 quote_tick.bid_size = bid_size;
2284 quote_tick.ask_size = ask_size;
2285
2286 if !self.process_bar_quote_tick(
2287 "e_tick,
2288 context,
2289 &mut has_current_bid,
2290 &mut has_current_ask,
2291 ) {
2292 return;
2293 }
2294 }
2295
2296 self.fill_at_market = false;
2298 quote_tick.bid_price = bid_bar.close;
2299 quote_tick.ask_price = ask_bar.close;
2300 quote_tick.bid_size = bid_sizes.close;
2301 quote_tick.ask_size = ask_sizes.close;
2302
2303 if !self.process_bar_quote_tick(
2304 "e_tick,
2305 "bar close quote tick",
2306 &mut has_current_bid,
2307 &mut has_current_ask,
2308 ) {
2309 return;
2310 }
2311
2312 self.last_bar_bid = None;
2313 self.last_bar_ask = None;
2314 self.fill_at_market = true;
2315 }
2316
2317 fn process_bar_quote_tick(
2318 &mut self,
2319 quote: &QuoteTick,
2320 context: &str,
2321 has_current_bid: &mut bool,
2322 has_current_ask: &mut bool,
2323 ) -> bool {
2324 let has_bid_size = quote.bid_size.non_zero();
2325 let has_ask_size = quote.ask_size.non_zero();
2326 let mut book_changed = false;
2327 let mut bid_cleared = false;
2328 let mut ask_cleared = false;
2329
2330 match (has_bid_size, has_ask_size) {
2331 (true, true) => {
2332 if !self.update_quote_tick_or_skip(quote, context) {
2333 return false;
2334 }
2335
2336 *has_current_bid = true;
2337 *has_current_ask = true;
2338 book_changed = true;
2339 }
2340 _ => {
2341 if has_bid_size {
2342 self.update_bar_quote_bid(quote);
2343 *has_current_bid = true;
2344 book_changed = true;
2345 } else if !*has_current_bid {
2346 self.clear_bar_quote_bid(quote);
2347 *has_current_bid = true;
2348 book_changed = true;
2349 bid_cleared = true;
2350 }
2351
2352 if has_ask_size {
2353 self.update_bar_quote_ask(quote);
2354 *has_current_ask = true;
2355 book_changed = true;
2356 } else if !*has_current_ask {
2357 self.clear_bar_quote_ask(quote);
2358 *has_current_ask = true;
2359 book_changed = true;
2360 ask_cleared = true;
2361 }
2362 }
2363 }
2364
2365 if book_changed
2366 && let (Some(best_bid), Some(best_ask)) =
2367 (self.book.best_bid_price(), self.book.best_ask_price())
2368 && best_bid > best_ask
2369 {
2370 if has_bid_size && !has_ask_size {
2371 self.clear_bar_quote_ask(quote);
2372 ask_cleared = true;
2373 } else if has_ask_size && !has_bid_size {
2374 self.clear_bar_quote_bid(quote);
2375 bid_cleared = true;
2376 }
2377 }
2378
2379 if has_bid_size {
2380 self.last_quote_bid = Some(quote.bid_price);
2381 } else if bid_cleared {
2382 self.last_quote_bid = None;
2383 }
2384
2385 if has_ask_size {
2386 self.last_quote_ask = Some(quote.ask_price);
2387 } else if ask_cleared {
2388 self.last_quote_ask = None;
2389 }
2390
2391 if !book_changed {
2392 return true;
2393 }
2394
2395 self.reset_trade_liquidity(quote.ts_event);
2396
2397 self.iterate(quote.ts_init, AggressorSide::NoAggressor);
2398 true
2399 }
2400
2401 fn bar_high_first(&self, bar: &Bar) -> bool {
2402 !self.config.bar_adaptive_high_low_ordering || bar.high - bar.open < bar.open - bar.low
2403 }
2404
2405 fn update_bar_quote_bid(&mut self, quote: &QuoteTick) {
2406 let bid = BookOrder::new(
2407 OrderSide::Buy,
2408 quote.bid_price,
2409 quote.bid_size,
2410 OrderSide::Buy as u64,
2411 );
2412 self.book
2413 .add(bid, 0, self.book.sequence.saturating_add(1), quote.ts_event);
2414 }
2415
2416 fn clear_bar_quote_bid(&mut self, quote: &QuoteTick) {
2417 self.book
2418 .clear_bids(self.book.sequence.saturating_add(1), quote.ts_event);
2419 }
2420
2421 fn update_bar_quote_ask(&mut self, quote: &QuoteTick) {
2422 let ask = BookOrder::new(
2423 OrderSide::Sell,
2424 quote.ask_price,
2425 quote.ask_size,
2426 OrderSide::Sell as u64,
2427 );
2428 self.book
2429 .add(ask, 0, self.book.sequence.saturating_add(1), quote.ts_event);
2430 }
2431
2432 fn clear_bar_quote_ask(&mut self, quote: &QuoteTick) {
2433 self.book
2434 .clear_asks(self.book.sequence.saturating_add(1), quote.ts_event);
2435 }
2436
2437 pub fn process_trade_tick(&mut self, trade: &TradeTick) {
2448 log::debug!("Processing {trade}");
2449
2450 if let Err(e) = self.check_price_precision(trade.price.precision, "trade price") {
2451 self.log_precision_mismatch("trade tick", trade.instrument_id, &e);
2452 return;
2453 }
2454
2455 if let Err(e) = self.check_size_precision(trade.size.precision, "trade size") {
2456 self.log_precision_mismatch("trade tick", trade.instrument_id, &e);
2457 return;
2458 }
2459
2460 self.precision_mismatch_streak = 0;
2461
2462 let price_raw = trade.price.raw();
2463
2464 if self.book_type == BookType::L1_MBP {
2465 if trade.ts_event < self.book.ts_last {
2467 log::warn!(
2468 "Skipping stale trade: ts_event {} < book.ts_last {} for {}",
2469 trade.ts_event,
2470 self.book.ts_last,
2471 self.book.instrument_id,
2472 );
2473 self.iterate(trade.ts_init, AggressorSide::NoAggressor);
2474 return;
2475 }
2476
2477 if !self.update_trade_tick_or_skip(trade, "trade tick") {
2478 return;
2479 }
2480 }
2481
2482 self.core.set_last_raw(trade.price);
2483
2484 if !self.config.trade_execution {
2485 if self.book_type == BookType::L1_MBP {
2486 if let Some(bid) = self.book.best_bid_price() {
2487 self.core.set_bid_raw(bid);
2488 }
2489
2490 if let Some(ask) = self.book.best_ask_price() {
2491 self.core.set_ask_raw(ask);
2492 }
2493 } else {
2494 self.iterate_with_mode(
2495 trade.ts_init,
2496 AggressorSide::NoAggressor,
2497 OrderMatchMode::StopTriggers(TriggerType::LastPrice),
2498 );
2499 }
2500
2501 return;
2502 }
2503
2504 let aggressor_side = trade.aggressor_side;
2505
2506 match aggressor_side {
2507 AggressorSide::Buy => {
2508 if self.core.ask.is_none_or(|ask| trade.price > ask) {
2511 self.core.set_ask_raw(trade.price);
2512 }
2513
2514 if self.core.bid.is_none() {
2516 self.core.set_bid_raw(trade.price);
2517 }
2518 }
2519 AggressorSide::Sell => {
2520 if self.core.bid.is_none_or(|bid| trade.price < bid) {
2523 self.core.set_bid_raw(trade.price);
2524 }
2525
2526 if self.core.ask.is_none() {
2528 self.core.set_ask_raw(trade.price);
2529 }
2530 }
2531 AggressorSide::NoAggressor => {
2532 if self.core.bid.is_none_or(|bid| trade.price <= bid) {
2533 self.core.set_bid_raw(trade.price);
2534 }
2535
2536 if self.core.ask.is_none_or(|ask| trade.price >= ask) {
2537 self.core.set_ask_raw(trade.price);
2538 }
2539 }
2540 }
2541
2542 let original_bid = self.core.bid;
2543 let original_ask = self.core.ask;
2544
2545 match aggressor_side {
2546 AggressorSide::Sell => {
2547 if original_ask.is_some_and(|ask| trade.price < ask) {
2548 self.core.set_ask_raw(trade.price);
2549 }
2550 }
2551 AggressorSide::Buy => {
2552 if original_bid.is_some_and(|bid| trade.price > bid) {
2553 self.core.set_bid_raw(trade.price);
2554 }
2555 }
2556 AggressorSide::NoAggressor => {
2557 self.core.set_bid_raw(trade.price);
2559 self.core.set_ask_raw(trade.price);
2560 }
2561 }
2562
2563 self.start_trade_iteration(trade.size);
2564
2565 if self.config.liquidity_consumption && self.book_type != BookType::L1_MBP {
2566 self.seed_trade_consumption(
2567 price_raw,
2568 trade.size.raw(),
2569 trade.ts_event,
2570 aggressor_side,
2571 );
2572 }
2573
2574 self.resolve_pending_on_trade(price_raw);
2575 self.decrement_queue_on_trade(price_raw, trade.size.raw(), aggressor_side);
2576
2577 self.iterate(trade.ts_init, aggressor_side);
2578
2579 self.finish_trade_iteration();
2580
2581 if self.book_type == BookType::L1_MBP {
2587 match aggressor_side {
2588 AggressorSide::Sell => {
2589 if let Some(ask) = self.last_quote_ask {
2590 self.core.ask = Some(ask);
2591 }
2592 }
2593 AggressorSide::Buy => {
2594 if let Some(bid) = self.last_quote_bid {
2595 self.core.bid = Some(bid);
2596 }
2597 }
2598 AggressorSide::NoAggressor => {}
2599 }
2600 } else {
2601 match aggressor_side {
2602 AggressorSide::Sell => {
2603 if let Some(ask) = original_ask
2604 && trade.price < ask
2605 {
2606 self.core.ask = Some(ask);
2607 }
2608 }
2609 AggressorSide::Buy => {
2610 if let Some(bid) = original_bid
2611 && trade.price > bid
2612 {
2613 self.core.bid = Some(bid);
2614 }
2615 }
2616 AggressorSide::NoAggressor => {}
2617 }
2618 }
2619 }
2620
2621 fn start_trade_iteration(&mut self, size: Quantity) {
2622 self.last_trade_size = Some(size);
2623 self.trade_consumption = 0;
2624 self.l1_trade_size = (self.book_type == BookType::L1_MBP
2625 && self.config.liquidity_consumption)
2626 .then_some(size.raw());
2627 }
2628
2629 fn finish_trade_iteration(&mut self) {
2630 self.last_trade_size = None;
2631
2632 if self.l1_trade_size.is_none() {
2634 self.trade_consumption = 0;
2635 }
2636 }
2637
2638 fn reset_trade_liquidity(&mut self, timestamp: UnixNanos) {
2639 if timestamp >= self.book.ts_last {
2640 self.l1_trade_size = None;
2641 self.trade_consumption = 0;
2642 }
2643 }
2644
2645 fn update_quote_tick_or_skip(&mut self, quote: &QuoteTick, context: &str) -> bool {
2646 if let Err(e) = self.book.update_quote_tick(quote) {
2647 log::warn!(
2648 "Skipping {context} for {}: update_quote_tick failed: {e}",
2649 quote.instrument_id,
2650 );
2651 return false;
2652 }
2653
2654 self.reset_trade_liquidity(quote.ts_event);
2655 true
2656 }
2657
2658 fn update_trade_tick_or_skip(&mut self, trade: &TradeTick, context: &str) -> bool {
2659 if let Err(e) = self.book.update_trade_tick(trade) {
2660 log::warn!(
2661 "Skipping {context} for {}: update_trade_tick failed: {e}",
2662 trade.instrument_id,
2663 );
2664 return false;
2665 }
2666
2667 self.reset_trade_liquidity(trade.ts_event);
2668 true
2669 }
2670
2671 pub fn process_status(&mut self, action: MarketStatusAction) {
2673 log::debug!("Processing {action}");
2674
2675 match action {
2676 MarketStatusAction::Trading | MarketStatusAction::PreOpen
2677 if matches!(
2678 self.market_status,
2679 MarketStatus::Closed | MarketStatus::Paused | MarketStatus::Suspended
2680 ) =>
2681 {
2682 self.market_status = MarketStatus::Open;
2683 }
2684 MarketStatusAction::Pause if self.market_status == MarketStatus::Open => {
2685 self.market_status = MarketStatus::Paused;
2686 }
2687 MarketStatusAction::Suspend if self.market_status == MarketStatus::Open => {
2688 self.market_status = MarketStatus::Suspended;
2689 }
2690 MarketStatusAction::Halt | MarketStatusAction::Close
2691 if self.market_status == MarketStatus::Open =>
2692 {
2693 self.market_status = MarketStatus::Closed;
2694 }
2695 _ => {}
2696 }
2697 }
2698
2699 pub fn process_instrument_close(&mut self, close: InstrumentClose) {
2705 if close.instrument_id != self.instrument.id() {
2706 log::warn!(
2707 "Received instrument close for unknown instrument_id: {}",
2708 close.instrument_id
2709 );
2710 return;
2711 }
2712
2713 if close.close_type == InstrumentCloseType::ContractExpired {
2714 self.instrument_close = Some(close);
2715 self.iterate(close.ts_init, AggressorSide::NoAggressor);
2716 }
2717 }
2718
2719 pub fn process_instrument_expiration(&mut self, timestamp_ns: UnixNanos) {
2724 self.check_instrument_expiration(timestamp_ns, false, &[]);
2725 }
2726
2727 #[must_use]
2729 pub const fn is_expiration_processed(&self) -> bool {
2730 self.expiration_processed
2731 }
2732
2733 pub fn has_pending_fills(&mut self) -> bool {
2735 self.purge_applied_fills();
2736 !self.pending_fills.is_empty()
2737 }
2738
2739 fn requires_pending_resolution(&self) -> bool {
2740 matches!(self.instrument, InstrumentAny::BinaryOption(_))
2741 }
2742
2743 fn cancel_open_orders_for_expiration(&mut self, excluded: &[ClientOrderId]) {
2744 let instrument_id = self.instrument.id();
2749
2750 let expiration_order_ids: IndexSet<ClientOrderId> = {
2751 let cache = self.cache.borrow();
2752 let mut order_ids = IndexSet::new();
2753 for order_info in self.get_open_orders() {
2754 order_ids.insert(order_info.client_order_id);
2755 }
2756
2757 for order in cache.orders(None, Some(&instrument_id), None, None, None) {
2758 if order.is_open() || order.is_inflight() {
2759 order_ids.insert(order.client_order_id());
2760 }
2761 }
2762
2763 order_ids.retain(|client_order_id| !excluded.contains(client_order_id));
2764 order_ids
2765 };
2766
2767 for client_order_id in expiration_order_ids {
2768 if let Some(order) = self.order_snapshot(client_order_id)
2769 && !self.contingent_order_is_closed(&order)
2770 {
2771 self.cancel_order_excluding(&order, None, excluded);
2772 }
2773 }
2774 }
2775
2776 fn enter_pending_resolution(&mut self, excluded: &[ClientOrderId]) {
2777 if self.pending_resolution {
2778 return;
2779 }
2780
2781 self.pending_resolution = true;
2782 self.market_status = MarketStatus::Closed;
2783 self.cancel_open_orders_for_expiration(excluded);
2784 log::info!(
2785 "{} expired and is now pending resolution; open orders canceled and new orders blocked",
2786 self.instrument.id()
2787 );
2788 }
2789
2790 fn check_instrument_expiration(
2791 &mut self,
2792 timestamp_ns: UnixNanos,
2793 defer_settlement: bool,
2794 excluded: &[ClientOrderId],
2795 ) {
2796 if self.expiration_processed || self.option_settlement_failed {
2797 return;
2798 }
2799
2800 let timestamp_triggered = self
2801 .instrument
2802 .expiration_ns()
2803 .is_some_and(|ns| timestamp_ns >= ns);
2804
2805 if !timestamp_triggered && self.instrument_close.is_none() {
2806 return;
2807 }
2808
2809 if self.instrument_close.is_none()
2810 && timestamp_triggered
2811 && self.requires_pending_resolution()
2812 {
2813 self.enter_pending_resolution(excluded);
2814 return;
2815 }
2816
2817 self.enter_pending_resolution(excluded);
2818
2819 if self.has_pending_fills() {
2821 return;
2822 }
2823
2824 if matches!(
2825 self.instrument,
2826 InstrumentAny::OptionContract(_) | InstrumentAny::CryptoOption(_)
2827 ) {
2828 if defer_settlement
2831 && self.instrument_close.is_none()
2832 && self.instrument.expiration_ns() == Some(timestamp_ns)
2833 {
2834 return;
2835 }
2836
2837 match self.process_option_expiry(timestamp_ns) {
2838 Ok(true) => {
2839 self.expiration_processed = true;
2840 self.pending_resolution = false;
2841 self.instrument_close.take();
2842 self.option_settlement_warning = None;
2843 log::info!("{} reached expiration", self.instrument.id());
2844 }
2845 Ok(false) => {}
2846 Err(e) => {
2847 self.option_settlement_failed = true;
2848 log::error!(
2849 "Option settlement failed terminally for {}: {e}",
2850 self.instrument.id()
2851 );
2852 }
2853 }
2854
2855 return;
2856 }
2857
2858 self.expiration_processed = true;
2859 self.pending_resolution = false;
2860 let close = self.instrument_close.take();
2861 log::info!("{} reached expiration", self.instrument.id());
2862
2863 let instrument_id = self.instrument.id();
2864
2865 let positions: Vec<(
2866 TraderId,
2867 StrategyId,
2868 AccountId,
2869 PositionId,
2870 OrderSide,
2871 Quantity,
2872 )> = {
2873 let cache = self.cache.borrow();
2874 cache
2875 .positions_open(None, Some(&instrument_id), None, None, None)
2876 .into_iter()
2877 .filter_map(|pos| {
2878 OrderCore::closing_side(pos.side).map(|closing_side| {
2879 (
2880 pos.trader_id,
2881 pos.strategy_id,
2882 pos.account_id,
2883 pos.id,
2884 closing_side,
2885 pos.quantity,
2886 )
2887 })
2888 })
2889 .collect()
2890 };
2891
2892 let ts_now = self.clock.borrow().timestamp_ns();
2893 let close_price = close.as_ref().map(|close| close.close_price);
2894
2895 for (trader_id, strategy_id, account_id, position_id, closing_side, quantity) in positions {
2896 let client_order_id =
2897 ClientOrderId::from(format!("EXPIRATION-{}-{}", self.venue, UUID4::new()).as_str());
2898 let mut order = OrderAny::Market(MarketOrder::new(
2899 trader_id,
2900 strategy_id,
2901 instrument_id,
2902 client_order_id,
2903 closing_side,
2904 quantity,
2905 TimeInForce::Gtc,
2906 UUID4::new(),
2907 ts_now,
2908 true, false,
2910 None,
2911 None,
2912 None,
2913 None,
2914 None,
2915 None,
2916 None,
2917 Some(vec![Ustr::from(&format!(
2918 "EXPIRATION_{}_CLOSE",
2919 self.venue
2920 ))]),
2921 ));
2922 order.set_liquidity_side(LiquiditySide::Taker);
2923
2924 let add_result =
2925 self.cache
2926 .borrow_mut()
2927 .add_order(order.clone(), Some(position_id), None, false);
2928
2929 if add_result.is_err() {
2930 log::debug!("Expiration order already in cache: {client_order_id}");
2931 } else {
2932 self.publish_order_initialized(&order);
2933 }
2934
2935 let venue_order_id = self.ids_generator.get_venue_order_id(&order).unwrap();
2936
2937 self.account_ids.insert(trader_id, account_id);
2940 self.generate_order_accepted(&order, venue_order_id);
2941
2942 if let Some(fill_price) = close_price {
2943 if let Err(e) = self.apply_fills(
2944 &order,
2945 &[(fill_price, quantity)],
2946 LiquiditySide::Taker,
2947 Some(position_id),
2948 None,
2949 None,
2950 false,
2951 ) {
2952 log::error!("Cannot fill expiration order {client_order_id}: {e}");
2953 }
2954 } else {
2955 self.fill_market_order(client_order_id);
2956 }
2957 }
2958 }
2959
2960 pub fn liquidate_open_positions(
2975 &mut self,
2976 ts_now: UnixNanos,
2977 cancel_open_orders: bool,
2978 settlement_currency: Currency,
2979 ) {
2980 if self.instrument.settlement_currency() != settlement_currency {
2982 return;
2983 }
2984
2985 if cancel_open_orders {
2986 let open_orders: Vec<RestingOrder> = self.get_open_orders();
2987 for order_info in &open_orders {
2988 let order = {
2989 let cache = self.cache.borrow();
2990 cache.order_owned(&order_info.client_order_id)
2991 };
2992
2993 if let Some(order) = order {
2994 self.cancel_order(&order, None);
2995 }
2996 }
2997 }
2998
2999 let instrument_id = self.instrument.id();
3000
3001 let positions: Vec<(
3002 TraderId,
3003 StrategyId,
3004 AccountId,
3005 PositionId,
3006 OrderSide,
3007 Quantity,
3008 )> = {
3009 let cache = self.cache.borrow();
3010 cache
3011 .positions_open(None, Some(&instrument_id), None, None, None)
3012 .into_iter()
3013 .filter_map(|pos| {
3014 OrderCore::closing_side(pos.side).map(|closing_side| {
3015 (
3016 pos.trader_id,
3017 pos.strategy_id,
3018 pos.account_id,
3019 pos.id,
3020 closing_side,
3021 pos.quantity,
3022 )
3023 })
3024 })
3025 .collect()
3026 };
3027
3028 for (trader_id, strategy_id, account_id, position_id, closing_side, quantity) in positions {
3029 let has_price = if closing_side == OrderSide::Sell {
3031 self.best_bid_price().is_some()
3032 } else {
3033 self.best_ask_price().is_some()
3034 };
3035
3036 if !has_price {
3037 log::warn!(
3038 "LIQUIDATION: no price available for {instrument_id} position {position_id}, skipping"
3039 );
3040 continue;
3041 }
3042
3043 let client_order_id = ClientOrderId::from(
3044 format!("LIQUIDATION-{}-{}", self.venue, UUID4::new()).as_str(),
3045 );
3046 let order = OrderAny::Market(MarketOrder::new(
3047 trader_id,
3048 strategy_id,
3049 instrument_id,
3050 client_order_id,
3051 closing_side,
3052 quantity,
3053 TimeInForce::Ioc,
3054 UUID4::new(),
3055 ts_now,
3056 true, false,
3058 None,
3059 None,
3060 None,
3061 None,
3062 None,
3063 None,
3064 None,
3065 Some(vec![Ustr::from(&format!(
3066 "LIQUIDATION_{}_CLOSE",
3067 self.venue
3068 ))]),
3069 ));
3070
3071 let venue_order_id = self.ids_generator.get_venue_order_id(&order).unwrap();
3072 {
3073 let mut cache = self.cache.borrow_mut();
3074 if let Err(e) = cache.add_order(order.clone(), Some(position_id), None, false) {
3075 log::debug!("Liquidation order already in cache: {e}");
3076 } else {
3077 drop(cache);
3078 self.publish_order_initialized(&order);
3079 self.cache
3080 .borrow_mut()
3081 .add_venue_order_id(&client_order_id, &venue_order_id, false)
3082 .ok();
3083 }
3084 }
3085
3086 self.account_ids.insert(trader_id, account_id);
3089 self.generate_order_submitted(&order, account_id);
3090 self.generate_order_accepted(&order, venue_order_id);
3091 self.fill_market_order(client_order_id);
3092 }
3093 }
3094
3095 pub fn process_order(&mut self, order: &mut OrderAny, account_id: AccountId) {
3105 self.process_order_received(order, account_id, false);
3106 }
3107
3108 pub fn process_oto_command(&mut self, command: &OtoCommand) {
3116 match command {
3117 OtoCommand::Release {
3118 client_order_id,
3119 account_id,
3120 ..
3121 } => {
3122 if self
3123 .orders
3124 .closed_oto_order_ids
3125 .borrow()
3126 .contains(client_order_id)
3127 || self
3128 .cache
3129 .borrow()
3130 .venue_order_id(client_order_id)
3131 .is_some()
3132 {
3133 return;
3134 }
3135
3136 let Some(mut order) = self.order_snapshot(*client_order_id) else {
3137 return;
3138 };
3139
3140 if let Err(e) = self.apply_deferred_submission(&mut order, false) {
3141 log::error!("Cannot release OTO order {client_order_id}: {e}");
3142 return;
3143 }
3144
3145 if self.contingent_order_is_closed(&order)
3146 || self.inflight_orders.contains(*client_order_id)
3147 {
3148 return;
3149 }
3150
3151 self.process_order_received(&mut order, *account_id, true);
3152 }
3153 OtoCommand::Resize {
3154 parent_order_id,
3155 quantity,
3156 ..
3157 } => self.process_oto_resize(*parent_order_id, *quantity),
3158 OtoCommand::Cancel {
3159 client_order_id,
3160 cancel_contingencies,
3161 excluded,
3162 ..
3163 } => {
3164 let Some(mut order) = self.order_snapshot(*client_order_id) else {
3165 return;
3166 };
3167
3168 if self.contingent_order_is_closed(&order) {
3169 return;
3170 }
3171
3172 if let Err(e) = self.apply_deferred_submission(&mut order, false) {
3173 log::error!("Cannot cancel OTO order {client_order_id}: {e}");
3174 return;
3175 }
3176
3177 self.cancel_order_excluding(&order, Some(*cancel_contingencies), excluded);
3178 }
3179 }
3180 }
3181
3182 pub fn process_oto_resize(&mut self, parent_order_id: ClientOrderId, quantity: Quantity) {
3191 if let Some(parent) = self.order_snapshot(parent_order_id) {
3192 self.update_oto_children(&parent, Some(quantity), &[]);
3193 }
3194 }
3195
3196 fn process_order_received(
3197 &mut self,
3198 order: &mut OrderAny,
3199 account_id: AccountId,
3200 released: bool,
3201 ) {
3202 if self.core.order_exists(order.client_order_id()) {
3204 return;
3205 }
3206
3207 if !self.cache.borrow().order_exists(&order.client_order_id()) {
3208 log::error!(
3209 "Cannot process order {}: not found in cache",
3210 order.client_order_id()
3211 );
3212 return;
3213 }
3214
3215 let ts_now = self.clock.borrow().timestamp_ns();
3218 self.check_instrument_expiration(
3219 ts_now,
3220 self.config.defer_option_settlement,
3221 &[order.client_order_id()],
3222 );
3223
3224 if self.config.support_contingent_orders
3225 && order.parent_order_id().is_some()
3226 && !self.prepare_oto_submission(order, account_id)
3227 {
3228 return;
3229 }
3230
3231 self.process_order_internal(order, account_id, released);
3232 }
3233
3234 fn prepare_oto_submission(&mut self, order: &mut OrderAny, account_id: AccountId) -> bool {
3235 let id = order.client_order_id();
3236
3237 let Some(snapshot) = self.order_snapshot(id) else {
3238 return false;
3239 };
3240
3241 if self.contingent_order_is_closed(&snapshot) {
3242 return false;
3243 }
3244
3245 *order = snapshot;
3246 self.account_ids.insert(order.trader_id(), account_id);
3247 self.orders.pending_oto_order_ids.borrow_mut().insert(id);
3248
3249 if let Err(e) = self.apply_deferred_submission(order, false) {
3250 log::error!("Cannot process OTO submission {id}: {e}");
3251 return false;
3252 }
3253
3254 if let Some(command) = self.oto_submission_cancel(order) {
3255 self.process_oto_command(&command);
3256 return false;
3257 }
3258
3259 self.resize_oto_order(order)
3260 }
3261
3262 fn resize_oto_order(&mut self, order: &mut OrderAny) -> bool {
3263 let Some(parent) = self.full_trigger_oto_parent(order) else {
3264 return true;
3265 };
3266
3267 if let Err(e) = self.apply_deferred_submission(order, false) {
3268 log::error!("Cannot resize OTO order {}: {e}", order.client_order_id());
3269 return false;
3270 }
3271
3272 let ids = parent
3273 .linked_order_ids()
3274 .expect("OTO parent has linked children");
3275 let filled_qty = self.engine_filled_qty(&parent);
3276 let remaining = self.oto_child_remaining(order, ids, parent.quantity());
3277 if remaining.is_zero() {
3278 self.cancel_order(order, Some(false));
3279 return false;
3280 }
3281
3282 let parent_inverse = self
3283 .cache
3284 .borrow()
3285 .instrument(&parent.instrument_id())
3286 .is_some_and(Instrument::is_inverse);
3287
3288 let sizing = OtoSizing {
3289 quantity: parent.quantity(),
3290 filled_qty,
3291 quote: parent.is_quote_quantity() && !parent_inverse,
3292 reduced_to_filled: true,
3293 grew: false,
3294 };
3295
3296 self.resize_oto_child(order, &sizing, remaining);
3297 if let Some(snapshot) = self.order_snapshot(order.client_order_id()) {
3298 order.set_quantity(snapshot.quantity());
3299 order.set_leaves_qty(snapshot.leaves_qty());
3300 order.set_is_quote_quantity(snapshot.is_quote_quantity());
3301 }
3302
3303 true
3304 }
3305
3306 fn full_trigger_oto_parent(&self, order: &OrderAny) -> Option<OrderAny> {
3307 if !self.config.support_contingent_orders || !self.config.oto_full_trigger {
3308 return None;
3309 }
3310
3311 let parent = self.order_snapshot(order.parent_order_id()?)?;
3312 if parent.contingency_type() != Some(ContingencyType::Oto) {
3313 return None;
3314 }
3315
3316 let ids = parent.linked_order_ids()?;
3317 if self.engine_filled_qty(&parent) < parent.quantity()
3318 || !ids
3319 .iter()
3320 .filter_map(|id| self.order_snapshot(*id))
3321 .any(|child| child.instrument_id() != parent.instrument_id())
3322 {
3323 return None;
3324 }
3325
3326 Some(parent)
3327 }
3328
3329 fn process_order_internal(
3330 &mut self,
3331 order: &mut OrderAny,
3332 account_id: AccountId,
3333 released: bool,
3334 ) {
3335 let reject_reason: Option<Ustr> = 'validate: {
3340 let cache_borrow = self.cache.as_ref().borrow();
3341
3342 self.account_ids.insert(order.trader_id(), account_id);
3344
3345 if self.pending_resolution {
3346 break 'validate Some(
3347 format!(
3348 "Contract {} has expired and is pending resolution",
3349 self.instrument.id()
3350 )
3351 .into(),
3352 );
3353 }
3354
3355 if self.instrument.has_expiration() {
3357 if let Some(activation_ns) = self.instrument.activation_ns()
3358 && self.clock.borrow().timestamp_ns() < activation_ns
3359 {
3360 break 'validate Some(
3361 format!(
3362 "Contract {} is not yet active, activation {activation_ns}",
3363 self.instrument.id(),
3364 )
3365 .into(),
3366 );
3367 }
3368
3369 if let Some(expiration_ns) = self.instrument.expiration_ns()
3370 && self.clock.borrow().timestamp_ns() >= expiration_ns
3371 {
3372 break 'validate Some(
3373 format!(
3374 "Contract {} has expired, expiration {expiration_ns}",
3375 self.instrument.id(),
3376 )
3377 .into(),
3378 );
3379 }
3380 }
3381
3382 if self.expiration_processed {
3384 break 'validate Some(
3385 format!("Contract {} has expired", self.instrument.id()).into(),
3386 );
3387 }
3388
3389 if self.market_status != MarketStatus::Open {
3390 break 'validate Some(
3391 format!(
3392 "Market {} is {}, cannot accept order {}",
3393 self.instrument.id(),
3394 self.market_status,
3395 order.client_order_id()
3396 )
3397 .into(),
3398 );
3399 }
3400
3401 if self.config.support_contingent_orders {
3403 if let Some(parent_order_id) = order.parent_order_id() {
3404 self.orders
3406 .pending_oto_order_ids
3407 .borrow_mut()
3408 .swap_remove(&order.client_order_id());
3409
3410 let parent_order = match self.order_snapshot(parent_order_id) {
3411 Some(o) if o.contingency_type() == Some(ContingencyType::Oto) => o,
3412 _ => panic!("OTO parent not found"),
3413 };
3414
3415 let parent_filled_qty = parent_order.filled_qty();
3416
3417 if parent_order.status() == OrderStatus::Rejected
3419 || self
3420 .orders
3421 .rejected_oto_parent_ids
3422 .borrow()
3423 .contains(&parent_order_id)
3424 {
3425 break 'validate Some(
3426 format!("Rejected OTO order from {parent_order_id}").into(),
3427 );
3428 } else if !released
3429 && (parent_filled_qty.is_zero()
3430 || (self.config.oto_full_trigger
3431 && parent_filled_qty < parent_order.quantity()))
3432 {
3433 log::info!(
3434 "Pending OTO order {} triggers from {parent_order_id}",
3435 order.client_order_id(),
3436 );
3437
3438 self.orders
3439 .pending_oto_order_ids
3440 .borrow_mut()
3441 .insert(order.client_order_id());
3442
3443 return;
3444 }
3445 }
3446
3447 if let Some(linked_order_ids) = order.linked_order_ids() {
3448 let contingency_type = order.contingency_type();
3449
3450 for client_order_id in linked_order_ids {
3451 match cache_borrow.order(client_order_id) {
3452 Some(contingent_order)
3453 if matches!(
3454 contingency_type,
3455 Some(ContingencyType::Oco | ContingencyType::Ouo)
3456 ) && !order.is_closed()
3457 && contingent_order.is_closed()
3458 && !Self::is_rejected_oto_sibling(order, &contingent_order) =>
3459 {
3460 break 'validate Some(
3461 format!("Contingent order {client_order_id} already closed")
3462 .into(),
3463 );
3464 }
3465 None => panic!("Cannot find contingent order for {client_order_id}"),
3466 _ => {}
3467 }
3468 }
3469 }
3470 }
3471
3472 if !order_precision_valid(order.quantity().precision, self.instrument.size_precision())
3474 {
3475 break 'validate Some(
3476 format!(
3477 "Invalid order quantity precision for order {}, was {} when {} size precision is {}",
3478 order.client_order_id(),
3479 order.quantity().precision,
3480 self.instrument.id(),
3481 self.instrument.size_precision()
3482 )
3483 .into(),
3484 );
3485 }
3486
3487 if let Some(display_qty) = order.display_qty()
3489 && !order_precision_valid(display_qty.precision, self.instrument.size_precision())
3490 {
3491 break 'validate Some(
3492 format!(
3493 "Invalid order display quantity precision for order {}, was {} when {} size precision is {}",
3494 order.client_order_id(),
3495 display_qty.precision,
3496 self.instrument.id(),
3497 self.instrument.size_precision()
3498 )
3499 .into(),
3500 );
3501 }
3502
3503 if let Some(reason) = order
3505 .price()
3506 .and_then(|price| self.order_price_rejection(order, "price", price))
3507 {
3508 break 'validate Some(reason);
3509 }
3510
3511 if let Some(reason) = order
3512 .trigger_price()
3513 .and_then(|price| self.order_price_rejection(order, "trigger price", price))
3514 {
3515 break 'validate Some(reason);
3516 }
3517
3518 if order.is_reduce_only() && !self.config.use_reduce_only {
3519 break 'validate Some(
3520 "Reduce-only orders are not supported by this matching engine".into(),
3521 );
3522 }
3523
3524 None
3525 };
3526
3527 if let Some(reason) = reject_reason {
3528 self.generate_order_rejected(order, reason);
3529 return;
3530 }
3531
3532 if let Some(reason) = self
3533 .order_position_rejection(order)
3534 .unwrap_or_else(|e| Some(e.to_string().into()))
3535 {
3536 self.generate_order_rejected(order, reason);
3537 return;
3538 }
3539
3540 if order.is_quote_quantity()
3548 && !self.instrument.is_inverse()
3549 && !matches!(
3550 order.order_type(),
3551 OrderType::TrailingStopLimit | OrderType::TrailingStopMarket,
3552 )
3553 && (order.price().is_some()
3554 || matches!(
3555 order.order_type(),
3556 OrderType::Market | OrderType::MarketToLimit,
3557 ))
3558 && !self.convert_quote_to_base_quantity(order)
3559 {
3560 return;
3561 }
3562
3563 match order.order_type() {
3564 OrderType::Market => self.process_market_order(order),
3565 OrderType::Limit => self.process_limit_order(order),
3566 OrderType::MarketToLimit => self.process_market_to_limit_order(order),
3567 OrderType::StopMarket => self.process_stop_market_order(order),
3568 OrderType::StopLimit => self.process_stop_limit_order(order),
3569 OrderType::MarketIfTouched => self.process_market_if_touched_order(order),
3570 OrderType::LimitIfTouched => self.process_limit_if_touched_order(order),
3571 OrderType::TrailingStopMarket => self.process_trailing_stop_order(order),
3572 OrderType::TrailingStopLimit => self.process_trailing_stop_order(order),
3573 }
3574 }
3575
3576 fn is_rejected_oto_sibling(order: &OrderAny, sibling: &OrderAny) -> bool {
3577 sibling.status() == OrderStatus::Rejected
3578 && order
3579 .parent_order_id()
3580 .is_some_and(|parent_id| sibling.parent_order_id() == Some(parent_id))
3581 }
3582
3583 fn order_position_rejection(&mut self, order: &OrderAny) -> anyhow::Result<Option<Ustr>> {
3584 let checks_reduce_only = self.config.use_reduce_only
3585 && order.is_reduce_only()
3586 && !order.is_closed()
3587 && !(self.restricts_short_selling() && order.is_sell());
3588
3589 if self.restricts_short_selling() || checks_reduce_only {
3590 self.purge_applied_fills();
3591 }
3592
3593 let cache = self.cache.borrow();
3594 let position = self.position_for_order_in_cache(&cache, order);
3595
3596 if let Some(reason) = self.cash_sell_rejection(&cache, order, position.as_ref())? {
3597 return Ok(Some(reason));
3598 }
3599
3600 if checks_reduce_only {
3601 let quantity = self.position_quantity(&cache, order, position.as_ref())?;
3604
3605 if (order.is_buy() && quantity >= Decimal::ZERO)
3606 || (order.is_sell() && quantity <= Decimal::ZERO)
3607 {
3608 return Ok(Some(
3609 format!(
3610 "Reduce-only order {} ({}-{}) would have increased position",
3611 order.client_order_id(),
3612 order.order_type().to_string().to_uppercase(),
3613 order.order_side().to_string().to_uppercase()
3614 )
3615 .into(),
3616 ));
3617 }
3618 }
3619
3620 Ok(None)
3621 }
3622
3623 fn restricts_short_selling(&self) -> bool {
3624 self.account_type != AccountType::Margin
3625 && matches!(
3626 self.instrument,
3627 InstrumentAny::Equity(_) | InstrumentAny::BinaryOption(_)
3628 )
3629 }
3630
3631 fn cash_sell_rejection(
3632 &self,
3633 cache: &Cache,
3634 order: &OrderAny,
3635 position: Option<&Position>,
3636 ) -> anyhow::Result<Option<Ustr>> {
3637 if !self.restricts_short_selling() || !order.is_sell() {
3638 return Ok(None);
3639 }
3640
3641 let quantity = if order.is_quote_quantity() {
3642 let reference_price = order.price().or(self.core.bid).ok_or_else(|| {
3643 anyhow::anyhow!(
3644 "No market for {} to convert quote quantity to base",
3645 order.instrument_id()
3646 )
3647 })?;
3648
3649 self.instrument
3650 .try_calculate_base_quantity(order.leaves_qty(), reference_price)?
3651 } else {
3652 order.leaves_qty()
3653 };
3654
3655 let position_quantity = self.position_quantity(cache, order, position)?;
3656
3657 if quantity.as_decimal() <= position_quantity {
3658 return Ok(None);
3659 }
3660
3661 let position_string = position.map_or("None".to_string(), |pos| pos.id.to_string());
3662 Ok(Some(
3663 format!(
3664 "Short selling not permitted on a CASH account with position {position_string} and order {order}",
3665 )
3666 .into(),
3667 ))
3668 }
3669
3670 fn order_price_rejection(&self, order: &OrderAny, field: &str, price: Price) -> Option<Ustr> {
3671 let price_precision = self.instrument.price_precision();
3672 if !order_precision_valid(price.precision, price_precision) {
3673 return Some(
3674 format!(
3675 "Invalid order {field} precision for order {}, was {} when {} price precision is {price_precision}",
3676 order.client_order_id(),
3677 price.precision,
3678 self.instrument.id(),
3679 )
3680 .into(),
3681 );
3682 }
3683
3684 let price_increment = self.instrument.price_increment();
3685 if !Self::price_matches_tick(price, price_increment) {
3686 return Some(
3687 format!(
3688 "Invalid order {field} {price} for order {}, not a multiple of {} price increment {price_increment}",
3689 order.client_order_id(),
3690 self.instrument.id(),
3691 )
3692 .into(),
3693 );
3694 }
3695
3696 None
3697 }
3698
3699 fn convert_quote_to_base_quantity(&self, order: &mut OrderAny) -> bool {
3700 let reference_price = if let Some(price) = order.price() {
3704 Some(price)
3705 } else {
3706 match order.order_side() {
3707 OrderSide::Buy => self.core.ask,
3708 OrderSide::Sell => self.core.bid,
3709 }
3710 };
3711
3712 let Some(reference_price) = reference_price else {
3713 self.generate_order_rejected(
3714 order,
3715 format!(
3716 "No market for {} to convert quote quantity to base",
3717 order.instrument_id(),
3718 )
3719 .into(),
3720 );
3721 return false;
3722 };
3723
3724 let base_quantity = self
3725 .instrument
3726 .calculate_base_quantity(order.quantity(), reference_price);
3727
3728 let ts_now = self.clock.borrow().timestamp_ns();
3729
3730 let update = OrderUpdated::new(
3731 order.trader_id(),
3732 order.strategy_id(),
3733 order.instrument_id(),
3734 order.client_order_id(),
3735 base_quantity,
3736 UUID4::new(),
3737 ts_now,
3738 ts_now,
3739 false,
3740 order.venue_order_id(),
3741 order.account_id(),
3742 None,
3743 None,
3744 None,
3745 false,
3746 );
3747 let event = OrderEventAny::Updated(update);
3748
3749 if let Err(e) = order.apply(event.clone()) {
3753 log::error!(
3754 "Failed to apply quote-to-base update for {}: {e}",
3755 order.client_order_id(),
3756 );
3757 return false;
3758 }
3759
3760 self.orders
3762 .pending_updates
3763 .borrow_mut()
3764 .entry(order.client_order_id())
3765 .or_default()
3766 .push(update);
3767 self.dispatch_order_event(event);
3768 true
3769 }
3770
3771 pub fn restore_open_order(&mut self, order: &OrderAny, account_id: AccountId) {
3785 if self.core.order_exists(order.client_order_id()) {
3786 return;
3787 }
3788
3789 if self.contingent_order_is_closed(order) {
3790 return;
3791 }
3792
3793 self.account_ids.insert(order.trader_id(), account_id);
3794
3795 if let Err(e) = self.ids_generator.get_venue_order_id(order) {
3796 log::error!(
3797 "Failed to resolve venue order ID restoring {}: {e}",
3798 order.client_order_id(),
3799 );
3800 return;
3801 }
3802
3803 if let Some(mut cached) = self.cache.borrow_mut().order_mut(&order.client_order_id())
3807 && !matches!(
3808 cached.liquidity_side(),
3809 Some(LiquiditySide::Maker | LiquiditySide::Taker)
3810 )
3811 {
3812 cached.set_liquidity_side(LiquiditySide::Maker);
3813 }
3814
3815 let filled_qty = order.filled_qty();
3816 if !filled_qty.is_zero() {
3817 self.orders
3818 .cached_filled_qty
3819 .borrow_mut()
3820 .insert(order.client_order_id(), filled_qty);
3821 }
3822
3823 let match_info = Self::matching_core_entry(order);
3824 self.track_post_match_order(order);
3825 self.core.add_order(match_info);
3826 }
3827
3828 pub fn process_modify(&mut self, command: &ModifyOrder, account_id: AccountId) {
3830 if !self.core.order_exists(command.client_order_id) {
3831 self.generate_order_modify_rejected(
3832 command.trader_id,
3833 command.strategy_id,
3834 command.instrument_id,
3835 command.client_order_id,
3836 Ustr::from(format!("Order {} not found", command.client_order_id).as_str()),
3837 command.venue_order_id,
3838 Some(account_id),
3839 );
3840 return;
3841 }
3842
3843 let order = match self.order_snapshot(command.client_order_id) {
3844 Some(order) => order,
3845 None => {
3846 log::error!(
3847 "Cannot modify order: order {} not found in cache",
3848 command.client_order_id
3849 );
3850 return;
3851 }
3852 };
3853
3854 let update_success = self.update_order(
3855 &order,
3856 command.quantity,
3857 command.price,
3858 command.trigger_price,
3859 None,
3860 );
3861
3862 if !update_success {
3863 return;
3864 }
3865
3866 if !self.core.order_exists(command.client_order_id) {
3867 return;
3868 }
3869
3870 let Some(refreshed) = self.resync_core_entry(command.client_order_id) else {
3871 return;
3872 };
3873
3874 let price_changed = refreshed.price() != order.price()
3876 || refreshed.trigger_price() != order.trigger_price();
3877 if price_changed
3878 && refreshed.is_open()
3879 && self.config.queue_position
3880 && let Some(new_price) = refreshed.price()
3881 {
3882 self.snapshot_queue_position(&refreshed, new_price);
3883 self.queue_excess.swap_remove(&refreshed.client_order_id());
3884 }
3885 }
3886
3887 pub fn process_cancel(&mut self, command: &CancelOrder, account_id: AccountId) {
3889 if !self.core.order_exists(command.client_order_id)
3891 && !self
3892 .orders
3893 .pending_oto_order_ids
3894 .borrow()
3895 .contains(&command.client_order_id)
3896 {
3897 let order = self
3900 .order_snapshot(command.client_order_id)
3901 .filter(|order| {
3902 order.account_id() == Some(account_id)
3903 && (order.is_inflight() || order.is_open())
3904 && !self.inflight_orders.contains(order.client_order_id())
3905 });
3906
3907 if let Some(order) = order {
3908 self.cancel_order(&order, None);
3909 return;
3910 }
3911
3912 self.generate_order_cancel_rejected(
3913 command.trader_id,
3914 command.strategy_id,
3915 account_id,
3916 command.instrument_id,
3917 command.client_order_id,
3918 command.venue_order_id,
3919 Ustr::from(format!("Order {} not found", command.client_order_id).as_str()),
3920 );
3921 return;
3922 }
3923
3924 let mut order = match self.order_snapshot(command.client_order_id) {
3925 Some(order) => order,
3926 None => {
3927 log::error!(
3928 "Cannot cancel order: order {} not found in cache",
3929 command.client_order_id
3930 );
3931 return;
3932 }
3933 };
3934
3935 if let Err(e) = self.apply_deferred_submission(&mut order, false) {
3937 self.generate_order_cancel_rejected(
3938 command.trader_id,
3939 command.strategy_id,
3940 account_id,
3941 command.instrument_id,
3942 command.client_order_id,
3943 command.venue_order_id,
3944 Ustr::from(e.to_string().as_str()),
3945 );
3946 return;
3947 }
3948
3949 if !order.is_inflight() && !order.is_open() {
3950 self.purge_stale_core_entry(command.client_order_id);
3951 return;
3952 }
3953
3954 self.cancel_order(&order, None);
3955 }
3956
3957 pub fn process_cancel_all(&mut self, command: &CancelAllOrders, account_id: AccountId) {
3961 self.process_cancel_all_excluding(command, account_id, &[]);
3962 }
3963
3964 pub fn process_cancel_all_excluding(
3967 &mut self,
3968 command: &CancelAllOrders,
3969 account_id: AccountId,
3970 excluded: &[ClientOrderId],
3971 ) {
3972 let instrument_id = command.instrument_id;
3973 let order_side = command.order_side;
3974
3975 let mut client_order_ids: Vec<ClientOrderId> = {
3976 let cache = self.cache.borrow();
3977 cache
3978 .orders_open_refs(
3979 None,
3980 Some(&instrument_id),
3981 None,
3982 Some(&account_id),
3983 order_side,
3984 )
3985 .into_iter()
3986 .chain(cache.orders_inflight_refs(
3987 None,
3988 Some(&instrument_id),
3989 None,
3990 Some(&account_id),
3991 order_side,
3992 ))
3993 .map(|order| order.client_order_id())
3994 .filter(|client_order_id| !excluded.contains(client_order_id))
3995 .collect()
3996 };
3997
3998 client_order_ids.sort_unstable();
3999 client_order_ids.dedup();
4000
4001 for client_order_id in client_order_ids {
4002 let order = match self
4003 .cache
4004 .borrow()
4005 .order(&client_order_id)
4006 .map(|o| o.clone())
4007 {
4008 Some(order) => order,
4009 None => continue,
4010 };
4011
4012 if !order.is_inflight() && !order.is_open() {
4013 self.purge_stale_core_entry(client_order_id);
4014 continue;
4015 }
4016
4017 self.cancel_order_excluding(&order, None, excluded);
4018 }
4019 }
4020
4021 fn purge_stale_core_entry(&mut self, client_order_id: ClientOrderId) {
4024 if self.core.order_exists(client_order_id) {
4025 self.delete_core_order(client_order_id);
4026 }
4027
4028 self.remove_queue_position(client_order_id);
4029 self.orders
4030 .cached_filled_qty
4031 .borrow_mut()
4032 .swap_remove(&client_order_id);
4033 }
4034
4035 fn resync_core_entry(&mut self, client_order_id: ClientOrderId) -> Option<OrderAny> {
4036 let order = self.order_snapshot(client_order_id)?;
4037
4038 if order.is_closed() {
4041 self.delete_core_order(client_order_id);
4042 self.remove_queue_position(client_order_id);
4043 return Some(order);
4044 }
4045
4046 let new_match_info = Self::matching_core_entry(&order);
4047
4048 let unchanged = self
4050 .core
4051 .get_order(client_order_id)
4052 .is_some_and(|existing| *existing == new_match_info);
4053
4054 if unchanged {
4055 self.track_post_match_order(&order);
4056 return Some(order);
4057 }
4058
4059 self.delete_core_order(client_order_id);
4060 self.track_post_match_order(&order);
4061 self.core.add_order(new_match_info);
4062 Some(order)
4063 }
4064
4065 fn order_snapshot(&self, client_order_id: ClientOrderId) -> Option<OrderAny> {
4066 let mut order = self.cache.borrow().order(&client_order_id)?.clone();
4067 let mut pending = self.orders.pending_updates.borrow_mut();
4068 if order.is_closed() {
4069 pending.swap_remove(&client_order_id);
4070 return Some(order);
4071 }
4072
4073 if let Some(updates) = pending.get_mut(&client_order_id) {
4074 Self::retain_unapplied_updates(&order, updates);
4075 for update in updates.iter() {
4076 if let Err(e) = order.apply(OrderEventAny::Updated(*update)) {
4077 log::error!("Cannot apply pending update for {client_order_id}: {e}");
4078 return None;
4079 }
4080 }
4081
4082 if updates.is_empty() {
4083 pending.swap_remove(&client_order_id);
4084 }
4085 }
4086
4087 if let Some(filled_qty) = self
4088 .orders
4089 .cached_filled_qty
4090 .borrow()
4091 .get(&client_order_id)
4092 .copied()
4093 {
4094 write_filled_qty(&mut order, filled_qty);
4095 order.set_leaves_qty(order.quantity().saturating_sub(filled_qty));
4096 }
4097
4098 Some(order)
4099 }
4100
4101 fn purge_applied_updates(&self) {
4102 let cache = self.cache.borrow();
4103 self.orders
4104 .pending_updates
4105 .borrow_mut()
4106 .retain(|id, updates| {
4107 let Some(order) = cache.order(id) else {
4108 return false;
4109 };
4110
4111 Self::retain_unapplied_updates(&order, updates);
4112 !updates.is_empty()
4113 });
4114 }
4115
4116 fn purge_applied_rejections(&self) {
4117 let cache = self.cache.borrow();
4118 self.orders
4119 .rejected_oto_parent_ids
4120 .borrow_mut()
4121 .retain(|id| {
4122 cache
4123 .order(id)
4124 .is_some_and(|order| order.status() != OrderStatus::Rejected)
4125 });
4126 }
4127
4128 fn purge_applied_oto_terminals(&self) {
4129 let cache = self.cache.borrow();
4130 self.orders
4131 .closed_oto_order_ids
4132 .borrow_mut()
4133 .retain(|id| cache.order(id).is_some_and(|order| !order.is_closed()));
4134 }
4135
4136 fn retain_unapplied_updates(order: &OrderAny, updates: &mut Vec<OrderUpdated>) {
4137 if order.is_closed() {
4138 updates.clear();
4139 return;
4140 }
4141
4142 let events = order.events();
4143 updates.retain(|update| {
4144 !events.iter().any(|event| {
4145 matches!(event, OrderEventAny::Updated(applied) if applied.event_id == update.event_id)
4146 })
4147 });
4148 }
4149
4150 pub fn process_batch_cancel(&mut self, command: &BatchCancelOrders, account_id: AccountId) {
4152 for order in &command.cancels {
4153 self.process_cancel(order, account_id);
4154 }
4155 }
4156
4157 pub fn process_batch_modify(&mut self, command: &BatchModifyOrders, account_id: AccountId) {
4159 for order in &command.modifies {
4160 self.process_modify(order, account_id);
4161 }
4162 }
4163
4164 fn process_market_order(&mut self, order: &OrderAny) {
4165 if order.time_in_force() == TimeInForce::AtTheOpen
4166 || order.time_in_force() == TimeInForce::AtTheClose
4167 {
4168 self.generate_order_rejected(
4169 order,
4170 format!(
4171 "time in force {} is not currently supported",
4172 order.time_in_force()
4173 )
4174 .into(),
4175 );
4176 return;
4177 }
4178
4179 if (order.order_side() == OrderSide::Buy && self.core.ask.is_none())
4181 || (order.order_side() == OrderSide::Sell && self.core.bid.is_none())
4182 {
4183 self.generate_order_rejected(
4184 order,
4185 format!("No market for {}", order.instrument_id()).into(),
4186 );
4187 return;
4188 }
4189
4190 if self.config.use_market_order_acks {
4191 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
4192 self.generate_order_accepted(order, venue_order_id);
4193 }
4194
4195 self.fill_market_order(order.client_order_id());
4196 }
4197
4198 fn process_limit_order(&mut self, order: &mut OrderAny) {
4199 if order.time_in_force() == TimeInForce::AtTheOpen
4200 || order.time_in_force() == TimeInForce::AtTheClose
4201 {
4202 self.generate_order_rejected(
4203 order,
4204 format!(
4205 "time in force {} is not currently supported",
4206 order.time_in_force()
4207 )
4208 .into(),
4209 );
4210 return;
4211 }
4212
4213 let limit_px = order.price().expect("Limit order must have a price");
4214 if order.is_post_only() && self.core.is_limit_matched(order.order_side(), limit_px) {
4215 self.generate_order_rejected(
4216 order,
4217 format!(
4218 "POST_ONLY {} {} order limit px of {} would have been a TAKER: bid={}, ask={}",
4219 order.order_type(),
4220 order.order_side(),
4221 order.price().unwrap(),
4222 self.core
4223 .bid
4224 .map_or_else(|| "None".to_string(), |p| p.to_string()),
4225 self.core
4226 .ask
4227 .map_or_else(|| "None".to_string(), |p| p.to_string())
4228 )
4229 .into(),
4230 );
4231 return;
4232 }
4233
4234 self.accept_order(order);
4236
4237 if self.core.is_limit_matched(order.order_side(), limit_px) {
4239 order.set_liquidity_side(LiquiditySide::Taker);
4241 self.fill_limit_order_with_snapshot(order.clone());
4242
4243 if self.core.order_exists(order.client_order_id())
4246 && let Some(mut order) = self.cache.borrow_mut().order_mut(&order.client_order_id())
4247 {
4248 order.set_liquidity_side(LiquiditySide::Maker);
4249 }
4250 } else if matches!(order.time_in_force(), TimeInForce::Fok | TimeInForce::Ioc) {
4251 self.cancel_order(order, None);
4252 } else {
4253 if let Some(price) = order.price() {
4254 self.snapshot_queue_position(order, price);
4255 }
4256
4257 if let Some(mut order) = self.cache.borrow_mut().order_mut(&order.client_order_id())
4259 && !matches!(
4260 order.liquidity_side(),
4261 Some(LiquiditySide::Maker | LiquiditySide::Taker)
4262 )
4263 {
4264 order.set_liquidity_side(LiquiditySide::Maker);
4265 }
4266 }
4267 }
4268
4269 fn process_market_to_limit_order(&mut self, order: &OrderAny) {
4270 if (order.order_side() == OrderSide::Buy && self.core.ask.is_none())
4272 || (order.order_side() == OrderSide::Sell && self.core.bid.is_none())
4273 {
4274 self.generate_order_rejected(
4275 order,
4276 format!("No market for {}", order.instrument_id()).into(),
4277 );
4278 return;
4279 }
4280
4281 if self.config.use_market_order_acks {
4282 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
4283 self.generate_order_accepted(order, venue_order_id);
4284 }
4285
4286 let client_order_id = order.client_order_id();
4288 self.fill_market_order(client_order_id);
4289
4290 let filled_qty = self
4292 .orders
4293 .cached_filled_qty
4294 .borrow()
4295 .get(&client_order_id)
4296 .copied()
4297 .unwrap_or_default();
4298 let leaves_qty = order.quantity().saturating_sub(filled_qty);
4299 if leaves_qty.is_zero()
4300 || order.time_in_force() == TimeInForce::Fok
4301 || (order.time_in_force() == TimeInForce::Ioc && filled_qty.is_zero())
4302 {
4303 self.purge_cached_filled_qty_if_closed(client_order_id);
4304 return;
4305 }
4306
4307 if let Some(mut updated_order) = self.order_snapshot(client_order_id) {
4308 self.accept_order(&mut updated_order);
4309 }
4310 }
4311
4312 fn process_stop_market_order(&mut self, order: &mut OrderAny) {
4313 let stop_px = order
4314 .trigger_price()
4315 .expect("Stop order must have a trigger price");
4316
4317 if self.core.is_stop_matched_with_trigger_type(
4318 order.order_side(),
4319 stop_px,
4320 order.trigger_type().unwrap_or(TriggerType::Default),
4321 ) {
4322 if self.config.reject_stop_orders {
4323 self.generate_order_rejected(order, self.in_market_rejection(order, "stop"));
4324 return;
4325 }
4326
4327 self.fill_market_order(order.client_order_id());
4328 return;
4329 }
4330
4331 self.accept_order(order);
4333 }
4334
4335 fn process_stop_limit_order(&mut self, order: &mut OrderAny) {
4336 let stop_px = order
4337 .trigger_price()
4338 .expect("Stop order must have a trigger price");
4339
4340 if self.core.is_stop_matched_with_trigger_type(
4341 order.order_side(),
4342 stop_px,
4343 order.trigger_type().unwrap_or(TriggerType::Default),
4344 ) {
4345 if self.config.reject_stop_orders {
4346 self.generate_order_rejected(order, self.in_market_rejection(order, "stop"));
4347 return;
4348 }
4349
4350 self.accept_triggered_limit_style_order(order);
4351 return;
4352 }
4353
4354 self.accept_order(order);
4355 }
4356
4357 fn process_market_if_touched_order(&mut self, order: &mut OrderAny) {
4358 if self.core.is_touch_triggered_with_trigger_type(
4359 order.order_side(),
4360 order.trigger_price().unwrap(),
4361 order.trigger_type().unwrap_or(TriggerType::Default),
4362 ) {
4363 if self.config.reject_stop_orders {
4364 self.generate_order_rejected(order, self.in_market_rejection(order, "trigger"));
4365 return;
4366 }
4367
4368 self.fill_market_order(order.client_order_id());
4369 return;
4370 }
4371
4372 self.accept_order(order);
4374 }
4375
4376 fn process_limit_if_touched_order(&mut self, order: &mut OrderAny) {
4377 if self.core.is_touch_triggered_with_trigger_type(
4378 order.order_side(),
4379 order.trigger_price().unwrap(),
4380 order.trigger_type().unwrap_or(TriggerType::Default),
4381 ) {
4382 if self.config.reject_stop_orders {
4383 self.generate_order_rejected(order, self.in_market_rejection(order, "trigger"));
4384 return;
4385 }
4386
4387 self.accept_triggered_limit_style_order(order);
4388 return;
4389 }
4390
4391 self.accept_order(order);
4393 }
4394
4395 fn in_market_rejection(&self, order: &OrderAny, field: &str) -> Ustr {
4396 format!(
4397 "{} {} order {field} px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
4398 order.order_type(),
4399 order.order_side(),
4400 order.trigger_price().expect("Triggered order must have a trigger price"),
4401 self.core.bid.map_or_else(|| "None".to_string(), |p| p.to_string()),
4402 self.core.ask.map_or_else(|| "None".to_string(), |p| p.to_string()),
4403 )
4404 .into()
4405 }
4406
4407 fn accept_triggered_limit_style_order(&mut self, order: &mut OrderAny) {
4408 self.accept_order(order);
4409 self.trigger_limit_style_stop_order(order.client_order_id(), order.clone());
4410 if let Some(cached_order) = self
4411 .cache
4412 .borrow()
4413 .order(&order.client_order_id())
4414 .map(|order| order.clone())
4415 {
4416 *order = cached_order;
4417 }
4418 }
4419
4420 fn process_trailing_stop_order(&mut self, order: &mut OrderAny) {
4421 let side = order.order_side();
4422 let trigger_type = order.trigger_type().unwrap_or(TriggerType::Default);
4423
4424 if let Some(activation_price) = order.activation_price()
4425 && self.trailing_stop_activates_now(order)
4426 && self.config.reject_stop_orders
4427 {
4428 self.generate_order_rejected(
4429 order,
4430 format!(
4431 "{} {} order activation px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
4432 order.order_type(),
4433 side,
4434 activation_price,
4435 self.core
4436 .bid
4437 .map_or_else(|| "None".to_string(), |p| p.to_string()),
4438 self.core
4439 .ask
4440 .map_or_else(|| "None".to_string(), |p| p.to_string())
4441 )
4442 .into(),
4443 );
4444 return;
4445 }
4446
4447 if self.trailing_stop_activates_now(order)
4448 && let Some(trigger_price) = order.trigger_price()
4449 && self
4450 .core
4451 .is_stop_matched_with_trigger_type(side, trigger_price, trigger_type)
4452 {
4453 self.generate_order_rejected(
4454 order,
4455 format!(
4456 "{} {} order trigger px of {} was in the market: bid={}, ask={}, but rejected because of configuration",
4457 order.order_type(),
4458 side,
4459 trigger_price,
4460 self.core
4461 .bid
4462 .map_or_else(|| "None".to_string(), |p| p.to_string()),
4463 self.core
4464 .ask
4465 .map_or_else(|| "None".to_string(), |p| p.to_string())
4466 )
4467 .into(),
4468 );
4469 return;
4470 }
4471
4472 order.set_liquidity_side(LiquiditySide::Maker);
4476
4477 self.accept_order(order);
4478 }
4479
4480 fn trailing_stop_activates_now(&self, order: &OrderAny) -> bool {
4483 let side = order.order_side();
4484 match order.activation_price() {
4485 Some(activation_price) => self.core.is_touch_triggered_with_trigger_type(
4486 side,
4487 activation_price,
4488 if order.trigger_type() == Some(TriggerType::MarkPrice) {
4489 TriggerType::MarkPrice
4490 } else {
4491 TriggerType::BidAsk
4492 },
4493 ),
4494 None => self
4495 .get_trailing_activation_price(
4496 order.trigger_type().unwrap_or(TriggerType::Default),
4497 side,
4498 self.core.bid,
4499 self.core.ask,
4500 self.core.last,
4501 )
4502 .is_some(),
4503 }
4504 }
4505
4506 pub fn iterate(&mut self, timestamp_ns: UnixNanos, aggressor_side: AggressorSide) {
4513 self.iterate_with_mode(timestamp_ns, aggressor_side, OrderMatchMode::All);
4514 }
4515
4516 fn iterate_with_mode(
4517 &mut self,
4518 timestamp_ns: UnixNanos,
4519 aggressor_side: AggressorSide,
4520 match_mode: OrderMatchMode,
4521 ) {
4522 self.purge_closed_cached_filled_qty();
4524 self.purge_applied_updates();
4525 self.purge_applied_fills();
4526 self.purge_applied_rejections();
4527 self.purge_applied_oto_terminals();
4528
4529 if aggressor_side == AggressorSide::NoAggressor && self.last_trade_size.is_none() {
4535 if self.book_type == BookType::L1_MBP {
4536 if let Some(bid) = self.book.best_bid_price() {
4537 self.core.set_bid_raw(bid);
4538 }
4539
4540 if let Some(ask) = self.book.best_ask_price() {
4541 self.core.set_ask_raw(ask);
4542 }
4543 } else {
4544 self.core.bid = self.book.best_bid_price();
4548 self.core.ask = self.book.best_ask_price();
4549 }
4550 }
4551
4552 let mut matched_order = false;
4553
4554 if self.market_status == MarketStatus::Open {
4555 for action in self.core.iterate_bids() {
4558 if !self.should_process_match_action(action, match_mode) {
4559 continue;
4560 }
4561
4562 matched_order = true;
4563
4564 match action {
4565 MatchAction::FillLimit(id) => self.fill_resting_limit_order(id),
4566 MatchAction::TriggerStop(id) => self.trigger_stop_order(id),
4567 }
4568 }
4569
4570 for action in self.core.iterate_asks() {
4571 if !self.should_process_match_action(action, match_mode) {
4572 continue;
4573 }
4574
4575 matched_order = true;
4576
4577 match action {
4578 MatchAction::FillLimit(id) => self.fill_resting_limit_order(id),
4579 MatchAction::TriggerStop(id) => self.trigger_stop_order(id),
4580 }
4581 }
4582 }
4583
4584 let order_ids: Vec<ClientOrderId> = if matched_order {
4585 self.core.iter_orders().map(|m| m.client_order_id).collect()
4586 } else if self.post_match_order_ids.is_empty() {
4587 Vec::new()
4588 } else {
4589 self.core
4590 .iter_orders()
4591 .filter_map(|order| {
4592 self.post_match_order_ids
4593 .contains(&order.client_order_id)
4594 .then_some(order.client_order_id)
4595 })
4596 .collect()
4597 };
4598
4599 let support_gtd_orders = self.config.support_gtd_orders;
4600
4601 for client_order_id in order_ids {
4602 let (action, keep_tracking) = {
4603 let cache = self.cache.borrow();
4604
4605 let Some(order) = cache.order(&client_order_id) else {
4606 self.post_match_order_ids.swap_remove(&client_order_id);
4607 continue;
4608 };
4609
4610 (
4611 post_match_order_action(&order, support_gtd_orders, timestamp_ns, |order| {
4612 self.order_snapshot(client_order_id)
4613 .unwrap_or_else(|| order.clone())
4614 }),
4615 Self::requires_post_match_maintenance(&order),
4616 )
4617 };
4618
4619 match action {
4620 PostMatchOrderAction::RemoveClosed => {
4621 self.delete_core_order(client_order_id);
4622 self.remove_queue_position(client_order_id);
4623 self.orders
4624 .cached_filled_qty
4625 .borrow_mut()
4626 .swap_remove(&client_order_id);
4627 continue;
4628 }
4629 PostMatchOrderAction::Expire(order) => {
4630 self.delete_core_order(client_order_id);
4631 self.expire_order(&order);
4632 self.purge_cached_filled_qty_if_closed(client_order_id);
4633 continue;
4634 }
4635 PostMatchOrderAction::UpdateTrailing(mut order) => {
4636 if self.maybe_activate_trailing_stop(
4637 &mut order,
4638 self.core.bid,
4639 self.core.ask,
4640 self.core.last,
4641 ) {
4642 self.update_trailing_stop_order(&order);
4643 self.resync_core_entry(client_order_id);
4644 }
4645 }
4646 PostMatchOrderAction::NoMaintenance => {
4647 if !keep_tracking {
4648 self.post_match_order_ids.swap_remove(&client_order_id);
4649 }
4650 }
4651 }
4652
4653 if self.target_bid.is_some() || self.target_ask.is_some() || self.target_last.is_some()
4656 {
4657 if let Some(t) = self.target_bid.take() {
4658 self.core.bid = Some(t);
4659 }
4660
4661 if let Some(t) = self.target_ask.take() {
4662 self.core.ask = Some(t);
4663 }
4664
4665 if let Some(t) = self.target_last.take() {
4666 self.core.last = Some(t);
4667 }
4668 }
4669 }
4670
4671 if let Some(t) = self.target_bid.take() {
4675 self.core.bid = Some(t);
4676 }
4677
4678 if let Some(t) = self.target_ask.take() {
4679 self.core.ask = Some(t);
4680 }
4681
4682 if let Some(t) = self.target_last.take() {
4683 self.core.last = Some(t);
4684 }
4685
4686 self.core.bid = self.book.best_bid_price();
4689 self.core.ask = self.book.best_ask_price();
4690
4691 self.check_instrument_expiration(timestamp_ns, self.config.defer_option_settlement, &[]);
4694 self.purge_closed_cached_filled_qty();
4695 self.purge_applied_updates();
4696 self.purge_applied_fills();
4697 self.purge_applied_rejections();
4698 self.purge_applied_oto_terminals();
4699 }
4700
4701 fn fill_resting_limit_order(&mut self, client_order_id: ClientOrderId) {
4702 if self
4704 .core
4705 .get_order(client_order_id)
4706 .is_some_and(|order| order.order_type == OrderType::MarketToLimit)
4707 && let Some(mut order) = self.cache.borrow_mut().order_mut(&client_order_id)
4708 {
4709 order.set_liquidity_side(LiquiditySide::Maker);
4710 }
4711
4712 self.fill_limit_order(client_order_id);
4713 }
4714
4715 fn should_process_match_action(&self, action: MatchAction, match_mode: OrderMatchMode) -> bool {
4716 let client_order_id = match action {
4717 MatchAction::FillLimit(id) | MatchAction::TriggerStop(id) => id,
4718 };
4719
4720 if !self.core.order_exists(client_order_id) {
4721 return false;
4722 }
4723
4724 match match_mode {
4725 OrderMatchMode::All => true,
4726 OrderMatchMode::StopTriggers(trigger_type) => match action {
4727 MatchAction::TriggerStop(client_order_id) => self
4728 .core
4729 .get_order(client_order_id)
4730 .is_some_and(|order| order.trigger_type == Some(trigger_type)),
4731 MatchAction::FillLimit(_) => false,
4732 },
4733 }
4734 }
4735
4736 fn get_trailing_activation_price(
4737 &self,
4738 trigger_type: TriggerType,
4739 order_side: OrderSide,
4740 bid: Option<Price>,
4741 ask: Option<Price>,
4742 last: Option<Price>,
4743 ) -> Option<Price> {
4744 match trigger_type {
4745 TriggerType::LastPrice => last,
4746 TriggerType::MarkPrice => self.core.mark,
4747 TriggerType::LastOrBidAsk => last.or(match order_side {
4748 OrderSide::Buy => ask,
4749 OrderSide::Sell => bid,
4750 }),
4751
4752 _ => match order_side {
4754 OrderSide::Buy => ask,
4755 OrderSide::Sell => bid,
4756 },
4757 }
4758 }
4759
4760 fn maybe_activate_trailing_stop(
4761 &self,
4762 order: &mut OrderAny,
4763 bid: Option<Price>,
4764 ask: Option<Price>,
4765 last: Option<Price>,
4766 ) -> bool {
4767 let is_activated = match order {
4768 OrderAny::TrailingStopMarket(inner) => inner.is_activated,
4769 OrderAny::TrailingStopLimit(inner) => inner.is_activated,
4770 _ => return true,
4771 };
4772
4773 if is_activated {
4774 return true;
4775 }
4776
4777 let (bid, ask) = if order.trigger_type() == Some(TriggerType::MarkPrice) {
4778 (self.core.mark, self.core.mark)
4779 } else {
4780 (bid, ask)
4781 };
4782
4783 let activation_price = match order.activation_price() {
4784 Some(price) => {
4785 let hit = match order.order_side() {
4786 OrderSide::Buy => ask.is_some_and(|ask| ask <= price),
4787 OrderSide::Sell => bid.is_some_and(|bid| bid >= price),
4788 };
4789
4790 hit.then_some(price)
4791 }
4792 None => self.get_trailing_activation_price(
4793 order
4794 .trigger_type()
4795 .expect("Trailing order has a trigger type"),
4796 order.order_side(),
4797 bid,
4798 ask,
4799 last,
4800 ),
4801 };
4802
4803 let Some(activation_price) = activation_price else {
4804 return false;
4805 };
4806
4807 match order {
4808 OrderAny::TrailingStopMarket(inner) => {
4809 inner.activation_price = Some(activation_price);
4810 inner.set_activated();
4811 }
4812 OrderAny::TrailingStopLimit(inner) => {
4813 inner.activation_price = Some(activation_price);
4814 inner.set_activated();
4815 }
4816 _ => unreachable!("only trailing orders require activation"),
4817 }
4818
4819 store_trailing_stop_activation(&mut self.cache.borrow_mut(), order);
4820 true
4821 }
4822
4823 fn determine_limit_price_and_volume(
4824 &mut self,
4825 order: &OrderAny,
4826 leaves_qty: Quantity,
4827 journal: Option<&mut ConsumptionJournal>,
4828 ) -> Vec<(Price, Quantity)> {
4829 match order.price() {
4830 Some(order_price) => {
4831 let mut fills = if self.config.liquidity_consumption {
4836 let size_prec = self.instrument.size_precision();
4837 self.book
4838 .get_all_crossed_levels(order.order_side(), order_price, size_prec)
4839 } else {
4840 let book_order =
4841 BookOrder::new(order.order_side(), order_price, order.quantity(), 1);
4842 self.book.simulate_fills(&book_order)
4843 };
4844
4845 if let Some(trade_size) = self.last_trade_size
4847 && let Some(trade_price) = self.core.last
4848 {
4849 let fills_at_trade_price = fills.iter().any(|(px, _)| *px == trade_price);
4850
4851 if (self.book_type == BookType::L1_MBP || !fills_at_trade_price)
4852 && self.core.is_limit_matched(order.order_side(), order_price)
4853 {
4854 let available_qty = if self.config.liquidity_consumption {
4857 let remaining = trade_size.raw().saturating_sub(self.trade_consumption);
4858 Quantity::from_raw(remaining, trade_size.precision)
4859 } else {
4860 trade_size
4861 };
4862
4863 let fill_qty = min(leaves_qty, available_qty);
4864
4865 if fill_qty.non_zero() {
4866 let fill_price = if self.book_type == BookType::L1_MBP
4867 && fills_at_trade_price
4868 && order.liquidity_side() == Some(LiquiditySide::Taker)
4869 {
4870 trade_price
4871 } else {
4872 order_price
4873 };
4874
4875 log::debug!(
4876 "Trade execution fill: {} @ {} (trade_price={}, available: {}, book had {} fills)",
4877 fill_qty,
4878 fill_price,
4879 trade_price,
4880 available_qty,
4881 fills.len()
4882 );
4883
4884 if self.config.liquidity_consumption {
4885 self.trade_consumption += fill_qty.raw();
4886 }
4887
4888 return vec![(fill_price, fill_qty)];
4891 }
4892
4893 if self.book_type == BookType::L1_MBP {
4894 return Vec::new();
4895 }
4896 }
4897 }
4898
4899 if fills.is_empty() {
4901 return fills;
4902 }
4903
4904 let book_prices: Vec<Price> = if self.config.liquidity_consumption {
4908 fills.iter().map(|(px, _)| *px).collect()
4909 } else {
4910 Vec::new()
4911 };
4912
4913 let book_prices_ref: Option<&[Price]> = if book_prices.is_empty() {
4914 None
4915 } else {
4916 Some(&book_prices)
4917 };
4918
4919 if order
4921 .liquidity_side()
4922 .is_some_and(|liquidity_side| liquidity_side == LiquiditySide::Maker)
4923 {
4924 let trigger_price = order
4925 .trigger_price()
4926 .filter(|_| order.trigger_type() != Some(TriggerType::MarkPrice));
4927 match order.order_side() {
4928 OrderSide::Buy => {
4929 let target_price = if trigger_price
4930 .is_some_and(|trigger_price| order_price > trigger_price)
4931 {
4932 trigger_price.unwrap()
4933 } else {
4934 order_price
4935 };
4936
4937 for fill in &mut fills {
4938 let last_px = fill.0;
4939 if last_px < order_price {
4940 self.target_bid = self.core.bid;
4942 self.target_ask = self.core.ask;
4943 self.target_last = self.core.last;
4944 self.core.set_ask_raw(target_price);
4945 self.core.set_last_raw(target_price);
4946 fill.0 = target_price;
4947 }
4948 }
4949 }
4950 OrderSide::Sell => {
4951 let target_price = if trigger_price
4952 .is_some_and(|trigger_price| order_price < trigger_price)
4953 {
4954 trigger_price.unwrap()
4955 } else {
4956 order_price
4957 };
4958
4959 for fill in &mut fills {
4960 let last_px = fill.0;
4961 if last_px > order_price {
4962 self.target_bid = self.core.bid;
4964 self.target_ask = self.core.ask;
4965 self.target_last = self.core.last;
4966 self.core.set_bid_raw(target_price);
4967 self.core.set_last_raw(target_price);
4968 fill.0 = target_price;
4969 }
4970 }
4971 }
4972 }
4973 }
4974
4975 self.apply_liquidity_consumption(
4976 fills,
4977 order,
4978 leaves_qty,
4979 book_prices_ref,
4980 self.l1_trade_size,
4981 journal,
4982 )
4983 }
4984 None => panic!("Limit order must have a price"),
4985 }
4986 }
4987
4988 fn determine_market_price_and_volume(&self, order: &OrderAny) -> Vec<(Price, Quantity)> {
4989 let price = match order.order_side() {
4990 OrderSide::Buy => Price::max(FIXED_PRECISION),
4991 OrderSide::Sell => Price::min(FIXED_PRECISION),
4992 };
4993
4994 let mut fills = if self.config.liquidity_consumption {
4997 let size_prec = self.instrument.size_precision();
4998 self.book
4999 .get_all_crossed_levels(order.order_side(), price, size_prec)
5000 } else {
5001 let book_order = BookOrder::new(order.order_side(), price, order.quantity(), 0);
5002 self.book.simulate_fills(&book_order)
5003 };
5004
5005 if !self.fill_at_market
5008 && order.trigger_type() != Some(TriggerType::MarkPrice)
5009 && self.book_type == BookType::L1_MBP
5010 && !fills.is_empty()
5011 && matches!(
5012 order.order_type(),
5013 OrderType::StopMarket | OrderType::TrailingStopMarket | OrderType::MarketIfTouched
5014 )
5015 && let Some(trigger_price) = order.trigger_price()
5016 {
5017 fills[0] = (trigger_price, fills[0].1);
5018
5019 let mut remaining_qty = order.leaves_qty();
5021 let mut capped_fills = Vec::with_capacity(fills.len());
5022
5023 for (price, qty) in fills {
5024 if remaining_qty.is_zero() {
5025 break;
5026 }
5027
5028 let mut capped_qty = qty.min(remaining_qty);
5029 capped_qty.precision = qty.precision;
5030 if capped_qty.is_zero() {
5031 continue;
5032 }
5033
5034 remaining_qty = remaining_qty - capped_qty;
5035 capped_fills.push((price, capped_qty));
5036 }
5037
5038 return capped_fills;
5039 }
5040
5041 fills
5042 }
5043
5044 fn determine_market_fill_model_price_and_volume(
5045 &mut self,
5046 order: &OrderAny,
5047 ) -> anyhow::Result<(Vec<(Price, Quantity)>, bool)> {
5048 if let Some(book) = self.fill_model.get_orderbook_for_fill_simulation(
5049 &self.instrument,
5050 order,
5051 self.core.bid,
5052 self.core.ask,
5053 )? {
5054 let price = match order.order_side() {
5055 OrderSide::Buy => Price::max(FIXED_PRECISION),
5056 OrderSide::Sell => Price::min(FIXED_PRECISION),
5057 };
5058
5059 let book_order = BookOrder::new(order.order_side(), price, order.quantity(), 0);
5060 return Ok((book.simulate_fills(&book_order), true));
5061 }
5062
5063 Ok((self.determine_market_price_and_volume(order), false))
5064 }
5065
5066 fn determine_limit_fill_model_price_and_volume(
5067 &mut self,
5068 order: &OrderAny,
5069 leaves_qty: Quantity,
5070 journal: Option<&mut ConsumptionJournal>,
5071 ) -> anyhow::Result<Vec<(Price, Quantity)>> {
5072 if let Some(book) = self.fill_model.get_orderbook_for_fill_simulation(
5073 &self.instrument,
5074 order,
5075 self.core.bid,
5076 self.core.ask,
5077 )? && let Some(limit_price) = order.price()
5078 {
5079 let book_order = BookOrder::new(order.order_side(), limit_price, order.quantity(), 0);
5080 return Ok(book.simulate_fills(&book_order));
5081 }
5082
5083 Ok(self.determine_limit_price_and_volume(order, leaves_qty, journal))
5084 }
5085
5086 pub fn fill_market_order(&mut self, client_order_id: ClientOrderId) {
5091 let mut order = match self.order_snapshot(client_order_id) {
5092 Some(order) => order,
5093 None => {
5094 log::error!("Cannot fill market order: order {client_order_id} not found in cache");
5095 return;
5096 }
5097 };
5098
5099 if !self.prepare_order_fill(&mut order) {
5100 return;
5101 }
5102
5103 if order.is_quote_quantity()
5107 && !self.instrument.is_inverse()
5108 && !self.convert_quote_to_base_quantity(&mut order)
5109 {
5110 return;
5111 }
5112
5113 if let Some(filled_qty) = self
5114 .orders
5115 .cached_filled_qty
5116 .borrow()
5117 .get(&order.client_order_id())
5118 .copied()
5119 && filled_qty >= order.quantity()
5120 {
5121 log::debug!(
5122 "Ignoring fill as already filled pending application of events: {:?}, {:?}, {:?}, {:?}",
5123 filled_qty,
5124 order.quantity(),
5125 order.filled_qty(),
5126 order.quantity()
5127 );
5128 return;
5129 }
5130
5131 let (venue_position_id, position) = self.fill_position_for_order(&order, Some(true));
5132
5133 let Some(leaves_qty) = self.fill_leaves_qty(&order, position.as_ref()) else {
5134 return;
5135 };
5136
5137 order.set_liquidity_side(LiquiditySide::Taker);
5138
5139 let (mut fills, from_synthetic) =
5140 match self.determine_market_fill_model_price_and_volume(&order) {
5141 Ok(result) => result,
5142 Err(e) => {
5143 log::error!(
5144 "Cannot fill market order {}: fill model failed: {e}",
5145 order.client_order_id()
5146 );
5147 return;
5148 }
5149 };
5150
5151 let protection_price: Option<Price> = if let Some(protection_points) =
5153 self.config.price_protection_points
5154 && matches!(
5155 order.order_type(),
5156 OrderType::Market | OrderType::StopMarket
5157 ) {
5158 protection_price_calculate(
5159 self.instrument.price_increment(),
5160 &order,
5161 protection_points,
5162 self.core.bid,
5163 self.core.ask,
5164 )
5165 .ok()
5166 } else {
5167 None
5168 };
5169
5170 if let Some(protection_price) = protection_price {
5171 fills = self.filter_fills_by_protection(fills, &order, protection_price);
5172 }
5173
5174 let is_trigger_price_fill = !self.fill_at_market
5177 && order.trigger_type() != Some(TriggerType::MarkPrice)
5178 && self.book_type == BookType::L1_MBP
5179 && matches!(
5180 order.order_type(),
5181 OrderType::StopMarket | OrderType::TrailingStopMarket | OrderType::MarketIfTouched
5182 )
5183 && order.trigger_price().is_some();
5184 if !from_synthetic && !is_trigger_price_fill {
5185 let mut journal = self.fok_consumption_journal(&order);
5186
5187 fills = self.apply_liquidity_consumption(
5188 fills,
5189 &order,
5190 leaves_qty,
5191 None,
5192 None,
5193 journal.as_mut(),
5194 );
5195
5196 if let Some(journal) = journal
5197 && self.is_fok_unfillable(&order, &fills, leaves_qty)
5198 {
5199 self.revert_consumption_journal(journal);
5200 }
5201 }
5202
5203 if let Err(e) = self.apply_fills(
5204 &order,
5205 &fills,
5206 LiquiditySide::Taker,
5207 if self.config.use_reduce_only && order.is_reduce_only() {
5208 venue_position_id
5209 } else {
5210 None
5211 },
5212 position.as_ref(),
5213 protection_price,
5214 from_synthetic,
5215 ) {
5216 log::error!("Cannot fill market order {}: {e}", order.client_order_id());
5217 }
5218 }
5219
5220 fn filter_fills_by_protection(
5221 &self,
5222 fills: Vec<(Price, Quantity)>,
5223 order: &OrderAny,
5224 protection_price: Price,
5225 ) -> Vec<(Price, Quantity)> {
5226 fills
5227 .into_iter()
5228 .filter(|(fill_price, _)| {
5229 match order.order_side() {
5230 OrderSide::Buy => *fill_price <= protection_price,
5232
5233 OrderSide::Sell => *fill_price >= protection_price,
5235 }
5236 })
5237 .collect()
5238 }
5239
5240 pub fn fill_limit_order(&mut self, client_order_id: ClientOrderId) {
5249 let order = match self.order_snapshot(client_order_id) {
5250 Some(order) => order,
5251 None => {
5252 log::error!("Cannot fill limit order: order {client_order_id} not found in cache");
5253 return;
5254 }
5255 };
5256
5257 self.fill_limit_order_with_snapshot(order);
5258 }
5259
5260 fn fill_limit_order_with_snapshot(&mut self, mut order: OrderAny) {
5261 if !self.prepare_order_fill(&mut order) {
5262 return;
5263 }
5264
5265 if order.is_quote_quantity()
5269 && !self.instrument.is_inverse()
5270 && !self.convert_quote_to_base_quantity(&mut order)
5271 {
5272 return;
5273 }
5274
5275 match order.price() {
5276 Some(order_price) => {
5277 let cached_filled_qty = self
5278 .orders
5279 .cached_filled_qty
5280 .borrow()
5281 .get(&order.client_order_id())
5282 .copied();
5283
5284 if let Some(qty) = cached_filled_qty
5285 && qty >= order.quantity()
5286 {
5287 log::debug!(
5288 "Ignoring fill as already filled pending application of events: {}, {}, {}, {}",
5289 qty,
5290 order.quantity(),
5291 order.filled_qty(),
5292 order.leaves_qty(),
5293 );
5294 return;
5295 }
5296
5297 if order
5299 .liquidity_side()
5300 .is_some_and(|liquidity_side| liquidity_side == LiquiditySide::Maker)
5301 {
5302 let at_limit = if self.last_trade_size.is_some() && self.core.last.is_some() {
5305 self.core.last.is_some_and(|last| last == order_price)
5306 } else if order.order_side() == OrderSide::Buy {
5307 self.core.bid.is_some_and(|bid| bid == order_price)
5308 } else {
5309 self.core.ask.is_some_and(|ask| ask == order_price)
5310 };
5311
5312 if at_limit {
5313 let is_limit_filled = match self.fill_model.is_limit_filled() {
5314 Ok(value) => value,
5315 Err(e) => {
5316 log::error!(
5317 "Cannot fill limit order {}: fill model failed: {e}",
5318 order.client_order_id()
5319 );
5320 return;
5321 }
5322 };
5323
5324 if !is_limit_filled {
5325 return; }
5327 }
5328 }
5329
5330 let queue_allowed_raw = if self.config.queue_position {
5331 match self.determine_trade_fill_qty(&order) {
5332 None | Some(0) => {
5333 if matches!(order.time_in_force(), TimeInForce::Fok | TimeInForce::Ioc)
5334 {
5335 self.cancel_order(&order, None);
5336 }
5337
5338 return;
5339 }
5340 Some(allowed) => Some(allowed),
5341 }
5342 } else {
5343 None
5344 };
5345
5346 let (venue_position_id, position) = self.fill_position_for_order(&order, None);
5347
5348 let Some(leaves_qty) = self.fill_leaves_qty(&order, position.as_ref()) else {
5349 return;
5350 };
5351
5352 let required_qty = leaves_qty;
5353 let leaves_qty = queue_allowed_raw.map_or(leaves_qty, |allowed| {
5354 Quantity::from_raw(leaves_qty.raw().min(allowed), leaves_qty.precision)
5355 });
5356
5357 let mut journal = self.fok_consumption_journal(&order);
5358 let tc_before = self.trade_consumption;
5359
5360 let mut fills = match self.determine_limit_fill_model_price_and_volume(
5361 &order,
5362 leaves_qty,
5363 journal.as_mut(),
5364 ) {
5365 Ok(fills) => fills,
5366 Err(e) => {
5367 log::error!(
5368 "Cannot fill limit order {}: fill model failed: {e}",
5369 order.client_order_id()
5370 );
5371 return;
5372 }
5373 };
5374
5375 if let Some(allowed_raw) = queue_allowed_raw {
5376 let size_prec = self.instrument.size_precision();
5377 let mut remaining = allowed_raw;
5378 fills = fills
5379 .into_iter()
5380 .filter_map(|(price, qty)| {
5381 if remaining == 0 {
5382 return None;
5383 }
5384
5385 let capped = qty.raw().min(remaining);
5386 remaining -= capped;
5387 Some((price, Quantity::from_raw(capped, size_prec)))
5388 })
5389 .collect();
5390
5391 let consumed: QuantityRaw = fills.iter().map(|(_, qty)| qty.raw()).sum();
5393 if let Some(excess) = self.queue_excess.get_mut(&order.client_order_id()) {
5394 *excess = excess.saturating_sub(consumed);
5395 }
5396
5397 self.trade_consumption = tc_before + consumed;
5398 }
5399
5400 if let Some(journal) = journal
5401 && self.is_fok_unfillable(&order, &fills, required_qty)
5402 {
5403 self.revert_consumption_journal(journal);
5404 }
5405
5406 if fills.is_empty() {
5407 log::debug!(
5408 "Skipping fill for {}: no liquidity available",
5409 order.client_order_id()
5410 );
5411
5412 if matches!(order.time_in_force(), TimeInForce::Fok | TimeInForce::Ioc) {
5413 self.cancel_order(&order, None);
5414 }
5415
5416 return;
5417 }
5418
5419 let liquidity_side = order.liquidity_side().unwrap();
5420
5421 if let Err(e) = self.apply_fills(
5422 &order,
5423 &fills,
5424 liquidity_side,
5425 venue_position_id,
5426 position.as_ref(),
5427 None,
5428 false,
5429 ) {
5430 log::error!("Cannot fill limit order {}: {e}", order.client_order_id());
5431 }
5432 }
5433 None => panic!("Limit order must have a price"),
5434 }
5435 }
5436
5437 fn fill_leaves_qty(
5438 &mut self,
5439 order: &OrderAny,
5440 position: Option<&Position>,
5441 ) -> Option<Quantity> {
5442 if !self.config.liquidity_consumption && position.is_some() {
5443 return Some(order.leaves_qty());
5444 }
5445
5446 let leaves_qty = order
5447 .quantity()
5448 .saturating_sub(self.engine_filled_qty(order));
5449
5450 match self.reduce_only_remaining(order, position) {
5451 Ok(Some(remaining)) if remaining.is_zero() => {
5452 log::warn!(
5453 "Canceling REDUCE_ONLY {} as would increase position",
5454 order.order_type()
5455 );
5456 self.cancel_order(order, None);
5457 None
5458 }
5459 Ok(Some(remaining)) => Some(leaves_qty.min(remaining)),
5460 Ok(None) => Some(leaves_qty),
5461 Err(e) => {
5462 log::error!(
5463 "Cannot determine fill quantity for {}: {e}",
5464 order.client_order_id()
5465 );
5466 None
5467 }
5468 }
5469 }
5470
5471 fn prepare_order_fill(&mut self, order: &mut OrderAny) -> bool {
5472 if order.is_closed() {
5473 self.purge_stale_core_entry(order.client_order_id());
5474 return false;
5475 }
5476
5477 self.resize_oto_order(order)
5478 }
5479
5480 fn fill_position_for_order(
5481 &mut self,
5482 order: &OrderAny,
5483 generate: Option<bool>,
5484 ) -> (Option<PositionId>, Option<Position>) {
5485 if self.restricts_short_selling()
5486 && self.oms_type == OmsType::Hedging
5487 && let Some(position) = self.position_for_order_in_cache(&self.cache.borrow(), order)
5488 {
5489 return (Some(position.id), Some(position));
5490 }
5491
5492 if self.restricts_short_selling()
5493 && self.oms_type == OmsType::Hedging
5494 && order.parent_order_id().is_some()
5495 {
5496 let cache = self.cache.borrow();
5497 return (Self::position_id_for_order(&cache, order), None);
5498 }
5499
5500 if self.oms_type == OmsType::Hedging
5501 && self.config.use_reduce_only
5502 && order.is_reduce_only()
5503 {
5504 let cache = self.cache.as_ref().borrow();
5505 if let Some(position_id) = Self::position_id_for_order(&cache, order) {
5506 let position = cache
5507 .position(&position_id)
5508 .map(|position| position.clone_without_events());
5509 return (Some(position_id), position);
5510 }
5511
5512 if self.config.support_contingent_orders && order.parent_order_id().is_some() {
5513 return (None, None);
5514 }
5515
5516 if let Some(position) = Self::open_position_reduced_by_order(&cache, order) {
5517 return (Some(position.id), Some(position));
5518 }
5519 }
5520
5521 let pending_position_id = if self.oms_type == OmsType::Hedging {
5522 self.pending_fills.values().find_map(|fill| {
5523 (fill.client_order_id == order.client_order_id())
5524 .then_some(fill.position_id)
5525 .flatten()
5526 })
5527 } else {
5528 None
5529 };
5530
5531 let venue_position_id =
5532 pending_position_id.or_else(|| self.ids_generator.get_position_id(order, generate));
5533
5534 let position = {
5535 let cache = self.cache.as_ref().borrow();
5536 venue_position_id
5537 .as_ref()
5538 .and_then(|position_id| cache.position(position_id))
5539 .map(|position| position.clone_without_events())
5540 };
5541
5542 (venue_position_id, position)
5543 }
5544
5545 fn position_for_order_in_cache(&self, cache: &Cache, order: &OrderAny) -> Option<Position> {
5546 if let Some(position_id) = Self::position_id_for_order(cache, order) {
5547 return cache
5548 .position(&position_id)
5549 .map(|position| position.clone_without_events());
5550 }
5551
5552 if self.oms_type == OmsType::Netting {
5553 let position_id = PositionId::new(
5554 format!("{}-{}", order.instrument_id(), order.strategy_id()).as_str(),
5555 );
5556 return cache
5557 .position(&position_id)
5558 .map(|position| position.clone_without_events());
5559 }
5560
5561 if self.oms_type == OmsType::Hedging
5562 && self.config.use_reduce_only
5563 && order.is_reduce_only()
5564 {
5565 if self.config.support_contingent_orders && order.parent_order_id().is_some() {
5566 return None;
5567 }
5568
5569 return Self::open_position_reduced_by_order(cache, order);
5570 }
5571
5572 None
5573 }
5574
5575 fn position_id_for_order(cache: &Cache, order: &OrderAny) -> Option<PositionId> {
5576 cache
5577 .position_id(&order.client_order_id())
5578 .copied()
5579 .or_else(|| {
5580 order
5581 .parent_order_id()
5582 .filter(|parent_id| {
5583 cache
5584 .order(parent_id)
5585 .is_some_and(|parent| parent.instrument_id() == order.instrument_id())
5586 })
5587 .and_then(|parent_id| cache.position_id(&parent_id).copied())
5588 })
5589 }
5590
5591 fn open_position_reduced_by_order(cache: &Cache, order: &OrderAny) -> Option<Position> {
5592 cache
5593 .positions_open(
5594 None,
5595 Some(&order.instrument_id()),
5596 Some(&order.strategy_id()),
5597 None,
5598 None,
5599 )
5600 .into_iter()
5601 .find(|position| order.would_reduce_only(position.side, position.quantity))
5602 .map(|position| position.clone_without_events())
5603 }
5604
5605 #[allow(
5606 clippy::too_many_arguments,
5607 reason = "fill application needs order, position, pricing, and liquidity-source context"
5608 )]
5609 fn apply_fills(
5610 &mut self,
5611 order: &OrderAny,
5612 fills: &[(Price, Quantity)],
5613 liquidity_side: LiquiditySide,
5614 venue_position_id: Option<PositionId>,
5615 position: Option<&Position>,
5616 protection_price: Option<Price>,
5617 from_synthetic: bool,
5618 ) -> anyhow::Result<()> {
5619 let mut reduce_only_remaining = self.reduce_only_remaining(order, position)?;
5620 let required_qty = reduce_only_remaining.map_or(order.leaves_qty(), |remaining| {
5621 order.leaves_qty().min(remaining)
5622 });
5623
5624 if self.is_fok_unfillable(order, fills, required_qty) {
5625 self.cancel_order(order, None);
5626 return Ok(());
5627 }
5628
5629 if fills.is_empty() {
5630 match (order.status(), order.time_in_force()) {
5631 (OrderStatus::Initialized | OrderStatus::Submitted, time_in_force)
5632 if time_in_force != TimeInForce::Ioc =>
5633 {
5634 self.generate_order_rejected(
5635 order,
5636 format!("No market for {}", order.instrument_id()).into(),
5637 );
5638 }
5639 _ => self.cancel_order(order, None),
5640 }
5641
5642 return Ok(());
5643 }
5644
5645 let venue_position_id = if self.oms_type == OmsType::Netting {
5647 None
5648 } else {
5649 venue_position_id
5650 };
5651
5652 let initial_market_to_limit =
5653 order.order_type() == OrderType::MarketToLimit && order.filled_qty().is_zero();
5654 let mut first_price = None;
5655 let mut total_filled = self.engine_filled_qty(order);
5656 let initial_total_filled = total_filled;
5657 let mut last_fill_px: Option<Price> = None;
5658 let mut reduce_only_filled = None;
5659
5660 if let Some(remaining) = reduce_only_remaining {
5661 if remaining.is_zero() {
5662 self.cancel_order(order, None);
5663 return Ok(());
5664 }
5665
5666 reduce_only_filled = Some(total_filled);
5667 }
5668
5669 for &(fill_px, fill_qty) in fills {
5670 let Some(mut fill_px) = self.normalize_fill_price(fill_px, order.client_order_id())
5671 else {
5672 continue;
5673 };
5674
5675 let Some(fill_qty) = self.normalize_fill_quantity(fill_qty, order.client_order_id())
5676 else {
5677 continue;
5678 };
5679
5680 if initial_market_to_limit {
5681 if fill_qty.is_zero() {
5682 continue;
5683 }
5684
5685 if first_price.is_some_and(|price| price != fill_px) {
5686 break;
5687 }
5688
5689 if first_price.is_none() {
5690 self.generate_order_updated(order, order.quantity(), Some(fill_px), None, None);
5691 first_price = Some(fill_px);
5692 }
5693 }
5694
5695 if self.book_type == BookType::L1_MBP
5696 && self.fill_model.is_slipped()?
5697 && let Some(slipped_px) = self.slipped_price(fill_px, order.order_side())
5698 {
5699 fill_px = slipped_px;
5700 }
5701
5702 let mut effective_fill_qty = fill_qty;
5703
5704 if let Some(remaining) = reduce_only_remaining {
5705 if remaining.is_zero() {
5706 return Ok(());
5707 }
5708
5709 if effective_fill_qty > remaining {
5710 let precision = effective_fill_qty.precision;
5711 effective_fill_qty = remaining;
5712 effective_fill_qty.precision = precision;
5713 }
5714 }
5715
5716 if fill_qty.is_zero() {
5717 if fills.len() == 1 && order.status() == OrderStatus::Submitted {
5718 self.generate_order_rejected(
5719 order,
5720 format!("No market for {}", order.instrument_id()).into(),
5721 );
5722 }
5723
5724 return Ok(());
5725 }
5726
5727 let capped_fill_qty = min(
5729 effective_fill_qty,
5730 order.quantity().saturating_sub(total_filled),
5731 );
5732 let reduce_only_exhausts_position =
5733 reduce_only_remaining.is_some_and(|remaining| capped_fill_qty >= remaining);
5734
5735 if reduce_only_exhausts_position {
5736 let mut reduce_only_target = reduce_only_filled
5737 .unwrap_or(initial_total_filled)
5738 .checked_add(capped_fill_qty)
5739 .expect("Overflow occurred when adding reduce-only target quantity");
5740 reduce_only_target.precision = self.instrument.size_precision();
5741 if order.quantity() != reduce_only_target {
5742 self.generate_order_updated(order, reduce_only_target, None, None, None);
5743 }
5744 }
5745
5746 total_filled = total_filled.add(capped_fill_qty);
5747 if let Some(remaining) = reduce_only_remaining.as_mut() {
5748 *remaining = *remaining - capped_fill_qty.min(*remaining);
5749 }
5750
5751 if let Some(filled) = reduce_only_filled.as_mut() {
5752 *filled = filled
5753 .checked_add(capped_fill_qty)
5754 .expect("Overflow occurred when adding reduce-only filled quantity");
5755 }
5756
5757 self.fill_order(
5758 order,
5759 fill_px,
5760 effective_fill_qty,
5761 liquidity_side,
5762 venue_position_id,
5763 position,
5764 )?;
5765 last_fill_px = Some(fill_px);
5766
5767 if initial_market_to_limit
5768 && (total_filled >= order.quantity() || reduce_only_exhausts_position)
5769 {
5770 return Ok(());
5771 }
5772
5773 if reduce_only_exhausts_position {
5774 self.purge_cached_filled_qty_if_closed(order.client_order_id());
5775 return Ok(());
5776 }
5777 }
5778
5779 let filled_in_loop = total_filled > initial_total_filled;
5780 if initial_market_to_limit && filled_in_loop {
5781 return Ok(());
5782 }
5783
5784 let leaves_remaining = total_filled < order.quantity();
5785
5786 if order.time_in_force() == TimeInForce::Ioc && leaves_remaining {
5787 self.cancel_order(order, None);
5788 return Ok(());
5789 }
5790
5791 if leaves_remaining
5794 && !from_synthetic
5795 && (order.is_open() || filled_in_loop)
5796 && self.book_type == BookType::L1_MBP
5797 && matches!(
5798 order.order_type(),
5799 OrderType::Market
5800 | OrderType::MarketIfTouched
5801 | OrderType::StopMarket
5802 | OrderType::TrailingStopMarket
5803 )
5804 {
5805 let Some(last_fill_px) = last_fill_px else {
5807 return Ok(());
5808 };
5809
5810 let side = order.order_side();
5811
5812 let Some(slip_fill_px) = self.slipped_price(last_fill_px, side) else {
5813 return Ok(());
5814 };
5815
5816 if let Some(protection_price) = protection_price {
5817 let exceeds_boundary = match side {
5818 OrderSide::Buy => slip_fill_px > protection_price,
5819 OrderSide::Sell => slip_fill_px < protection_price,
5820 };
5821
5822 if exceeds_boundary {
5823 return Ok(());
5824 }
5825 }
5826
5827 let mut leaves_qty = order.quantity().saturating_sub(total_filled);
5828
5829 if let Some(remaining) = reduce_only_remaining {
5830 if remaining.is_zero() {
5831 return Ok(());
5832 }
5833
5834 if leaves_qty > remaining {
5835 let precision = leaves_qty.precision;
5836 leaves_qty = remaining;
5837 leaves_qty.precision = precision;
5838 }
5839
5840 if leaves_qty >= remaining {
5841 let mut reduce_only_target = reduce_only_filled
5842 .unwrap_or(initial_total_filled)
5843 .checked_add(leaves_qty)
5844 .expect("Overflow occurred when adding reduce-only target quantity");
5845 reduce_only_target.precision = self.instrument.size_precision();
5846 if order.quantity() != reduce_only_target {
5847 self.generate_order_updated(order, reduce_only_target, None, None, None);
5848 }
5849 }
5850 }
5851
5852 if leaves_qty.is_zero() {
5853 return Ok(());
5854 }
5855
5856 self.fill_order(
5857 order,
5858 slip_fill_px,
5859 leaves_qty,
5860 liquidity_side,
5861 venue_position_id,
5862 position,
5863 )?;
5864 self.purge_cached_filled_qty_if_closed(order.client_order_id());
5865 }
5866
5867 Ok(())
5868 }
5869
5870 fn slipped_price(&self, price: Price, side: OrderSide) -> Option<Price> {
5872 let price_increment = self.instrument.price_increment();
5873 let slipped = match side {
5874 OrderSide::Buy => price.add(price_increment),
5875 OrderSide::Sell => price.sub(price_increment),
5876 };
5877
5878 is_price_tradable(&self.instrument, slipped).then_some(slipped)
5879 }
5880
5881 fn normalize_fill_price(
5882 &self,
5883 fill_px: Price,
5884 client_order_id: ClientOrderId,
5885 ) -> Option<Price> {
5886 let normalized = self.normalize_price_for_current_instrument(fill_px);
5887 if normalized.is_none() {
5888 log::warn!(
5889 "Skipping fill for {client_order_id}: fill price {fill_px} is not compatible \
5890 with {} price_precision={} price_increment={}",
5891 self.instrument.id(),
5892 self.instrument.price_precision(),
5893 self.instrument.price_increment()
5894 );
5895 }
5896
5897 normalized
5898 }
5899
5900 fn normalize_fill_quantity(
5901 &self,
5902 fill_qty: Quantity,
5903 client_order_id: ClientOrderId,
5904 ) -> Option<Quantity> {
5905 let normalized = self.normalize_quantity_for_current_instrument(fill_qty);
5906 if normalized.is_none() {
5907 log::warn!(
5908 "Skipping fill for {client_order_id}: fill quantity {fill_qty} is not compatible \
5909 with {} size_precision={}",
5910 self.instrument.id(),
5911 self.instrument.size_precision()
5912 );
5913 }
5914
5915 normalized
5916 }
5917
5918 fn reduce_only_remaining(
5919 &mut self,
5920 order: &OrderAny,
5921 position: Option<&Position>,
5922 ) -> anyhow::Result<Option<Quantity>> {
5923 if self.config.use_reduce_only && order.is_reduce_only() {
5924 return Ok(Some(self.position_quantity_remaining(order, position)?));
5925 }
5926
5927 Ok(None)
5928 }
5929
5930 fn position_quantity_remaining(
5931 &mut self,
5932 order: &OrderAny,
5933 position: Option<&Position>,
5934 ) -> anyhow::Result<Quantity> {
5935 let precision = self.instrument.size_precision();
5936
5937 self.purge_applied_fills();
5938 let quantity = self.position_quantity(&self.cache.borrow(), order, position)?;
5939
5940 if (order.is_buy() && quantity >= Decimal::ZERO)
5941 || (order.is_sell() && quantity <= Decimal::ZERO)
5942 {
5943 return Ok(Quantity::zero(precision));
5944 }
5945
5946 Ok(Quantity::from_decimal_dp(quantity.abs(), precision)?)
5947 }
5948
5949 fn position_quantity(
5950 &self,
5951 cache: &Cache,
5952 order: &OrderAny,
5953 position: Option<&Position>,
5954 ) -> anyhow::Result<Decimal> {
5955 let position_id = position
5956 .map(|position| position.id)
5957 .or_else(|| Self::position_id_for_order(cache, order))
5958 .or_else(|| {
5959 (self.oms_type == OmsType::Netting).then(|| {
5960 PositionId::new(format!("{}-{}", order.instrument_id(), order.strategy_id()))
5961 })
5962 });
5963
5964 let cached_position = position_id.as_ref().and_then(|id| cache.position(id));
5965 let position = position.or(cached_position.as_deref());
5966 let splits_virtual_position = self.restricts_short_selling()
5967 && self.oms_type == OmsType::Hedging
5968 && position_id.is_some_and(|id| id.is_virtual());
5969
5970 let mut quantity = position.map_or(Decimal::ZERO, |position| match position.side {
5971 PositionSide::Long => position.quantity.as_decimal(),
5972 PositionSide::Short => -position.quantity.as_decimal(),
5973 PositionSide::Flat => Decimal::ZERO,
5974 });
5975
5976 let mut flipped_orders = IndexSet::new();
5977
5978 for fill in self.pending_fills.values() {
5979 let matches_position = match position_id {
5980 Some(position_id) => fill.position_id == Some(position_id),
5981 None => order.parent_order_id().is_some_and(|parent_id| {
5982 fill.client_order_id == parent_id
5983 || cache
5984 .order(&fill.client_order_id)
5985 .is_some_and(|order| order.parent_order_id() == Some(parent_id))
5986 }),
5987 };
5988
5989 if !matches_position || flipped_orders.contains(&fill.client_order_id) {
5990 continue;
5991 }
5992
5993 let remaining = quantity
5994 .checked_add(fill.quantity_change)
5995 .ok_or_else(|| anyhow::anyhow!("Pending position quantity overflow"))?;
5996
5997 quantity = if splits_virtual_position
5998 && !quantity.is_zero()
5999 && !remaining.is_zero()
6000 && quantity.is_sign_negative() != remaining.is_sign_negative()
6001 {
6002 flipped_orders.insert(fill.client_order_id);
6003 Decimal::ZERO
6004 } else {
6005 remaining
6006 };
6007 }
6008
6009 Ok(quantity)
6010 }
6011
6012 fn purge_applied_fills(&mut self) {
6013 let cache = self.cache.borrow();
6014 let cash_cache = self.restricts_short_selling().then_some(&*cache);
6015 self.pending_fills.retain(|trade_id, fill| {
6016 let previous_position_id = fill.position_id;
6017 fill.position_id = cache
6018 .position_id(&fill.client_order_id)
6019 .copied()
6020 .or_else(|| {
6021 fill.position_id
6022 .filter(|id| cache.position(id).is_some())
6023 })
6024 .or_else(|| {
6025 let cache = cash_cache?;
6026 let order = cache.order(&fill.client_order_id)?;
6027 Self::position_id_for_order(cache, &order)
6028 })
6029 .or(fill.position_id);
6030
6031 let Some(position_id) = fill.position_id else {
6032 return cache.order_exists(&fill.client_order_id);
6033 };
6034
6035 let Some(position) = cache.position(&position_id) else {
6036 return cache.order_exists(&fill.client_order_id);
6037 };
6038
6039 if previous_position_id
6040 .and_then(|id| cache.position(&id))
6041 .iter()
6042 .chain(std::iter::once(&position))
6043 .any(|position| position.trade_ids.contains(trade_id))
6044 {
6045 return false;
6046 }
6047
6048 let opening_trade_id = position.events.first().map(|event| event.trade_id);
6049 if opening_trade_id != fill.opening_trade_id {
6050 if position.replay_events.iter().any(|event| {
6052 matches!(event, PositionReplayEvent::Filled(event) if event.trade_id == *trade_id)
6053 }) || cache.position_snapshots(Some(&position_id), None).iter()
6054 .any(|snapshot| snapshot.trade_ids.contains(trade_id))
6055 || cash_cache
6056 .and_then(|cache| cache.order(&fill.client_order_id))
6057 .filter(|order| position.opening_order_id == order.client_order_id())
6058 .is_some_and(|order| {
6059 order
6060 .trade_ids()
6061 .into_iter()
6062 .take_while(|id| Some(**id) != opening_trade_id)
6063 .any(|id| id == trade_id)
6064 })
6065 {
6066 return false;
6067 }
6068
6069 fill.opening_trade_id = opening_trade_id;
6070 }
6071
6072 true
6073 });
6074 }
6075
6076 fn fill_order(
6077 &mut self,
6078 order: &OrderAny,
6079 last_px: Price,
6080 last_qty: Quantity,
6081 liquidity_side: LiquiditySide,
6082 venue_position_id: Option<PositionId>,
6083 position: Option<&Position>,
6084 ) -> anyhow::Result<()> {
6085 self.check_size_precision(last_qty.precision, "fill quantity")?;
6086
6087 let (last_qty, new_filled_qty) = if let Some(filled_qty) = self
6088 .orders
6089 .cached_filled_qty
6090 .borrow()
6091 .get(&order.client_order_id())
6092 .copied()
6093 {
6094 let leaves_qty = order.quantity().saturating_sub(filled_qty);
6095 let last_qty = min(last_qty, leaves_qty);
6096 (last_qty, filled_qty + last_qty)
6097 } else {
6098 let last_qty = min(last_qty, order.quantity());
6099 (last_qty, last_qty)
6100 };
6101
6102 if last_qty.is_zero() {
6103 return Ok(());
6104 }
6105
6106 let fee_order;
6107
6108 let commission_order = {
6109 let mut cloned = order.clone();
6112 write_filled_qty(&mut cloned, new_filled_qty.saturating_sub(last_qty));
6113 if order.liquidity_side() != Some(liquidity_side) {
6114 cloned.set_liquidity_side(liquidity_side);
6115 }
6116
6117 fee_order = cloned;
6118 &fee_order
6119 };
6120
6121 let underlying_px = self.fee_underlying_price()?;
6122 let commission = self.fee_model.get_commission_with_context(
6123 commission_order,
6124 last_qty,
6125 last_px,
6126 &self.instrument,
6127 underlying_px,
6128 )?;
6129
6130 let mut reduce_only_order_ids = position
6132 .map(|position| self.reduce_only_order_ids(position.id))
6133 .unwrap_or_default();
6134
6135 self.orders
6136 .cached_filled_qty
6137 .borrow_mut()
6138 .insert(order.client_order_id(), new_filled_qty);
6139
6140 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
6141 self.generate_order_filled(
6142 order,
6143 venue_order_id,
6144 venue_position_id,
6145 last_qty,
6146 last_px,
6147 self.instrument.quote_currency(),
6148 commission,
6149 liquidity_side,
6150 );
6151
6152 let post_fill_filled_qty = self.engine_filled_qty(order);
6153
6154 let post_fill_quantity = self
6155 .orders
6156 .pending_updates
6157 .borrow()
6158 .get(&order.client_order_id())
6159 .and_then(|updates| updates.last())
6160 .map_or_else(
6161 || {
6162 self.cache
6163 .borrow()
6164 .order(&order.client_order_id())
6165 .map_or(order.quantity(), |order| order.quantity())
6166 },
6167 |update| update.quantity,
6168 );
6169
6170 let post_fill_leaves_qty = post_fill_quantity.saturating_sub(post_fill_filled_qty);
6171 let fully_filled = post_fill_leaves_qty.is_zero();
6172
6173 if order.is_closed() || fully_filled {
6174 self.delete_core_order(order.client_order_id());
6175 self.remove_queue_position(order.client_order_id());
6176
6177 if order.order_type() != OrderType::MarketToLimit {
6180 self.purge_cached_filled_qty_if_closed(order.client_order_id());
6181 }
6182 }
6183
6184 self.process_fill_contingencies(order, venue_position_id, post_fill_leaves_qty)?;
6185
6186 if let Some(position) = position {
6187 reduce_only_order_ids.extend(self.reduce_only_order_ids(position.id));
6188 }
6189
6190 reduce_only_order_ids.extend(self.reduce_only_linked_order_ids(
6191 order,
6192 venue_position_id.or(position.map(|position| position.id)),
6193 ));
6194 reduce_only_order_ids.sort_unstable();
6195 reduce_only_order_ids.dedup();
6196 self.sync_reduce_only_orders(order, position, &reduce_only_order_ids)?;
6197
6198 Ok(())
6199 }
6200
6201 fn process_fill_contingencies(
6202 &mut self,
6203 order: &OrderAny,
6204 position_id: Option<PositionId>,
6205 leaves_qty: Quantity,
6206 ) -> anyhow::Result<()> {
6207 if !self.config.support_contingent_orders {
6208 return Ok(());
6209 }
6210
6211 self.queue_oto_resize(order);
6212
6213 match order.contingency_type() {
6214 Some(ContingencyType::Oto) => {
6215 self.release_oto_children_after_fill(order, position_id, leaves_qty.is_zero())?;
6216 }
6217 Some(ContingencyType::Oco) => self.cancel_oco_after_fill(order)?,
6218 Some(ContingencyType::Ouo) => self.update_ouo_after_fill(order, leaves_qty)?,
6219 None => {}
6220 }
6221
6222 Ok(())
6223 }
6224
6225 fn queue_oto_resize(&self, order: &OrderAny) {
6226 let Some(parent) = self.full_trigger_oto_parent(order) else {
6227 return;
6228 };
6229
6230 let mut commands = self.orders.commands.borrow_mut();
6231 let index = commands
6232 .iter()
6233 .position(|command| !matches!(command, OtoCommand::Cancel { .. }))
6234 .unwrap_or(commands.len());
6235 commands.insert(
6236 index,
6237 OtoCommand::Resize {
6238 instrument_id: parent.instrument_id(),
6239 parent_order_id: parent.client_order_id(),
6240 quantity: parent.quantity(),
6241 },
6242 );
6243 }
6244
6245 fn release_oto_children_after_fill(
6246 &mut self,
6247 parent: &OrderAny,
6248 position_id: Option<PositionId>,
6249 fully_filled: bool,
6250 ) -> anyhow::Result<()> {
6251 let Some(ids) = parent.linked_order_ids() else {
6252 log::error!(
6253 "OTO order {} does not have linked orders",
6254 parent.client_order_id()
6255 );
6256 return Ok(());
6257 };
6258
6259 for id in ids {
6260 let mut child = self
6261 .order_snapshot(*id)
6262 .ok_or_else(|| anyhow::anyhow!("Order {id} not found in cache"))?;
6263 self.release_oto_child_after_fill(parent, &mut child, position_id, fully_filled)?;
6264 }
6265
6266 Ok(())
6267 }
6268
6269 fn release_oto_child_after_fill(
6270 &mut self,
6271 parent: &OrderAny,
6272 child: &mut OrderAny,
6273 position_id: Option<PositionId>,
6274 fully_filled: bool,
6275 ) -> anyhow::Result<()> {
6276 self.apply_deferred_submission(child, false)?;
6277 let id = child.client_order_id();
6278 if child.is_closed()
6279 || child.leaves_qty().is_zero()
6280 || child.is_active_local()
6281 || self.orders.closed_oto_order_ids.borrow().contains(&id)
6282 || self.inflight_orders.contains(id)
6283 {
6284 return Ok(());
6285 }
6286
6287 if child.instrument_id() == parent.instrument_id()
6288 && let (None, Some(position_id)) =
6289 (child.position_id(), parent.position_id().or(position_id))
6290 {
6291 self.cache
6292 .borrow_mut()
6293 .add_position_id(&position_id, &self.venue, &id, &child.strategy_id())
6294 .unwrap();
6295 log::debug!("Added position id {position_id} to cache for order {id}");
6296 }
6297
6298 if child.is_open()
6299 && !(matches!(child.status(), OrderStatus::PendingUpdate)
6300 && child
6301 .previous_status()
6302 .is_some_and(|s| matches!(s, OrderStatus::Submitted)))
6303 {
6304 return Ok(());
6305 }
6306
6307 let account_id = parent
6308 .account_id()
6309 .or_else(|| self.account_ids.get(&parent.trader_id()).copied())
6310 .ok_or_else(|| {
6311 anyhow::anyhow!("Account ID not found for trader {}", parent.trader_id())
6312 })?;
6313
6314 if !self.config.oto_full_trigger || fully_filled {
6315 self.release_oto_child(child, account_id, parent, None);
6316 }
6317
6318 Ok(())
6319 }
6320
6321 fn oto_submission_cancel(&self, order: &OrderAny) -> Option<OtoCommand> {
6322 let id = order.client_order_id();
6323
6324 if let Some(command) = self.orders.commands.borrow().iter().find(|command| {
6325 matches!(command, OtoCommand::Cancel { client_order_id, .. } if *client_order_id == id)
6326 }) {
6327 return Some(command.clone());
6328 }
6329
6330 let sibling_filled = order.contingency_type() == Some(ContingencyType::Oco)
6332 && order.linked_order_ids().is_some_and(|ids| {
6333 ids.iter().any(|id| {
6334 self.order_snapshot(*id)
6335 .is_some_and(|sibling| !self.engine_filled_qty(&sibling).is_zero())
6336 })
6337 });
6338
6339 sibling_filled.then(|| OtoCommand::Cancel {
6340 instrument_id: order.instrument_id(),
6341 client_order_id: id,
6342 cancel_contingencies: false,
6343 excluded: Vec::new(),
6344 })
6345 }
6346
6347 fn cancel_oco_after_fill(&mut self, order: &OrderAny) -> anyhow::Result<()> {
6348 let Some(ids) = order.linked_order_ids() else {
6349 log::error!(
6350 "OCO order {} does not have linked orders",
6351 order.client_order_id()
6352 );
6353 return Ok(());
6354 };
6355
6356 for id in ids {
6357 let child = self
6358 .order_snapshot(*id)
6359 .ok_or_else(|| anyhow::anyhow!("Order {id} not found in cache"))?;
6360
6361 if self.contingent_order_is_closed(&child) || child.is_active_local() {
6362 continue;
6363 }
6364
6365 self.cancel_order(&child, Some(false));
6366 }
6367
6368 Ok(())
6369 }
6370
6371 fn update_ouo_after_fill(
6372 &mut self,
6373 order: &OrderAny,
6374 leaves_qty: Quantity,
6375 ) -> anyhow::Result<()> {
6376 let Some(ids) = order.linked_order_ids() else {
6377 log::error!(
6378 "OUO order {} does not have linked orders",
6379 order.client_order_id()
6380 );
6381 return Ok(());
6382 };
6383
6384 let mut excluded = ids.to_vec();
6385 excluded.push(order.client_order_id());
6386
6387 for id in ids {
6388 let child = self
6389 .order_snapshot(*id)
6390 .ok_or_else(|| anyhow::anyhow!("Order {id} not found in cache"))?;
6391 self.update_ouo_child_after_fill(&child, leaves_qty, &excluded);
6392 }
6393
6394 Ok(())
6395 }
6396
6397 fn update_ouo_child_after_fill(
6398 &mut self,
6399 child: &OrderAny,
6400 leaves_qty: Quantity,
6401 excluded: &[ClientOrderId],
6402 ) {
6403 if child.is_active_local()
6404 || self.contingent_order_is_closed(child)
6405 || self.inflight_orders.contains(child.client_order_id())
6406 {
6407 return;
6408 }
6409
6410 let leaves_qty = self.quantity_for_order(child, leaves_qty);
6411 let child_filled_qty = self.engine_filled_qty(child);
6412 if leaves_qty.is_zero() && !child.is_closed() {
6413 self.cancel_order_excluding(child, None, excluded);
6414 } else if child_filled_qty >= leaves_qty {
6415 self.cancel_order(child, Some(false));
6416 } else if leaves_qty.non_zero() {
6417 self.update_contingent_quantity(child, leaves_qty, false);
6418 }
6419 }
6420
6421 fn reduce_only_linked_order_ids(
6422 &self,
6423 order: &OrderAny,
6424 position_id: Option<PositionId>,
6425 ) -> Vec<ClientOrderId> {
6426 if !self.config.support_contingent_orders || !self.config.use_reduce_only {
6427 return Vec::new();
6428 }
6429
6430 let Some(linked_order_ids) = order.linked_order_ids() else {
6431 return Vec::new();
6432 };
6433
6434 let cache = self.cache.borrow();
6435 let position_id = position_id
6436 .or_else(|| Self::position_id_for_order(&cache, order))
6437 .or_else(|| {
6438 self.position_for_order_in_cache(&cache, order)
6439 .map(|position| position.id)
6440 });
6441
6442 linked_order_ids
6443 .iter()
6444 .filter(|id| {
6445 cache.order(id).is_some_and(|linked| {
6446 linked.strategy_id() == order.strategy_id()
6447 && Self::position_id_for_order(&cache, &linked).or_else(|| {
6448 self.position_for_order_in_cache(&cache, &linked)
6449 .map(|position| position.id)
6450 }) == position_id
6451 })
6452 })
6453 .copied()
6454 .collect()
6455 }
6456
6457 fn reduce_only_order_ids(&self, position_id: PositionId) -> Vec<ClientOrderId> {
6458 if !self.config.use_reduce_only {
6459 return Vec::new();
6460 }
6461
6462 let cache = self.cache.borrow();
6463 let mut order_ids = Vec::new();
6464
6465 for resting in self.core.iter_orders() {
6466 let Some(order) = cache.order(&resting.client_order_id) else {
6467 continue;
6468 };
6469
6470 if !order.is_reduce_only() || !order.is_open() || !order.is_passive() {
6471 continue;
6472 }
6473
6474 let matches_position = match cache.position_id(&resting.client_order_id) {
6475 Some(id) => *id == position_id,
6476 None => self
6477 .position_for_order_in_cache(&cache, &order)
6478 .is_some_and(|position| position.id == position_id),
6479 };
6480
6481 if matches_position {
6482 order_ids.push(resting.client_order_id);
6483 }
6484 }
6485
6486 order_ids.sort_unstable();
6487 order_ids
6488 }
6489
6490 fn sync_reduce_only_orders(
6491 &mut self,
6492 filled_order: &OrderAny,
6493 position: Option<&Position>,
6494 order_ids: &[ClientOrderId],
6495 ) -> anyhow::Result<()> {
6496 for &client_order_id in order_ids {
6497 if client_order_id == filled_order.client_order_id()
6499 || !self.core.order_exists(client_order_id)
6500 {
6501 continue;
6502 }
6503
6504 let Some(mut order) = self.order_snapshot(client_order_id) else {
6505 continue;
6506 };
6507
6508 if !order.is_reduce_only() || order.is_closed() || !order.is_passive() {
6509 continue;
6510 }
6511
6512 let position = {
6515 let cache = self.cache.borrow();
6516 position.map_or_else(
6517 || self.position_for_order_in_cache(&cache, &order),
6518 |position| {
6519 Some(cache.position(&position.id).map_or_else(
6520 || position.clone_without_events(),
6521 |position| position.clone_without_events(),
6522 ))
6523 },
6524 )
6525 };
6526
6527 let remaining = self.position_quantity_remaining(&order, position.as_ref())?;
6528 if remaining.is_zero() {
6529 self.cancel_reduce_only_order(&order, filled_order.client_order_id())?;
6530 continue;
6531 }
6532
6533 let leaves = self.parent_capped_leaves(&order, remaining);
6534
6535 let target = order.filled_qty().checked_add(leaves).ok_or_else(|| {
6536 anyhow::anyhow!("Reduce-only quantity overflow for order {client_order_id}")
6537 })?;
6538
6539 if self.resize_order(&mut order, target) {
6540 if target == order.filled_qty() {
6541 self.cancel_reduce_only_order(&order, filled_order.client_order_id())?;
6542 } else if self.config.support_contingent_orders
6543 && order.contingency_type() == Some(ContingencyType::Ouo)
6544 {
6545 self.sync_ouo_leaves(&order, leaves, filled_order.client_order_id())?;
6546 }
6547 }
6548 }
6549
6550 Ok(())
6551 }
6552
6553 fn cancel_reduce_only_order(
6554 &mut self,
6555 order: &OrderAny,
6556 filled_order_id: ClientOrderId,
6557 ) -> anyhow::Result<()> {
6558 let propagate = self.config.support_contingent_orders
6559 && order.contingency_type() == Some(ContingencyType::Ouo);
6560 self.cancel_order(order, Some(!propagate));
6561
6562 if propagate {
6563 self.sync_ouo_leaves(
6564 order,
6565 Quantity::zero(order.quantity().precision),
6566 filled_order_id,
6567 )?;
6568 }
6569
6570 Ok(())
6571 }
6572
6573 fn parent_capped_leaves(&self, order: &OrderAny, leaves: Quantity) -> Quantity {
6574 let parent = if self.config.support_contingent_orders {
6575 order
6576 .parent_order_id()
6577 .and_then(|id| self.order_snapshot(id))
6578 } else {
6579 None
6580 };
6581
6582 parent.map_or(leaves, |parent| {
6583 min(
6584 leaves,
6585 parent.filled_qty().saturating_sub(order.filled_qty()),
6586 )
6587 })
6588 }
6589
6590 fn sync_ouo_leaves(
6591 &mut self,
6592 order: &OrderAny,
6593 leaves: Quantity,
6594 filled_order_id: ClientOrderId,
6595 ) -> anyhow::Result<()> {
6596 for &client_order_id in order.linked_order_ids().into_iter().flatten() {
6597 if client_order_id == filled_order_id || !self.core.order_exists(client_order_id) {
6598 continue;
6599 }
6600
6601 let Some(mut sibling) = self.order_snapshot(client_order_id) else {
6602 continue;
6603 };
6604
6605 if sibling.is_closed() || sibling.is_active_local() || !sibling.is_passive() {
6606 continue;
6607 }
6608
6609 if leaves.is_zero() {
6611 self.cancel_order(&sibling, Some(false));
6612 continue;
6613 }
6614
6615 if !sibling.is_open() {
6616 continue;
6617 }
6618
6619 let leaves = self.parent_capped_leaves(&sibling, leaves);
6620
6621 let target = sibling.filled_qty().checked_add(leaves).ok_or_else(|| {
6622 anyhow::anyhow!("OUO quantity overflow for order {client_order_id}")
6623 })?;
6624
6625 self.resize_order(&mut sibling, target);
6626 if leaves.is_zero() {
6627 self.cancel_order(&sibling, Some(false));
6628 }
6629 }
6630
6631 Ok(())
6632 }
6633
6634 fn resize_order(&self, order: &mut OrderAny, target: Quantity) -> bool {
6637 let quote = order.is_quote_quantity() && !self.instrument.is_inverse();
6638
6639 if order.quantity() == target && !quote {
6640 return false;
6641 }
6642
6643 if quote {
6644 order.set_is_quote_quantity(false);
6645 }
6646
6647 self.generate_order_updated(order, target, order.price(), order.trigger_price(), None);
6648 true
6649 }
6650
6651 fn fee_underlying_price(&self) -> CorrectnessResult<Option<Price>> {
6652 if !matches!(
6653 self.instrument,
6654 InstrumentAny::CryptoOption(_) | InstrumentAny::OptionContract(_)
6655 ) {
6656 return Ok(None);
6657 }
6658
6659 let Some(underlying) = self.instrument.underlying() else {
6660 return Ok(None);
6661 };
6662
6663 let underlying_id = InstrumentId::from(format!("{underlying}.{}", self.venue).as_str());
6664 let instrument_id = self.instrument.id();
6665
6666 let cache = self.cache.borrow();
6667 if let Some(price) = cache
6668 .price(&underlying_id, PriceType::Last)
6669 .or_else(|| cache.price(&underlying_id, PriceType::Mark))
6670 .or_else(|| cache.price(&underlying_id, PriceType::Mid))
6671 {
6672 return Ok(Some(price));
6673 }
6674
6675 cache
6676 .option_greeks(&instrument_id)
6677 .and_then(|greeks| greeks.underlying_price)
6678 .map(|price| Price::new_checked(price, FIXED_PRECISION))
6679 .transpose()
6680 }
6681
6682 fn cached_order_is_closed(&self, client_order_id: ClientOrderId) -> bool {
6683 self.cache
6684 .borrow()
6685 .order(&client_order_id)
6686 .is_none_or(|order| order.is_closed())
6687 }
6688
6689 fn purge_cached_filled_qty_if_closed(&self, client_order_id: ClientOrderId) {
6690 if self.cached_order_is_closed(client_order_id) {
6691 self.orders
6692 .cached_filled_qty
6693 .borrow_mut()
6694 .swap_remove(&client_order_id);
6695 }
6696 }
6697
6698 fn has_nothing_to_match(&self, timestamp_ns: UnixNanos) -> bool {
6700 self.core.iter_orders().next().is_none()
6701 && self.post_match_order_ids.is_empty()
6702 && self.pending_fills.is_empty()
6703 && self.orders.cached_filled_qty.borrow().is_empty()
6704 && self.orders.pending_updates.borrow().is_empty()
6705 && self.orders.rejected_oto_parent_ids.borrow().is_empty()
6706 && self.orders.closed_oto_order_ids.borrow().is_empty()
6707 && self.target_bid.is_none()
6708 && self.target_ask.is_none()
6709 && self.target_last.is_none()
6710 && self.instrument_close.is_none()
6711 && self
6712 .instrument
6713 .expiration_ns()
6714 .is_none_or(|expiration_ns| timestamp_ns < expiration_ns)
6715 }
6716
6717 fn purge_closed_cached_filled_qty(&self) {
6718 let client_order_ids: Vec<ClientOrderId> = self
6719 .orders
6720 .cached_filled_qty
6721 .borrow()
6722 .keys()
6723 .copied()
6724 .collect();
6725
6726 for client_order_id in client_order_ids {
6727 self.purge_cached_filled_qty_if_closed(client_order_id);
6728 }
6729 }
6730
6731 fn update_limit_order(
6732 &mut self,
6733 order: &OrderAny,
6734 quantity: Quantity,
6735 price: Price,
6736 ) -> ModifyOutcome {
6737 if self.core.is_limit_matched(order.order_side(), price) {
6738 if order.is_post_only() {
6739 self.generate_order_modify_rejected(
6740 order.trader_id(),
6741 order.strategy_id(),
6742 order.instrument_id(),
6743 order.client_order_id(),
6744 Ustr::from(format!(
6745 "POST_ONLY {} {} order with new limit px of {} would have been a TAKER: bid={}, ask={}",
6746 order.order_type(),
6747 order.order_side(),
6748 price,
6749 self.core.bid.map_or_else(|| "None".to_string(), |p| p.to_string()),
6750 self.core.ask.map_or_else(|| "None".to_string(), |p| p.to_string())
6751 ).as_str()),
6752 order.venue_order_id(),
6753 order.account_id(),
6754 );
6755 return ModifyOutcome::Rejected;
6756 }
6757
6758 self.generate_order_updated(order, quantity, Some(price), None, None);
6759
6760 let client_order_id = order.client_order_id();
6762 if let Some(mut order) = self.cache.borrow_mut().order_mut(&client_order_id) {
6763 order.set_liquidity_side(LiquiditySide::Taker);
6764 }
6765
6766 self.fill_limit_order(client_order_id);
6767 return ModifyOutcome::Applied;
6768 }
6769
6770 self.generate_order_updated(order, quantity, Some(price), None, None);
6771 ModifyOutcome::Applied
6772 }
6773
6774 fn update_stop_market_order(
6775 &self,
6776 order: &OrderAny,
6777 quantity: Quantity,
6778 trigger_price: Price,
6779 ) -> ModifyOutcome {
6780 if self.core.is_stop_matched_with_trigger_type(
6781 order.order_side(),
6782 trigger_price,
6783 order.trigger_type().unwrap_or(TriggerType::Default),
6784 ) {
6785 self.generate_order_modify_rejected(
6786 order.trader_id(),
6787 order.strategy_id(),
6788 order.instrument_id(),
6789 order.client_order_id(),
6790 Ustr::from(
6791 format!(
6792 "{} {} order new stop px of {} was in the market: bid={}, ask={}",
6793 order.order_type(),
6794 order.order_side(),
6795 trigger_price,
6796 self.core
6797 .bid
6798 .map_or_else(|| "None".to_string(), |p| p.to_string()),
6799 self.core
6800 .ask
6801 .map_or_else(|| "None".to_string(), |p| p.to_string())
6802 )
6803 .as_str(),
6804 ),
6805 order.venue_order_id(),
6806 order.account_id(),
6807 );
6808 return ModifyOutcome::Rejected;
6809 }
6810
6811 self.generate_order_updated(order, quantity, None, Some(trigger_price), None);
6812 ModifyOutcome::Applied
6813 }
6814
6815 fn update_stop_limit_order(
6816 &mut self,
6817 order: &OrderAny,
6818 quantity: Quantity,
6819 price: Price,
6820 trigger_price: Price,
6821 ) -> ModifyOutcome {
6822 if order.is_triggered().is_some_and(|t| t) {
6823 if self.core.is_limit_matched(order.order_side(), price) {
6824 return self.update_limit_order(order, quantity, price);
6825 }
6826 } else {
6827 if self.core.is_stop_matched_with_trigger_type(
6829 order.order_side(),
6830 trigger_price,
6831 order.trigger_type().unwrap_or(TriggerType::Default),
6832 ) {
6833 self.generate_order_modify_rejected(
6834 order.trader_id(),
6835 order.strategy_id(),
6836 order.instrument_id(),
6837 order.client_order_id(),
6838 Ustr::from(
6839 format!(
6840 "{} {} order new stop px of {} was in the market: bid={}, ask={}",
6841 order.order_type(),
6842 order.order_side(),
6843 trigger_price,
6844 self.core
6845 .bid
6846 .map_or_else(|| "None".to_string(), |p| p.to_string()),
6847 self.core
6848 .ask
6849 .map_or_else(|| "None".to_string(), |p| p.to_string())
6850 )
6851 .as_str(),
6852 ),
6853 order.venue_order_id(),
6854 order.account_id(),
6855 );
6856 return ModifyOutcome::Rejected;
6857 }
6858 }
6859
6860 self.generate_order_updated(order, quantity, Some(price), Some(trigger_price), None);
6861 ModifyOutcome::Applied
6862 }
6863
6864 fn update_market_if_touched_order(
6865 &self,
6866 order: &OrderAny,
6867 quantity: Quantity,
6868 trigger_price: Price,
6869 ) -> ModifyOutcome {
6870 if self.core.is_touch_triggered_with_trigger_type(
6871 order.order_side(),
6872 trigger_price,
6873 order.trigger_type().unwrap_or(TriggerType::Default),
6874 ) {
6875 self.generate_order_modify_rejected(
6876 order.trader_id(),
6877 order.strategy_id(),
6878 order.instrument_id(),
6879 order.client_order_id(),
6880 Ustr::from(
6881 format!(
6882 "{} {} order new trigger px of {} was in the market: bid={}, ask={}",
6883 order.order_type(),
6884 order.order_side(),
6885 trigger_price,
6886 self.core
6887 .bid
6888 .map_or_else(|| "None".to_string(), |p| p.to_string()),
6889 self.core
6890 .ask
6891 .map_or_else(|| "None".to_string(), |p| p.to_string())
6892 )
6893 .as_str(),
6894 ),
6895 order.venue_order_id(),
6896 order.account_id(),
6897 );
6898
6899 return ModifyOutcome::Rejected;
6901 }
6902
6903 self.generate_order_updated(order, quantity, None, Some(trigger_price), None);
6904 ModifyOutcome::Applied
6905 }
6906
6907 fn update_limit_if_touched_order(
6908 &mut self,
6909 order: &OrderAny,
6910 quantity: Quantity,
6911 price: Price,
6912 trigger_price: Price,
6913 ) -> ModifyOutcome {
6914 if order.is_triggered().is_some_and(|t| t) {
6915 if self.core.is_limit_matched(order.order_side(), price) {
6916 return self.update_limit_order(order, quantity, price);
6917 }
6918 } else {
6919 if self.core.is_touch_triggered_with_trigger_type(
6921 order.order_side(),
6922 trigger_price,
6923 order.trigger_type().unwrap_or(TriggerType::Default),
6924 ) {
6925 self.generate_order_modify_rejected(
6926 order.trader_id(),
6927 order.strategy_id(),
6928 order.instrument_id(),
6929 order.client_order_id(),
6930 Ustr::from(
6931 format!(
6932 "{} {} order new trigger px of {} was in the market: bid={}, ask={}",
6933 order.order_type(),
6934 order.order_side(),
6935 trigger_price,
6936 self.core
6937 .bid
6938 .map_or_else(|| "None".to_string(), |p| p.to_string()),
6939 self.core
6940 .ask
6941 .map_or_else(|| "None".to_string(), |p| p.to_string())
6942 )
6943 .as_str(),
6944 ),
6945 order.venue_order_id(),
6946 order.account_id(),
6947 );
6948 return ModifyOutcome::Rejected;
6949 }
6950 }
6951
6952 self.generate_order_updated(order, quantity, Some(price), Some(trigger_price), None);
6953 ModifyOutcome::Applied
6954 }
6955
6956 fn update_trailing_stop_order(&self, order: &OrderAny) {
6957 let (new_trigger_price, new_price) = match trailing_stop_calculate(
6958 self.instrument.price_increment(),
6959 order.trigger_price(),
6960 order,
6961 self.core.bid,
6962 self.core.ask,
6963 if order.trigger_type() == Some(TriggerType::MarkPrice) {
6964 self.core.mark
6965 } else {
6966 self.core.last
6967 },
6968 ) {
6969 Ok(prices) => prices,
6970 Err(e) => {
6971 log::debug!("Cannot calculate trailing-stop update: {e}");
6973 return;
6974 }
6975 };
6976
6977 if new_trigger_price.is_none() && new_price.is_none() {
6978 return;
6979 }
6980
6981 self.generate_order_updated(order, order.quantity(), new_price, new_trigger_price, None);
6982 }
6983
6984 fn accept_order(&mut self, order: &mut OrderAny) {
6985 if order.is_closed() {
6986 return;
6988 }
6989
6990 if order.status() != OrderStatus::Accepted {
6991 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
6992 let event = self.create_order_accepted(order, venue_order_id);
6993
6994 if let Err(e) = order.apply(event.clone()) {
6997 log::warn!(
6998 "Skipping local apply of accepted event for {}: {e}",
6999 order.client_order_id(),
7000 );
7001 }
7002
7003 self.dispatch_order_event(event);
7004
7005 if matches!(
7008 order.order_type(),
7009 OrderType::TrailingStopLimit | OrderType::TrailingStopMarket
7010 ) && order.trigger_price().is_none()
7011 && self.maybe_activate_trailing_stop(
7012 order,
7013 self.core.bid,
7014 self.core.ask,
7015 self.core.last,
7016 )
7017 {
7018 self.update_trailing_stop_order(order);
7019 }
7020 }
7021
7022 let match_info = Self::matching_core_entry(order);
7023 self.track_post_match_order(order);
7024 self.core.add_order(match_info);
7025 }
7026
7027 fn track_post_match_order(&mut self, order: &OrderAny) {
7028 self.post_match_order_ids.insert(order.client_order_id());
7029 }
7030
7031 fn delete_core_order(&mut self, client_order_id: ClientOrderId) {
7032 self.post_match_order_ids.swap_remove(&client_order_id);
7033 let _ = self.core.delete_order(client_order_id);
7034 }
7035
7036 fn requires_post_match_maintenance(order: &OrderAny) -> bool {
7037 order.expire_time().is_some()
7038 || matches!(
7039 order.order_type(),
7040 OrderType::TrailingStopMarket | OrderType::TrailingStopLimit
7041 )
7042 }
7043
7044 fn matching_core_entry(order: &OrderAny) -> RestingOrder {
7045 let is_limit_style_stop = matches!(
7046 order.order_type(),
7047 OrderType::StopLimit | OrderType::LimitIfTouched | OrderType::TrailingStopLimit
7048 );
7049 let is_triggered = order.is_triggered().is_some_and(|triggered| triggered);
7050 let triggered_limit_style = is_limit_style_stop && is_triggered;
7051
7052 let limit_price = if is_limit_style_stop && !is_triggered && order.trigger_price().is_none()
7054 {
7055 None
7056 } else {
7057 order.price()
7058 };
7059
7060 RestingOrder::new_with_trigger_type(
7061 order.client_order_id(),
7062 order.order_side(),
7063 order.order_type(),
7064 Some(order.trigger_type().unwrap_or(TriggerType::Default)),
7065 if triggered_limit_style {
7066 None
7067 } else {
7068 order.trigger_price()
7069 },
7070 limit_price,
7071 match order {
7072 OrderAny::TrailingStopMarket(o) => o.is_activated,
7073 OrderAny::TrailingStopLimit(o) => o.is_activated,
7074 _ => true,
7075 },
7076 )
7077 }
7078
7079 fn expire_order(&mut self, order: &OrderAny) {
7080 self.remove_queue_position(order.client_order_id());
7081 if self.config.support_contingent_orders && order.contingency_type().is_some() {
7082 self.cancel_contingent_orders(order, &[]);
7083 }
7084
7085 self.generate_order_expired(order);
7086 }
7087
7088 fn cancel_order(&mut self, order: &OrderAny, cancel_contingencies: Option<bool>) {
7089 self.cancel_order_excluding(order, cancel_contingencies, &[]);
7090 }
7091
7092 fn cancel_order_excluding(
7095 &mut self,
7096 order: &OrderAny,
7097 cancel_contingencies: Option<bool>,
7098 excluded: &[ClientOrderId],
7099 ) {
7100 if self.inflight_orders.contains(order.client_order_id()) {
7101 return;
7102 }
7103
7104 let cancel_contingencies = cancel_contingencies.unwrap_or(true);
7105
7106 if order.instrument_id() != self.instrument.id() && order.parent_order_id().is_some() {
7107 let mut commands = self.orders.commands.borrow_mut();
7108 if !commands.iter().any(|command| matches!(command,
7109 OtoCommand::Cancel { client_order_id, .. } if *client_order_id == order.client_order_id()))
7110 {
7111 commands.push_front(OtoCommand::Cancel {
7113 instrument_id: order.instrument_id(), client_order_id: order.client_order_id(),
7114 cancel_contingencies, excluded: excluded.to_vec(),
7115 });
7116 }
7117
7118 return;
7119 }
7120
7121 if order.is_active_local()
7122 && !matches!(
7123 (order.status(), order.order_type()),
7124 (
7125 OrderStatus::Initialized | OrderStatus::Released,
7126 OrderType::Market
7127 )
7128 )
7129 {
7130 log::error!(
7131 "Cannot cancel an order with {} from the matching engine",
7132 order.status()
7133 );
7134 return;
7135 }
7136
7137 if self.core.order_exists(order.client_order_id()) {
7139 self.delete_core_order(order.client_order_id());
7140 }
7141
7142 self.orders
7143 .pending_oto_order_ids
7144 .borrow_mut()
7145 .swap_remove(&order.client_order_id());
7146 self.remove_queue_position(order.client_order_id());
7147
7148 let venue_order_id = self.ids_generator.get_venue_order_id(order).unwrap();
7149 self.generate_order_canceled(order, venue_order_id);
7150 if self.config.support_contingent_orders
7151 && order.contingency_type().is_some()
7152 && cancel_contingencies
7153 {
7154 self.cancel_contingent_orders(order, excluded);
7155 }
7156
7157 self.purge_cached_filled_qty_if_closed(order.client_order_id());
7160 }
7161
7162 fn update_order(
7163 &mut self,
7164 order: &OrderAny,
7165 quantity: Option<Quantity>,
7166 price: Option<Price>,
7167 trigger_price: Option<Price>,
7168 update_contingencies: Option<bool>,
7169 ) -> bool {
7170 if self.inflight_orders.contains(order.client_order_id()) {
7171 return false;
7172 }
7173
7174 let update_contingencies = update_contingencies.unwrap_or(true);
7175 let quantity = quantity.unwrap_or(order.quantity());
7176
7177 let size_prec = self.instrument.size_precision();
7178 let instrument_id = self.instrument.id();
7179
7180 if !order_precision_valid(quantity.precision, size_prec) {
7181 self.generate_order_modify_rejected(
7182 order.trader_id(),
7183 order.strategy_id(),
7184 order.instrument_id(),
7185 order.client_order_id(),
7186 Ustr::from(&format!(
7187 "Invalid update quantity precision {}, expected {size_prec} for {instrument_id}",
7188 quantity.precision
7189 )),
7190 order.venue_order_id(),
7191 order.account_id(),
7192 );
7193 return false;
7194 }
7195
7196 let price_rejection = price
7199 .filter(|px| Some(*px) != order.price())
7200 .and_then(|px| self.update_price_rejection("price", px))
7201 .or_else(|| {
7202 trigger_price
7203 .filter(|tp| Some(*tp) != order.trigger_price())
7204 .and_then(|tp| self.update_price_rejection("trigger_price", tp))
7205 });
7206
7207 if let Some(reason) = price_rejection {
7208 self.generate_order_modify_rejected(
7209 order.trader_id(),
7210 order.strategy_id(),
7211 order.instrument_id(),
7212 order.client_order_id(),
7213 Ustr::from(&reason),
7214 order.venue_order_id(),
7215 order.account_id(),
7216 );
7217 return false;
7218 }
7219
7220 let filled_qty = self.engine_filled_qty(order);
7222
7223 if quantity < filled_qty {
7224 self.generate_order_modify_rejected(
7225 order.trader_id(),
7226 order.strategy_id(),
7227 order.instrument_id(),
7228 order.client_order_id(),
7229 Ustr::from(&format!(
7230 "Cannot reduce order quantity {quantity} below filled quantity {filled_qty}",
7231 )),
7232 order.venue_order_id(),
7233 order.account_id(),
7234 );
7235 return false;
7236 }
7237
7238 let outcome = match order {
7239 OrderAny::Limit(_) | OrderAny::MarketToLimit(_) => {
7240 let price = price.unwrap_or(order.price().unwrap());
7241 self.update_limit_order(order, quantity, price)
7242 }
7243 OrderAny::StopMarket(_) => {
7244 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
7245 self.update_stop_market_order(order, quantity, trigger_price)
7246 }
7247 OrderAny::StopLimit(_) => {
7248 let price = price.unwrap_or(order.price().unwrap());
7249 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
7250 self.update_stop_limit_order(order, quantity, price, trigger_price)
7251 }
7252 OrderAny::MarketIfTouched(_) => {
7253 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
7254 self.update_market_if_touched_order(order, quantity, trigger_price)
7255 }
7256 OrderAny::LimitIfTouched(_) => {
7257 let price = price.unwrap_or(order.price().unwrap());
7258 let trigger_price = trigger_price.unwrap_or(order.trigger_price().unwrap());
7259 self.update_limit_if_touched_order(order, quantity, price, trigger_price)
7260 }
7261 OrderAny::TrailingStopMarket(inner) => match trigger_price.or(order.trigger_price()) {
7265 Some(trigger_price)
7266 if inner.is_activated || self.trailing_stop_activates_now(order) =>
7267 {
7268 self.update_stop_market_order(order, quantity, trigger_price)
7269 }
7270 trigger_price => {
7271 self.generate_order_updated(order, quantity, None, trigger_price, None);
7272 ModifyOutcome::Applied
7273 }
7274 },
7275 OrderAny::TrailingStopLimit(inner) => match (
7276 price.or(order.price()),
7277 trigger_price.or(order.trigger_price()),
7278 ) {
7279 (Some(price), Some(trigger_price))
7280 if inner.is_activated || self.trailing_stop_activates_now(order) =>
7281 {
7282 self.update_stop_limit_order(order, quantity, price, trigger_price)
7283 }
7284 (price, trigger_price) => {
7285 self.generate_order_updated(order, quantity, price, trigger_price, None);
7286 ModifyOutcome::Applied
7287 }
7288 },
7289 _ => {
7290 panic!(
7291 "Unsupported order type {} for update_order",
7292 order.order_type()
7293 );
7294 }
7295 };
7296
7297 if outcome == ModifyOutcome::Rejected {
7298 return false;
7299 }
7300
7301 if self.config.support_contingent_orders
7302 && order.contingency_type().is_some()
7303 && update_contingencies
7304 {
7305 self.update_contingent_order(order, quantity);
7306 }
7307
7308 let new_leaves_qty = quantity.saturating_sub(filled_qty);
7310 if new_leaves_qty.is_zero() {
7311 self.cancel_order(order, Some(false));
7313 return true;
7314 }
7315
7316 true
7317 }
7318
7319 fn update_price_rejection(&self, field: &str, price: Price) -> Option<String> {
7320 let price_precision = self.instrument.price_precision();
7321 let instrument_id = self.instrument.id();
7322
7323 if !order_precision_valid(price.precision, price_precision) {
7324 return Some(format!(
7325 "Invalid update {field} precision {}, expected {price_precision} for {instrument_id}",
7326 price.precision
7327 ));
7328 }
7329
7330 let price_increment = self.instrument.price_increment();
7331 if !Self::price_matches_tick(price, price_increment) {
7332 return Some(format!(
7333 "Invalid update {field} {price}, not a multiple of {instrument_id} price increment {price_increment}"
7334 ));
7335 }
7336
7337 None
7338 }
7339
7340 pub fn trigger_stop_order(&mut self, client_order_id: ClientOrderId) {
7342 let order = match self.order_snapshot(client_order_id) {
7343 Some(order) => order,
7344 None => {
7345 log::error!(
7346 "Cannot trigger stop order: order {client_order_id} not found in cache"
7347 );
7348 return;
7349 }
7350 };
7351
7352 if order.is_closed() {
7353 log::debug!("Cannot trigger stop order: {client_order_id} already closed");
7354 return;
7355 }
7356
7357 match order.order_type() {
7358 OrderType::StopLimit | OrderType::LimitIfTouched | OrderType::TrailingStopLimit => {
7359 self.trigger_limit_style_stop_order(client_order_id, order);
7360 }
7361 OrderType::StopMarket | OrderType::MarketIfTouched | OrderType::TrailingStopMarket => {
7362 self.fill_market_order(client_order_id);
7363 }
7364 _ => {
7365 log::error!(
7366 "Cannot trigger stop order: invalid order type {}",
7367 order.order_type()
7368 );
7369 }
7370 }
7371 }
7372
7373 fn trigger_limit_style_stop_order(&mut self, client_order_id: ClientOrderId, order: OrderAny) {
7374 if order.is_triggered().is_some_and(|triggered| triggered) {
7375 let liquidity_side = match (order.price(), order.trigger_price()) {
7376 (Some(price), Some(trigger_price)) => self.determine_triggered_limit_liquidity(
7377 order.order_side(),
7378 price,
7379 trigger_price,
7380 order.trigger_type(),
7381 ),
7382 _ => LiquiditySide::Maker,
7383 };
7384
7385 if let Some(mut cached_order) = self.cache.borrow_mut().order_mut(&client_order_id)
7386 && !matches!(
7387 cached_order.liquidity_side(),
7388 Some(LiquiditySide::Maker | LiquiditySide::Taker)
7389 )
7390 {
7391 cached_order.set_liquidity_side(liquidity_side);
7392 }
7393
7394 self.fill_limit_order(client_order_id);
7395 return;
7396 }
7397
7398 let event = self.create_order_triggered(&order);
7399
7400 let order = match self.cache.borrow_mut().update_order(&event) {
7401 Ok(order) => order,
7402 Err(e) => {
7403 log::debug!(
7404 "Failed to apply triggered event for {} before fill: {e}",
7405 order.client_order_id(),
7406 );
7407 order
7408 }
7409 };
7410
7411 let order = self.order_snapshot(client_order_id).unwrap_or(order);
7412 self.dispatch_order_event(event);
7413
7414 let trigger_price = order
7415 .trigger_price()
7416 .expect("Limit-style stop order must have a trigger price");
7417 let price = order
7418 .price()
7419 .expect("Limit-style stop order must have a price");
7420
7421 let liquidity_side = self.determine_triggered_limit_liquidity(
7422 order.order_side(),
7423 price,
7424 trigger_price,
7425 order.trigger_type(),
7426 );
7427
7428 let maker_inside = match order.order_side() {
7429 OrderSide::Buy => self
7430 .core
7431 .ask
7432 .is_some_and(|ask| liquidity_side == LiquiditySide::Maker && price > ask),
7433 OrderSide::Sell => self
7434 .core
7435 .bid
7436 .is_some_and(|bid| liquidity_side == LiquiditySide::Maker && price < bid),
7437 };
7438
7439 if maker_inside {
7440 if let Some(mut cached_order) = self.cache.borrow_mut().order_mut(&client_order_id) {
7441 cached_order.set_liquidity_side(LiquiditySide::Maker);
7442 }
7443
7444 self.resync_core_entry(client_order_id);
7445 self.fill_limit_order(client_order_id);
7446 return;
7447 }
7448
7449 if self.core.is_limit_matched(order.order_side(), price) {
7450 if order.is_post_only() {
7451 self.delete_core_order(client_order_id);
7452 self.orders
7453 .cached_filled_qty
7454 .borrow_mut()
7455 .swap_remove(&client_order_id);
7456 let event = self.create_order_rejected(
7457 &order,
7458 format!(
7459 "POST_ONLY {} {} order limit px of {} would have been a TAKER: bid={}, ask={}",
7460 order.order_type(),
7461 order.order_side(),
7462 price,
7463 self.core
7464 .bid
7465 .map_or_else(|| "None".to_string(), |p| p.to_string()),
7466 self.core
7467 .ask
7468 .map_or_else(|| "None".to_string(), |p| p.to_string())
7469 )
7470 .into(),
7471 );
7472
7473 if let Err(e) = self.cache.borrow_mut().update_order(&event) {
7474 log::debug!(
7475 "Failed to apply rejected event for {} after post-only trigger: {e}",
7476 order.client_order_id(),
7477 );
7478 }
7479
7480 self.dispatch_order_event(event);
7481 return;
7482 }
7483
7484 if let Some(mut cached_order) = self.cache.borrow_mut().order_mut(&client_order_id) {
7485 cached_order.set_liquidity_side(LiquiditySide::Taker);
7486 }
7487
7488 self.resync_core_entry(client_order_id);
7489 self.fill_limit_order(client_order_id);
7490 return;
7491 }
7492
7493 if let Some(mut cached_order) = self.cache.borrow_mut().order_mut(&client_order_id) {
7494 cached_order.set_liquidity_side(liquidity_side);
7495 }
7496
7497 self.resync_core_entry(client_order_id);
7498 }
7499
7500 fn determine_triggered_limit_liquidity(
7501 &self,
7502 side: OrderSide,
7503 price: Price,
7504 trigger_price: Price,
7505 trigger_type: Option<TriggerType>,
7506 ) -> LiquiditySide {
7507 if trigger_type == Some(TriggerType::MarkPrice) {
7508 return if self.core.is_limit_matched(side, price) {
7509 LiquiditySide::Taker
7510 } else {
7511 LiquiditySide::Maker
7512 };
7513 }
7514
7515 if (side == OrderSide::Buy && trigger_price > price)
7516 || (side == OrderSide::Sell && trigger_price < price)
7517 {
7518 LiquiditySide::Maker
7519 } else {
7520 LiquiditySide::Taker
7521 }
7522 }
7523
7524 fn update_contingent_order(&mut self, order: &OrderAny, parent_quantity: Quantity) {
7525 log::debug!(
7526 "Updating contingent orders from {}",
7527 order.client_order_id()
7528 );
7529
7530 if order.contingency_type() == Some(ContingencyType::Oto) {
7531 self.update_oto_children(order, Some(parent_quantity), &[]);
7532 return;
7533 }
7534
7535 if let Some(linked_order_ids) = order.linked_order_ids() {
7536 let parent_filled_qty = self.engine_filled_qty(order);
7537 let parent_leaves_qty = parent_quantity.saturating_sub(parent_filled_qty);
7538
7539 for client_order_id in linked_order_ids {
7540 let child_order = match self.order_snapshot(*client_order_id) {
7541 Some(order) => order,
7542 None => panic!("Order {client_order_id} not found in cache."),
7543 };
7544
7545 if child_order.is_active_local() {
7546 continue;
7547 }
7548
7549 let child_filled_qty = self.engine_filled_qty(&child_order);
7550 if child_filled_qty >= parent_leaves_qty {
7551 self.cancel_order(&child_order, Some(false));
7553 } else {
7554 self.update_contingent_quantity(
7555 &child_order,
7556 parent_leaves_qty,
7557 order.is_quote_quantity() && !self.instrument.is_inverse(),
7558 );
7559 }
7560 }
7561 }
7562 }
7563
7564 fn update_contingent_quantity(
7565 &mut self,
7566 order: &OrderAny,
7567 quantity: Quantity,
7568 is_quote_quantity: bool,
7569 ) {
7570 if self.contingent_order_is_closed(order)
7571 || self.inflight_orders.contains(order.client_order_id())
7572 {
7573 return;
7574 }
7575
7576 let quantity = self.quantity_for_order(order, quantity);
7577 if quantity <= self.engine_filled_qty(order) {
7578 self.cancel_order(order, Some(false));
7579 return;
7580 }
7581
7582 let clear_quote = order.is_quote_quantity()
7583 && !is_quote_quantity
7584 && if order.instrument_id() == self.instrument.id() {
7585 !self.instrument.is_inverse()
7586 } else {
7587 self.cache
7588 .borrow()
7589 .instrument(&order.instrument_id())
7590 .is_some_and(|instrument| !instrument.is_inverse())
7591 };
7592
7593 if quantity == order.leaves_qty() && !clear_quote {
7594 return;
7595 }
7596
7597 let mut converted;
7598 let order = if clear_quote {
7599 converted = order.clone();
7600 converted.set_is_quote_quantity(false);
7601 &converted
7602 } else {
7603 order
7604 };
7605
7606 let price = order.price();
7607 let trigger_price = order.trigger_price();
7608 if order.instrument_id() == self.instrument.id() {
7609 self.update_order(order, Some(quantity), price, trigger_price, Some(false));
7610 } else {
7611 self.generate_order_updated(order, quantity, price, trigger_price, None);
7612 }
7613 }
7614
7615 fn apply_deferred_submission(
7622 &mut self,
7623 order: &mut OrderAny,
7624 accepted_elsewhere: bool,
7625 ) -> anyhow::Result<()> {
7626 let client_order_id = order.client_order_id();
7627 let accepted = self.core.order_exists(client_order_id) || accepted_elsewhere;
7628
7629 if !order.is_active_local()
7630 || !(accepted
7631 || self
7632 .orders
7633 .pending_oto_order_ids
7634 .borrow()
7635 .contains(&client_order_id))
7636 {
7637 return Ok(());
7638 }
7639
7640 let account_id = match order.account_id() {
7641 Some(account_id) => account_id,
7642 None => *self
7643 .account_ids
7644 .get(&order.trader_id())
7645 .ok_or_else(|| anyhow::anyhow!("No account ID for {}", order.trader_id()))?,
7646 };
7647
7648 let event = self.create_order_submitted(order, account_id);
7649 order.apply(event)?;
7650 if accepted {
7651 let venue_order_id = self.ids_generator.get_venue_order_id(order)?;
7652 let event = self.create_order_accepted(order, venue_order_id);
7653 order.apply(event)?;
7654 }
7655
7656 Ok(())
7657 }
7658
7659 fn works_in_another_engine(&self, order: &OrderAny) -> bool {
7661 order.instrument_id() != self.instrument.id()
7662 && (self
7663 .orders
7664 .released_oto_order_ids
7665 .borrow()
7666 .contains(&order.client_order_id())
7667 || self
7668 .cache
7669 .borrow()
7670 .venue_order_id(&order.client_order_id())
7671 .is_some())
7672 }
7673
7674 fn update_oto_children(
7690 &mut self,
7691 parent: &OrderAny,
7692 parent_quantity: Option<Quantity>,
7693 excluded: &[ClientOrderId],
7694 ) {
7695 let Some(ids) = parent.linked_order_ids() else {
7696 return;
7697 };
7698
7699 let snapshot = self
7700 .order_snapshot(parent.client_order_id())
7701 .unwrap_or_else(|| parent.clone());
7702 let filled_qty = self.engine_filled_qty(&snapshot);
7703 let quantity = parent_quantity.unwrap_or(filled_qty);
7704 let sizing = OtoSizing {
7705 quantity,
7706 filled_qty,
7707 quote: snapshot.is_quote_quantity() && !self.instrument.is_inverse(),
7708 reduced_to_filled: parent_quantity.is_some_and(|q| q <= filled_qty),
7709 grew: quantity > parent.quantity(),
7711 };
7712
7713 let mut released = false;
7714
7715 for id in ids {
7716 if excluded.contains(id) {
7717 continue;
7719 }
7720
7721 released |= self.update_oto_child(parent, *id, ids, &sizing);
7722 }
7723
7724 if released {
7725 let not_resting: Vec<_> = ids
7727 .iter()
7728 .filter(|id| {
7729 excluded.contains(*id)
7730 || (!self.core.order_exists(**id)
7731 && !self
7732 .order_snapshot(**id)
7733 .is_some_and(|order| self.works_in_another_engine(&order)))
7734 })
7735 .copied()
7736 .collect();
7737
7738 self.update_oto_children(parent, Some(quantity), ¬_resting);
7739 }
7740 }
7741
7742 fn update_oto_child(
7743 &mut self,
7744 parent: &OrderAny,
7745 id: ClientOrderId,
7746 ids: &[ClientOrderId],
7747 sizing: &OtoSizing,
7748 ) -> bool {
7749 let mut child = self
7750 .order_snapshot(id)
7751 .unwrap_or_else(|| panic!("Cannot find contingent order for {id}"));
7752 let elsewhere = self.works_in_another_engine(&child);
7753 let accepted_elsewhere = elsewhere && self.cache.borrow().venue_order_id(&id).is_some();
7754 if let Err(e) = self.apply_deferred_submission(&mut child, accepted_elsewhere) {
7755 log::error!("Cannot update contingent order {id}: {e}");
7756 return false;
7757 }
7758
7759 if child.is_active_local()
7760 || self.contingent_order_is_closed(&child)
7761 || self.inflight_orders.contains(id)
7762 {
7763 return false;
7764 }
7765
7766 let remaining = self.oto_child_remaining(&child, ids, sizing.quantity);
7767 let released = self.core.order_exists(id) || elsewhere;
7768 if !released && !self.config.oto_full_trigger && !sizing.filled_qty.is_zero() {
7770 return false;
7771 }
7772
7773 let release = !released && sizing.reduced_to_filled && self.config.oto_full_trigger;
7774 let parent_closed = sizing.quantity <= sizing.filled_qty;
7775 if parent_closed && ((!released && !release) || remaining.is_zero()) {
7776 self.cancel_order(&child, Some(false));
7777 return false;
7778 }
7779
7780 if remaining.is_zero() {
7781 return false;
7782 }
7783
7784 self.resize_oto_child(&mut child, sizing, remaining);
7785
7786 if !release {
7787 return false;
7788 }
7789
7790 self.release_oto_child_after_resize(parent, id, sizing.quantity)
7791 }
7792
7793 fn contingent_order_is_closed(&self, order: &OrderAny) -> bool {
7794 order.is_closed()
7795 || order.leaves_qty().is_zero()
7796 || self
7797 .orders
7798 .closed_oto_order_ids
7799 .borrow()
7800 .contains(&order.client_order_id())
7801 }
7802
7803 fn oto_child_remaining(
7804 &self,
7805 child: &OrderAny,
7806 ids: &[ClientOrderId],
7807 quantity: Quantity,
7808 ) -> Quantity {
7809 let filled = ids
7811 .iter()
7812 .filter_map(|id| self.order_snapshot(*id))
7813 .fold(Quantity::zero(quantity.precision), |acc, order| {
7814 acc + self.engine_filled_qty(&order)
7815 });
7816
7817 self.quantity_for_order(child, quantity.saturating_sub(filled))
7818 }
7819
7820 fn quantity_for_order(&self, order: &OrderAny, quantity: Quantity) -> Quantity {
7821 if order.instrument_id() == self.instrument.id() {
7822 let precision = self.instrument.size_precision();
7823 if order_precision_valid(quantity.precision, precision) {
7825 return quantity;
7826 }
7827
7828 let rounded = quantity
7829 .as_decimal()
7830 .round_dp_with_strategy(u32::from(precision), RoundingStrategy::ToZero);
7831 return Quantity::from_decimal_dp(rounded, precision)
7832 .unwrap_or_else(|_| Quantity::zero(precision));
7833 }
7834
7835 let cache = self.cache.borrow();
7836 cache
7837 .instrument(&order.instrument_id())
7838 .map_or(quantity, |instrument| {
7839 if quantity.precision <= FIXED_PRECISION
7840 && quantity.precision == instrument.size_precision()
7841 && Self::quantity_matches_precision(quantity, 0)
7842 {
7843 return quantity;
7844 }
7845
7846 instrument
7847 .try_make_qty_from_decimal(quantity.as_decimal(), Some(true))
7848 .unwrap_or_else(|_| Quantity::zero(instrument.size_precision()))
7849 })
7850 }
7851
7852 fn resize_oto_child(&self, child: &mut OrderAny, sizing: &OtoSizing, remaining: Quantity) {
7853 let quote = child.is_quote_quantity();
7854 let other_instrument = child.instrument_id() != self.instrument.id();
7855 if !self.convert_to_parent_units(child, sizing.quote, other_instrument) {
7856 return;
7857 }
7858
7859 let converted = quote != child.is_quote_quantity();
7860 let parent_closed = sizing.quantity <= sizing.filled_qty;
7861 let mut quantity = self.engine_filled_qty(child) + remaining;
7862 if parent_closed || !sizing.grew {
7863 quantity = quantity.min(child.quantity());
7864 }
7865
7866 if quantity != child.quantity() || (parent_closed && converted) {
7868 self.generate_order_updated(
7870 child,
7871 quantity,
7872 child.price(),
7873 child.trigger_price(),
7874 None,
7875 );
7876 }
7877 }
7878
7879 fn release_oto_child_after_resize(
7880 &mut self,
7881 parent: &OrderAny,
7882 id: ClientOrderId,
7883 quantity: Quantity,
7884 ) -> bool {
7885 let account_id = parent
7886 .account_id()
7887 .or_else(|| self.account_ids.get(&parent.trader_id()).copied());
7888
7889 match (self.order_snapshot(id), account_id) {
7890 (Some(mut child), Some(account_id)) => {
7891 self.release_oto_child(&mut child, account_id, parent, Some(quantity));
7892 true
7893 }
7894 _ => {
7895 log::error!("Cannot release OTO order {id}");
7896 false
7897 }
7898 }
7899 }
7900
7901 fn release_oto_child(
7902 &mut self,
7903 child: &mut OrderAny,
7904 account_id: AccountId,
7905 parent: &OrderAny,
7906 parent_quantity: Option<Quantity>,
7907 ) {
7908 if child.instrument_id() == self.instrument.id() {
7909 self.process_order(child, account_id);
7910 } else if self
7911 .orders
7912 .released_oto_order_ids
7913 .borrow_mut()
7914 .insert(child.client_order_id())
7915 {
7916 self.orders
7917 .commands
7918 .borrow_mut()
7919 .push_back(OtoCommand::Release {
7920 instrument_id: child.instrument_id(),
7921 client_order_id: child.client_order_id(),
7922 account_id,
7923 parent_instrument_id: self.instrument.id(),
7924 parent_order_id: parent.client_order_id(),
7925 parent_quantity,
7926 });
7927 }
7928 }
7929
7930 fn convert_to_parent_units(
7940 &self,
7941 child: &mut OrderAny,
7942 parent_quote: bool,
7943 other_instrument: bool,
7944 ) -> bool {
7945 let cache = self.cache.borrow();
7946 let instrument = cache
7947 .instrument(&child.instrument_id())
7948 .filter(|_| other_instrument)
7949 .unwrap_or(&self.instrument);
7950 let child_quote = child.is_quote_quantity() && !instrument.is_inverse();
7951
7952 if child_quote == parent_quote {
7953 return true;
7954 }
7955
7956 if parent_quote {
7957 return false;
7958 }
7959
7960 let opposing_best = match child.order_side() {
7961 OrderSide::Buy => self.core.ask,
7962 OrderSide::Sell => self.core.bid,
7963 };
7964
7965 let reference_price = child
7966 .price()
7967 .or(child.trigger_price())
7968 .or(opposing_best.filter(|_| !other_instrument));
7969
7970 let base_quantity = reference_price.and_then(|price| {
7971 instrument
7972 .try_calculate_base_quantity(child.quantity(), price)
7973 .ok()
7974 });
7975
7976 let Some(base_quantity) = base_quantity else {
7977 return false;
7978 };
7979
7980 child.set_quantity(base_quantity);
7981 child.set_is_quote_quantity(false);
7982 true
7983 }
7984
7985 fn engine_filled_qty(&self, order: &OrderAny) -> Quantity {
7986 self.orders
7987 .cached_filled_qty
7988 .borrow()
7989 .get(&order.client_order_id())
7990 .copied()
7991 .unwrap_or(order.filled_qty())
7992 }
7993
7994 fn cancel_contingent_orders(&mut self, order: &OrderAny, excluded: &[ClientOrderId]) {
7995 if order.contingency_type() == Some(ContingencyType::Oto) {
7996 self.update_oto_children(order, None, excluded);
7997 return;
7998 }
7999
8000 if let Some(linked_order_ids) = order.linked_order_ids() {
8001 for client_order_id in linked_order_ids {
8002 if excluded.contains(client_order_id) {
8003 continue;
8004 }
8005
8006 let mut contingent_order = match self.order_snapshot(*client_order_id) {
8007 Some(order) => order,
8008 None => panic!("Cannot find contingent order for {client_order_id}"),
8009 };
8010
8011 if let Err(e) = self.apply_deferred_submission(&mut contingent_order, false) {
8012 log::error!("Cannot cancel contingent order {client_order_id}: {e}");
8013 continue;
8014 }
8015
8016 if contingent_order.is_active_local() {
8017 continue;
8019 }
8020
8021 if !self.contingent_order_is_closed(&contingent_order) {
8022 self.cancel_order(&contingent_order, Some(false));
8023 }
8024 }
8025 }
8026 }
8027
8028 fn create_order_submitted(&self, order: &OrderAny, account_id: AccountId) -> OrderEventAny {
8029 let ts_now = self.clock.borrow().timestamp_ns();
8030 OrderEventAny::Submitted(OrderSubmitted::new(
8031 order.trader_id(),
8032 order.strategy_id(),
8033 order.instrument_id(),
8034 order.client_order_id(),
8035 account_id,
8036 UUID4::new(),
8037 ts_now,
8038 ts_now,
8039 ))
8040 }
8041
8042 fn generate_order_submitted(&self, order: &OrderAny, account_id: AccountId) {
8043 let event = self.create_order_submitted(order, account_id);
8044 self.dispatch_order_event(event);
8045 }
8046
8047 fn create_order_rejected(&self, order: &OrderAny, reason: Ustr) -> OrderEventAny {
8048 let ts_now = self.clock.borrow().timestamp_ns();
8049 let account_id = order
8050 .account_id()
8051 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
8052
8053 let due_post_only = reason.starts_with("POST_ONLY");
8054
8055 OrderEventAny::Rejected(OrderRejected::new(
8056 order.trader_id(),
8057 order.strategy_id(),
8058 order.instrument_id(),
8059 order.client_order_id(),
8060 account_id,
8061 reason,
8062 UUID4::new(),
8063 ts_now,
8064 ts_now,
8065 false,
8066 due_post_only,
8067 ))
8068 }
8069
8070 fn generate_order_rejected(&self, order: &OrderAny, reason: Ustr) {
8071 if order.contingency_type() == Some(ContingencyType::Oto) {
8072 self.orders
8073 .rejected_oto_parent_ids
8074 .borrow_mut()
8075 .insert(order.client_order_id());
8076 }
8077
8078 let event = self.create_order_rejected(order, reason);
8079 self.dispatch_oto_terminal(order, event);
8080 }
8081
8082 fn publish_order_initialized(&self, order: &OrderAny) {
8083 let event = OrderEventAny::Initialized(order.init_event().clone());
8084 let topic = msgbus::switchboard::get_event_order_topic(order.strategy_id());
8085 msgbus::publish_order_event(topic, &event);
8086 }
8087
8088 fn create_order_accepted(
8089 &self,
8090 order: &OrderAny,
8091 venue_order_id: VenueOrderId,
8092 ) -> OrderEventAny {
8093 let ts_now = self.clock.borrow().timestamp_ns();
8094 let account_id = order
8095 .account_id()
8096 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
8097 OrderEventAny::Accepted(OrderAccepted::new(
8098 order.trader_id(),
8099 order.strategy_id(),
8100 order.instrument_id(),
8101 order.client_order_id(),
8102 venue_order_id,
8103 account_id,
8104 UUID4::new(),
8105 ts_now,
8106 ts_now,
8107 false,
8108 ))
8109 }
8110
8111 fn generate_order_accepted(&self, order: &OrderAny, venue_order_id: VenueOrderId) {
8112 let event = self.create_order_accepted(order, venue_order_id);
8113 self.dispatch_order_event(event);
8114 }
8115
8116 #[expect(clippy::too_many_arguments)]
8117 fn generate_order_modify_rejected(
8118 &self,
8119 trader_id: TraderId,
8120 strategy_id: StrategyId,
8121 instrument_id: InstrumentId,
8122 client_order_id: ClientOrderId,
8123 reason: Ustr,
8124 venue_order_id: Option<VenueOrderId>,
8125 account_id: Option<AccountId>,
8126 ) {
8127 let ts_now = self.clock.borrow().timestamp_ns();
8128 let event = OrderEventAny::ModifyRejected(OrderModifyRejected::new(
8129 trader_id,
8130 strategy_id,
8131 instrument_id,
8132 client_order_id,
8133 reason,
8134 UUID4::new(),
8135 ts_now,
8136 ts_now,
8137 false,
8138 venue_order_id,
8139 account_id,
8140 ));
8141 self.dispatch_order_event(event);
8142 }
8143
8144 #[expect(clippy::too_many_arguments)]
8145 fn generate_order_cancel_rejected(
8146 &self,
8147 trader_id: TraderId,
8148 strategy_id: StrategyId,
8149 account_id: AccountId,
8150 instrument_id: InstrumentId,
8151 client_order_id: ClientOrderId,
8152 venue_order_id: Option<VenueOrderId>,
8153 reason: Ustr,
8154 ) {
8155 let ts_now = self.clock.borrow().timestamp_ns();
8156 let event = OrderEventAny::CancelRejected(OrderCancelRejected::new(
8157 trader_id,
8158 strategy_id,
8159 instrument_id,
8160 client_order_id,
8161 reason,
8162 UUID4::new(),
8163 ts_now,
8164 ts_now,
8165 false,
8166 venue_order_id,
8167 Some(account_id),
8168 ));
8169 self.dispatch_order_event(event);
8170 }
8171
8172 fn generate_order_updated(
8173 &self,
8174 order: &OrderAny,
8175 quantity: Quantity,
8176 price: Option<Price>,
8177 trigger_price: Option<Price>,
8178 protection_price: Option<Price>,
8179 ) {
8180 let ts_now = self.clock.borrow().timestamp_ns();
8181
8182 let event = OrderUpdated::new(
8183 order.trader_id(),
8184 order.strategy_id(),
8185 order.instrument_id(),
8186 order.client_order_id(),
8187 quantity,
8188 UUID4::new(),
8189 ts_now,
8190 ts_now,
8191 false,
8192 order.venue_order_id(),
8193 order.account_id(),
8194 price,
8195 trigger_price,
8196 protection_price,
8197 order.is_quote_quantity(),
8198 );
8199
8200 self.orders
8201 .pending_updates
8202 .borrow_mut()
8203 .entry(order.client_order_id())
8204 .or_default()
8205 .push(event);
8206 self.dispatch_order_event(OrderEventAny::Updated(event));
8207 }
8208
8209 fn generate_order_canceled(&self, order: &OrderAny, venue_order_id: VenueOrderId) {
8210 let ts_now = self.clock.borrow().timestamp_ns();
8211 let event = OrderEventAny::Canceled(OrderCanceled::new(
8212 order.trader_id(),
8213 order.strategy_id(),
8214 order.instrument_id(),
8215 order.client_order_id(),
8216 UUID4::new(),
8217 ts_now,
8218 ts_now,
8219 false,
8220 Some(venue_order_id),
8221 order.account_id(),
8222 None,
8223 ));
8224 self.dispatch_oto_terminal(order, event);
8225 }
8226
8227 fn create_order_triggered(&self, order: &OrderAny) -> OrderEventAny {
8228 let ts_now = self.clock.borrow().timestamp_ns();
8229 OrderEventAny::Triggered(OrderTriggered::new(
8230 order.trader_id(),
8231 order.strategy_id(),
8232 order.instrument_id(),
8233 order.client_order_id(),
8234 UUID4::new(),
8235 ts_now,
8236 ts_now,
8237 false,
8238 order.venue_order_id(),
8239 order.account_id(),
8240 ))
8241 }
8242
8243 fn generate_order_expired(&self, order: &OrderAny) {
8244 let ts_now = self.clock.borrow().timestamp_ns();
8245 let event = OrderEventAny::Expired(OrderExpired::new(
8246 order.trader_id(),
8247 order.strategy_id(),
8248 order.instrument_id(),
8249 order.client_order_id(),
8250 UUID4::new(),
8251 ts_now,
8252 ts_now,
8253 false,
8254 order.venue_order_id(),
8255 order.account_id(),
8256 ));
8257 self.dispatch_oto_terminal(order, event);
8258 }
8259
8260 fn dispatch_oto_terminal(&self, order: &OrderAny, event: OrderEventAny) {
8261 if self.config.support_contingent_orders && order.parent_order_id().is_some() {
8262 self.orders
8263 .closed_oto_order_ids
8264 .borrow_mut()
8265 .insert(order.client_order_id());
8266 }
8267
8268 self.dispatch_order_event(event);
8269
8270 if self.cached_order_is_closed(order.client_order_id()) {
8271 self.orders
8272 .closed_oto_order_ids
8273 .borrow_mut()
8274 .swap_remove(&order.client_order_id());
8275 }
8276 }
8277
8278 #[expect(clippy::too_many_arguments)]
8279 fn generate_order_filled(
8280 &mut self,
8281 order: &OrderAny,
8282 venue_order_id: VenueOrderId,
8283 venue_position_id: Option<PositionId>,
8284 last_qty: Quantity,
8285 last_px: Price,
8286 quote_currency: Currency,
8287 commission: Money,
8288 liquidity_side: LiquiditySide,
8289 ) {
8290 debug_assert!(
8291 last_qty <= order.quantity(),
8292 "Fill quantity {last_qty} exceeds order quantity {order_qty} for {client_order_id}",
8293 order_qty = order.quantity(),
8294 client_order_id = order.client_order_id()
8295 );
8296
8297 let ts_now = self.clock.borrow().timestamp_ns();
8298 let account_id = order
8299 .account_id()
8300 .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned());
8301
8302 let fill = OrderFilled::new(
8303 order.trader_id(),
8304 order.strategy_id(),
8305 order.instrument_id(),
8306 order.client_order_id(),
8307 venue_order_id,
8308 account_id,
8309 self.ids_generator.generate_trade_id(ts_now),
8310 order.order_side(),
8311 order.order_type(),
8312 last_qty,
8313 last_px,
8314 quote_currency,
8315 liquidity_side,
8316 UUID4::new(),
8317 ts_now,
8318 ts_now,
8319 false,
8320 venue_position_id,
8321 Some(commission),
8322 None,
8323 );
8324
8325 self.record_pending_fill(&fill);
8326 self.dispatch_order_event(OrderEventAny::Filled(fill));
8327 }
8328
8329 fn record_pending_fill(&mut self, fill: &OrderFilled) {
8330 if (!self.config.use_reduce_only
8331 && !self.restricts_short_selling()
8332 && !self.instrument.has_expiration())
8333 || self.instrument.is_spread()
8334 {
8335 return;
8336 }
8337
8338 self.purge_applied_fills();
8339 let cache = self.cache.borrow();
8340
8341 let position_id = cache
8342 .position_id(&fill.client_order_id)
8343 .copied()
8344 .or(fill.position_id)
8345 .or_else(|| {
8346 (self.oms_type == OmsType::Netting).then(|| {
8347 PositionId::new(format!("{}-{}", fill.instrument_id, fill.strategy_id))
8348 })
8349 });
8350
8351 let opening_trade_id = position_id.and_then(|id| {
8352 cache
8353 .position(&id)
8354 .and_then(|position| position.events.first().map(|event| event.trade_id))
8355 });
8356
8357 let mut quantity_change = if fill.order_side == OrderSide::Buy {
8358 fill.last_qty.as_decimal()
8359 } else {
8360 -fill.last_qty.as_decimal()
8361 };
8362
8363 if matches!(
8364 self.instrument,
8365 InstrumentAny::CurrencyPair(_) | InstrumentAny::TokenizedAsset(_)
8366 ) && let Some(commission) = fill.commission
8367 && Some(commission.currency) == self.instrument.base_currency()
8368 {
8369 quantity_change -= commission.as_decimal();
8370 }
8371
8372 self.pending_fills.insert(
8373 fill.trade_id,
8374 PendingFill {
8375 client_order_id: fill.client_order_id,
8376 position_id,
8377 opening_trade_id,
8378 quantity_change,
8379 },
8380 );
8381 }
8382}
8383
8384struct OtoSizing {
8385 quantity: Quantity,
8386 filled_qty: Quantity,
8387 quote: bool,
8388 reduced_to_filled: bool,
8389 grew: bool,
8390}
8391
8392#[derive(Debug)]
8393struct PendingFill {
8394 client_order_id: ClientOrderId,
8395 position_id: Option<PositionId>,
8396 opening_trade_id: Option<TradeId>,
8397 quantity_change: Decimal,
8398}
8399
8400#[derive(Debug)]
8402struct ConsumptionJournal {
8403 order_side: OrderSide,
8404 trade_consumption: QuantityRaw,
8405 levels: Vec<(PriceRaw, Option<(QuantityRaw, QuantityRaw)>)>,
8407}
8408
8409#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8410enum ModifyOutcome {
8411 Applied,
8412 Rejected,
8413}
8414
8415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8416enum OrderMatchMode {
8417 All,
8418 StopTriggers(TriggerType),
8419}
8420
8421#[derive(Debug)]
8422enum PostMatchOrderAction {
8423 RemoveClosed,
8424 Expire(OrderAny),
8425 UpdateTrailing(OrderAny),
8426 NoMaintenance,
8427}
8428
8429fn order_precision_valid(actual: u8, expected: u8) -> bool {
8430 actual <= expected && raw_scales_match(actual, expected)
8431}
8432
8433fn post_match_order_action<F>(
8434 order: &OrderAny,
8435 support_gtd_orders: bool,
8436 timestamp_ns: UnixNanos,
8437 clone_order: F,
8438) -> PostMatchOrderAction
8439where
8440 F: FnOnce(&OrderAny) -> OrderAny,
8441{
8442 if order.is_closed() {
8443 PostMatchOrderAction::RemoveClosed
8444 } else if support_gtd_orders
8445 && order
8446 .expire_time()
8447 .is_some_and(|expire_ns| timestamp_ns >= expire_ns)
8448 {
8449 PostMatchOrderAction::Expire(clone_order(order))
8450 } else if matches!(
8451 order.order_type(),
8452 OrderType::TrailingStopMarket | OrderType::TrailingStopLimit
8453 ) {
8454 PostMatchOrderAction::UpdateTrailing(clone_order(order))
8455 } else {
8456 PostMatchOrderAction::NoMaintenance
8457 }
8458}
8459
8460fn write_filled_qty(order: &mut OrderAny, filled_qty: Quantity) {
8465 match order {
8466 OrderAny::Limit(o) => o.filled_qty = filled_qty,
8467 OrderAny::LimitIfTouched(o) => o.filled_qty = filled_qty,
8468 OrderAny::Market(o) => o.filled_qty = filled_qty,
8469 OrderAny::MarketIfTouched(o) => o.filled_qty = filled_qty,
8470 OrderAny::MarketToLimit(o) => o.filled_qty = filled_qty,
8471 OrderAny::StopLimit(o) => o.filled_qty = filled_qty,
8472 OrderAny::StopMarket(o) => o.filled_qty = filled_qty,
8473 OrderAny::TrailingStopLimit(o) => o.filled_qty = filled_qty,
8474 OrderAny::TrailingStopMarket(o) => o.filled_qty = filled_qty,
8475 }
8476}
8477
8478#[derive(Debug, Clone, Copy)]
8479struct BarTickSizes {
8480 open: Quantity,
8481 high: Quantity,
8482 low: Quantity,
8483 close: Quantity,
8484}
8485
8486impl BarTickSizes {
8487 fn from_volume(volume: Quantity, size_increment: Quantity) -> Self {
8488 let precision_diff = FIXED_PRECISION.saturating_sub(volume.precision);
8489 let scale = QuantityRaw::pow(10, u32::from(precision_diff));
8490 let units = volume.raw() / scale;
8491 let increment_units = (size_increment.raw() / scale).max(1);
8492 let rounded_units = (units / increment_units) * increment_units;
8493 let increments = rounded_units / increment_units;
8494 let zero = Quantity::zero(volume.precision);
8495 let size =
8496 |increments| Quantity::from_raw(increments * increment_units * scale, volume.precision);
8497
8498 match increments {
8499 0 => Self {
8500 open: zero,
8501 high: zero,
8502 low: zero,
8503 close: zero,
8504 },
8505
8506 1 => Self {
8508 open: zero,
8509 high: zero,
8510 low: zero,
8511 close: size(1),
8512 },
8513 2 => Self {
8514 open: zero,
8515 high: size(1),
8516 low: size(1),
8517 close: zero,
8518 },
8519 3 => {
8520 let path_size = size(1);
8521
8522 Self {
8523 open: path_size,
8524 high: path_size,
8525 low: path_size,
8526 close: zero,
8527 }
8528 }
8529 _ => {
8530 let path_increments = increments / 4;
8531 let close_increments = increments - (path_increments * 3);
8532 let path_size = size(path_increments);
8533
8534 Self {
8535 open: path_size,
8536 high: path_size,
8537 low: path_size,
8538 close: size(close_increments),
8539 }
8540 }
8541 }
8542 }
8543}