diff --git a/nautilus_core/backtest/src/matching_engine.rs b/nautilus_core/backtest/src/matching_engine.rs index 2c5ddc03f66..4a8300f3b01 100644 --- a/nautilus_core/backtest/src/matching_engine.rs +++ b/nautilus_core/backtest/src/matching_engine.rs @@ -19,28 +19,36 @@ #![allow(dead_code)] #![allow(unused_variables)] -use std::{collections::HashMap, rc::Rc}; +use std::{any::Any, collections::HashMap, rc::Rc}; use log::{debug, info}; use nautilus_common::{cache::Cache, msgbus::MessageBus}; -use nautilus_core::{nanos::UnixNanos, time::AtomicTime}; +use nautilus_core::{nanos::UnixNanos, time::AtomicTime, uuid::UUID4}; use nautilus_execution::matching_core::OrderMatchingCore; use nautilus_model::{ data::{ bar::{Bar, BarType}, delta::OrderBookDelta, }, - enums::{AccountType, BookType, MarketStatus, OmsType}, - identifiers::{AccountId, ClientOrderId, InstrumentId, TraderId, Venue}, + enums::{AccountType, BookType, LiquiditySide, MarketStatus, OmsType}, + events::order::{ + OrderAccepted, OrderCancelRejected, OrderCanceled, OrderExpired, OrderFilled, + OrderModifyRejected, OrderRejected, OrderTriggered, OrderUpdated, + }, + identifiers::{ + AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId, Venue, + VenueOrderId, + }, instruments::any::InstrumentAny, orderbook::book::OrderBook, orders::{ - any::{PassiveOrderAny, StopOrderAny}, + any::{OrderAny, PassiveOrderAny, StopOrderAny}, trailing_stop_limit::TrailingStopLimitOrder, trailing_stop_market::TrailingStopMarketOrder, }, - types::price::Price, + types::{currency::Currency, money::Money, price::Price, quantity::Quantity}, }; +use ustr::Ustr; pub struct OrderMatchingEngineConfig { pub bar_execution: bool, @@ -260,4 +268,241 @@ impl OrderMatchingEngine { fn update_trailing_stop_limit(&mut self, order: &TrailingStopLimitOrder) { todo!() } + + // -- IDENTIFIER GENERATORS ----------------------------------------------------- + + fn generate_trade_id(&mut self) -> TradeId { + self.execution_count += 1; + TradeId::new(self.generate_trade_id_str().as_str()).unwrap() + } + + fn generate_trade_id_str(&self) -> Ustr { + if self.config.use_random_ids { + UUID4::new().to_string().into() + } else { + format!("{}-{}-{}", self.venue, self.raw_id, self.execution_count).into() + } + } + + // -- EVENT GENERATORS ----------------------------------------------------- + + fn generate_order_rejected(&self, order: &OrderAny, reason: Ustr) { + let ts_now = self.clock.get_time_ns(); + let account_id = order + .account_id() + .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned()); + let event = OrderRejected::new( + order.trader_id(), + order.strategy_id(), + order.instrument_id(), + order.client_order_id(), + account_id, + reason, + UUID4::new(), + ts_now, + ts_now, + false, + ) + .unwrap(); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } + + fn generate_order_accepted(&self, order: &OrderAny, venue_order_id: VenueOrderId) { + let ts_now = self.clock.get_time_ns(); + let account_id = order + .account_id() + .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned()); + let event = OrderAccepted::new( + order.trader_id(), + order.strategy_id(), + order.instrument_id(), + order.client_order_id(), + venue_order_id, + account_id, + UUID4::new(), + ts_now, + ts_now, + false, + ) + .unwrap(); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } + + #[allow(clippy::too_many_arguments)] + fn generate_order_modify_rejected( + &self, + trader_id: TraderId, + strategy_id: StrategyId, + account_id: AccountId, + instrument_id: InstrumentId, + client_order_id: ClientOrderId, + venue_order_id: VenueOrderId, + reason: Ustr, + ) { + let ts_now = self.clock.get_time_ns(); + let event = OrderModifyRejected::new( + trader_id, + strategy_id, + instrument_id, + client_order_id, + reason, + UUID4::new(), + ts_now, + ts_now, + false, + Some(venue_order_id), + Some(account_id), + ); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } + + #[allow(clippy::too_many_arguments)] + fn generate_order_cancel_rejected( + &self, + trader_id: TraderId, + strategy_id: StrategyId, + account_id: AccountId, + instrument_id: InstrumentId, + client_order_id: ClientOrderId, + venue_order_id: VenueOrderId, + reason: Ustr, + ) { + let ts_now = self.clock.get_time_ns(); + let event = OrderCancelRejected::new( + trader_id, + strategy_id, + instrument_id, + client_order_id, + reason, + UUID4::new(), + ts_now, + ts_now, + false, + Some(venue_order_id), + Some(account_id), + ) + .unwrap(); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } + + fn generate_order_updated( + &self, + order: &OrderAny, + quantity: Quantity, + price: Price, + trigger_price: Price, + ) { + let ts_now = self.clock.get_time_ns(); + let event = OrderUpdated::new( + order.trader_id(), + order.strategy_id(), + order.instrument_id(), + order.client_order_id(), + quantity, + UUID4::new(), + ts_now, + ts_now, + false, + order.venue_order_id(), + order.account_id(), + Some(price), + Some(trigger_price), + ) + .unwrap(); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } + + fn generate_order_canceled(&self, order: &OrderAny, venue_order_id: VenueOrderId) { + let ts_now = self.clock.get_time_ns(); + let event = OrderCanceled::new( + order.trader_id(), + order.strategy_id(), + order.instrument_id(), + order.client_order_id(), + UUID4::new(), + ts_now, + ts_now, + false, + Some(venue_order_id), + order.account_id(), + ) + .unwrap(); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } + + fn generate_order_triggered(&self, order: &OrderAny) { + let ts_now = self.clock.get_time_ns(); + let event = OrderTriggered::new( + order.trader_id(), + order.strategy_id(), + order.instrument_id(), + order.client_order_id(), + UUID4::new(), + ts_now, + ts_now, + false, + order.venue_order_id(), + order.account_id(), + ) + .unwrap(); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } + + fn generate_order_expired(&self, order: &OrderAny) { + let ts_now = self.clock.get_time_ns(); + let event = OrderExpired::new( + order.trader_id(), + order.strategy_id(), + order.instrument_id(), + order.client_order_id(), + UUID4::new(), + ts_now, + ts_now, + false, + order.venue_order_id(), + order.account_id(), + ) + .unwrap(); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } + + #[allow(clippy::too_many_arguments)] + fn generate_order_filled( + &mut self, + order: &OrderAny, + venue_order_id: VenueOrderId, + venue_position_id: PositionId, + last_qty: Quantity, + last_px: Price, + quote_currency: Currency, + commission: Money, + liquidity_side: LiquiditySide, + ) { + let ts_now = self.clock.get_time_ns(); + let account_id = order + .account_id() + .unwrap_or(self.account_ids.get(&order.trader_id()).unwrap().to_owned()); + let event = OrderFilled::new( + order.trader_id(), + order.strategy_id(), + order.instrument_id(), + order.client_order_id(), + venue_order_id, + account_id, + self.generate_trade_id(), + order.order_side(), + order.order_type(), + last_qty, + last_px, + quote_currency, + liquidity_side, + UUID4::new(), + ts_now, + ts_now, + false, + Some(venue_position_id), + Some(commission), + ); + self.msgbus.send("ExecEngine.process", &event as &dyn Any); + } }