nautilus_backtest/matching_engine/
mod.rs

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