nautilus_model/orders/
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//! Order types for the trading domain model.
17
18pub mod any;
19pub mod builder;
20pub mod default;
21pub mod limit;
22pub mod limit_if_touched;
23pub mod list;
24pub mod market;
25pub mod market_if_touched;
26pub mod market_to_limit;
27pub mod stop_limit;
28pub mod stop_market;
29pub mod trailing_stop_limit;
30pub mod trailing_stop_market;
31
32#[cfg(feature = "stubs")]
33pub mod stubs;
34
35// Re-exports
36use enum_dispatch::enum_dispatch;
37use indexmap::IndexMap;
38use nautilus_core::{UUID4, UnixNanos};
39use rust_decimal::Decimal;
40use serde::{Deserialize, Serialize};
41use ustr::Ustr;
42
43pub use crate::orders::{
44    any::{LimitOrderAny, OrderAny, PassiveOrderAny, StopOrderAny},
45    builder::OrderTestBuilder,
46    limit::LimitOrder,
47    limit_if_touched::LimitIfTouchedOrder,
48    list::OrderList,
49    market::MarketOrder,
50    market_if_touched::MarketIfTouchedOrder,
51    market_to_limit::MarketToLimitOrder,
52    stop_limit::StopLimitOrder,
53    stop_market::StopMarketOrder,
54    trailing_stop_limit::TrailingStopLimitOrder,
55    trailing_stop_market::TrailingStopMarketOrder,
56};
57use crate::{
58    enums::{
59        ContingencyType, LiquiditySide, OrderSide, OrderSideSpecified, OrderStatus, OrderType,
60        PositionSide, TimeInForce, TrailingOffsetType, TriggerType,
61    },
62    events::{
63        OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated,
64        OrderEventAny, OrderExpired, OrderFilled, OrderInitialized, OrderModifyRejected,
65        OrderPendingCancel, OrderPendingUpdate, OrderRejected, OrderReleased, OrderSubmitted,
66        OrderTriggered, OrderUpdated,
67    },
68    identifiers::{
69        AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
70        StrategyId, Symbol, TradeId, TraderId, Venue, VenueOrderId,
71    },
72    orderbook::OwnBookOrder,
73    types::{Currency, Money, Price, Quantity},
74};
75
76#[allow(dead_code)] // TODO: Will be used
77const STOP_ORDER_TYPES: &[OrderType] = &[
78    OrderType::StopMarket,
79    OrderType::StopLimit,
80    OrderType::MarketIfTouched,
81    OrderType::LimitIfTouched,
82];
83
84#[allow(dead_code)] // TODO: Will be used
85const LIMIT_ORDER_TYPES: &[OrderType] = &[
86    OrderType::Limit,
87    OrderType::StopLimit,
88    OrderType::LimitIfTouched,
89    OrderType::MarketIfTouched,
90];
91
92#[allow(dead_code)] // TODO: Will be used
93const LOCAL_ACTIVE_ORDER_STATUS: &[OrderStatus] = &[
94    OrderStatus::Initialized,
95    OrderStatus::Emulated,
96    OrderStatus::Released,
97];
98
99#[derive(thiserror::Error, Debug)]
100pub enum OrderError {
101    #[error("Order not found: {0}")]
102    NotFound(ClientOrderId),
103    #[error("Order invariant failed: must have a side for this operation")]
104    NoOrderSide,
105    #[error("Invalid event for order type")]
106    InvalidOrderEvent,
107    #[error("Invalid order state transition")]
108    InvalidStateTransition,
109    #[error("Order was already initialized")]
110    AlreadyInitialized,
111    #[error("Order had no previous state")]
112    NoPreviousState,
113}
114
115#[must_use]
116pub fn ustr_indexmap_to_str(h: IndexMap<Ustr, Ustr>) -> IndexMap<String, String> {
117    h.into_iter()
118        .map(|(k, v)| (k.to_string(), v.to_string()))
119        .collect()
120}
121
122#[must_use]
123pub fn str_indexmap_to_ustr(h: IndexMap<String, String>) -> IndexMap<Ustr, Ustr> {
124    h.into_iter()
125        .map(|(k, v)| (Ustr::from(&k), Ustr::from(&v)))
126        .collect()
127}
128
129impl OrderStatus {
130    /// Transitions the order state machine based on the given `event`.
131    ///
132    /// # Errors
133    ///
134    /// Returns an error if the state transition is invalid from the current status.
135    #[rustfmt::skip]
136    pub fn transition(&mut self, event: &OrderEventAny) -> Result<Self, OrderError> {
137        let new_state = match (self, event) {
138            (Self::Initialized, OrderEventAny::Denied(_)) => Self::Denied,
139            (Self::Initialized, OrderEventAny::Emulated(_)) => Self::Emulated,  // Emulated orders
140            (Self::Initialized, OrderEventAny::Released(_)) => Self::Released,  // Emulated orders
141            (Self::Initialized, OrderEventAny::Submitted(_)) => Self::Submitted,
142            (Self::Initialized, OrderEventAny::Rejected(_)) => Self::Rejected,  // External orders
143            (Self::Initialized, OrderEventAny::Accepted(_)) => Self::Accepted,  // External orders
144            (Self::Initialized, OrderEventAny::Canceled(_)) => Self::Canceled,  // External orders
145            (Self::Initialized, OrderEventAny::Expired(_)) => Self::Expired,  // External orders
146            (Self::Initialized, OrderEventAny::Triggered(_)) => Self::Triggered, // External orders
147            (Self::Emulated, OrderEventAny::Canceled(_)) => Self::Canceled,  // Emulated orders
148            (Self::Emulated, OrderEventAny::Expired(_)) => Self::Expired,  // Emulated orders
149            (Self::Emulated, OrderEventAny::Released(_)) => Self::Released,  // Emulated orders
150            (Self::Released, OrderEventAny::Submitted(_)) => Self::Submitted,  // Emulated orders
151            (Self::Released, OrderEventAny::Denied(_)) => Self::Denied,  // Emulated orders
152            (Self::Released, OrderEventAny::Canceled(_)) => Self::Canceled,  // Execution algo
153            (Self::Submitted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
154            (Self::Submitted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
155            (Self::Submitted, OrderEventAny::Rejected(_)) => Self::Rejected,
156            (Self::Submitted, OrderEventAny::Canceled(_)) => Self::Canceled,  // FOK and IOC cases
157            (Self::Submitted, OrderEventAny::Accepted(_)) => Self::Accepted,
158            (Self::Submitted, OrderEventAny::Filled(_)) => Self::Filled,
159            (Self::Submitted, OrderEventAny::Updated(_)) => Self::Submitted,
160            (Self::Accepted, OrderEventAny::Rejected(_)) => Self::Rejected,  // StopLimit order
161            (Self::Accepted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
162            (Self::Accepted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
163            (Self::Accepted, OrderEventAny::Canceled(_)) => Self::Canceled,
164            (Self::Accepted, OrderEventAny::Triggered(_)) => Self::Triggered,
165            (Self::Accepted, OrderEventAny::Expired(_)) => Self::Expired,
166            (Self::Accepted, OrderEventAny::Filled(_)) => Self::Filled,
167            (Self::Accepted, OrderEventAny::Updated(_)) => Self::Accepted,  // Updates should preserve state
168            (Self::Canceled, OrderEventAny::Filled(_)) => Self::Filled,  // Real world possibility
169            (Self::PendingUpdate, OrderEventAny::Rejected(_)) => Self::Rejected,
170            (Self::PendingUpdate, OrderEventAny::Accepted(_)) => Self::Accepted,
171            (Self::PendingUpdate, OrderEventAny::Canceled(_)) => Self::Canceled,
172            (Self::PendingUpdate, OrderEventAny::Expired(_)) => Self::Expired,
173            (Self::PendingUpdate, OrderEventAny::Triggered(_)) => Self::Triggered,
174            (Self::PendingUpdate, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,  // Allow multiple requests
175            (Self::PendingUpdate, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
176            (Self::PendingUpdate, OrderEventAny::Filled(_)) => Self::Filled,
177            (Self::PendingCancel, OrderEventAny::Rejected(_)) => Self::Rejected,
178            (Self::PendingCancel, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,  // Allow multiple requests
179            (Self::PendingCancel, OrderEventAny::Canceled(_)) => Self::Canceled,
180            (Self::PendingCancel, OrderEventAny::Expired(_)) => Self::Expired,
181            (Self::PendingCancel, OrderEventAny::Accepted(_)) => Self::Accepted,  // Allow failed cancel requests
182            (Self::PendingCancel, OrderEventAny::Filled(_)) => Self::Filled,
183            (Self::Triggered, OrderEventAny::Rejected(_)) => Self::Rejected,
184            (Self::Triggered, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
185            (Self::Triggered, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
186            (Self::Triggered, OrderEventAny::Canceled(_)) => Self::Canceled,
187            (Self::Triggered, OrderEventAny::Expired(_)) => Self::Expired,
188            (Self::Triggered, OrderEventAny::Filled(_)) => Self::Filled,
189            (Self::PartiallyFilled, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
190            (Self::PartiallyFilled, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
191            (Self::PartiallyFilled, OrderEventAny::Canceled(_)) => Self::Canceled,
192            (Self::PartiallyFilled, OrderEventAny::Expired(_)) => Self::Expired,
193            (Self::PartiallyFilled, OrderEventAny::Filled(_)) => Self::Filled,
194            (Self::PartiallyFilled, OrderEventAny::Accepted(_)) => Self::Accepted,
195            _ => return Err(OrderError::InvalidStateTransition),
196        };
197        Ok(new_state)
198    }
199}
200
201#[enum_dispatch]
202pub trait Order: 'static + Send {
203    fn into_any(self) -> OrderAny;
204    fn status(&self) -> OrderStatus;
205    fn trader_id(&self) -> TraderId;
206    fn strategy_id(&self) -> StrategyId;
207    fn instrument_id(&self) -> InstrumentId;
208    fn symbol(&self) -> Symbol;
209    fn venue(&self) -> Venue;
210    fn client_order_id(&self) -> ClientOrderId;
211    fn venue_order_id(&self) -> Option<VenueOrderId>;
212    fn position_id(&self) -> Option<PositionId>;
213    fn account_id(&self) -> Option<AccountId>;
214    fn last_trade_id(&self) -> Option<TradeId>;
215    fn order_side(&self) -> OrderSide;
216    fn order_type(&self) -> OrderType;
217    fn quantity(&self) -> Quantity;
218    fn time_in_force(&self) -> TimeInForce;
219    fn expire_time(&self) -> Option<UnixNanos>;
220    fn price(&self) -> Option<Price>;
221    fn trigger_price(&self) -> Option<Price>;
222    fn trigger_type(&self) -> Option<TriggerType>;
223    fn liquidity_side(&self) -> Option<LiquiditySide>;
224    fn is_post_only(&self) -> bool;
225    fn is_reduce_only(&self) -> bool;
226    fn is_quote_quantity(&self) -> bool;
227    fn display_qty(&self) -> Option<Quantity>;
228    fn limit_offset(&self) -> Option<Decimal>;
229    fn trailing_offset(&self) -> Option<Decimal>;
230    fn trailing_offset_type(&self) -> Option<TrailingOffsetType>;
231    fn emulation_trigger(&self) -> Option<TriggerType>;
232    fn trigger_instrument_id(&self) -> Option<InstrumentId>;
233    fn contingency_type(&self) -> Option<ContingencyType>;
234    fn order_list_id(&self) -> Option<OrderListId>;
235    fn linked_order_ids(&self) -> Option<&[ClientOrderId]>;
236    fn parent_order_id(&self) -> Option<ClientOrderId>;
237    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId>;
238    fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>>;
239    fn exec_spawn_id(&self) -> Option<ClientOrderId>;
240    fn tags(&self) -> Option<&[Ustr]>;
241    fn filled_qty(&self) -> Quantity;
242    fn leaves_qty(&self) -> Quantity;
243    fn avg_px(&self) -> Option<f64>;
244    fn slippage(&self) -> Option<f64>;
245    fn init_id(&self) -> UUID4;
246    fn ts_init(&self) -> UnixNanos;
247    fn ts_submitted(&self) -> Option<UnixNanos>;
248    fn ts_accepted(&self) -> Option<UnixNanos>;
249    fn ts_closed(&self) -> Option<UnixNanos>;
250    fn ts_last(&self) -> UnixNanos;
251
252    fn order_side_specified(&self) -> OrderSideSpecified {
253        self.order_side().as_specified()
254    }
255    fn commissions(&self) -> &IndexMap<Currency, Money>;
256
257    /// Applies the `event` to the order.
258    ///
259    /// # Errors
260    ///
261    /// Returns an error if the event is invalid for the current order status.
262    fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError>;
263    fn update(&mut self, event: &OrderUpdated);
264
265    fn events(&self) -> Vec<&OrderEventAny>;
266
267    fn last_event(&self) -> &OrderEventAny {
268        // SAFETY: Unwrap safe as `Order` specification guarantees at least one event (`OrderInitialized`)
269        self.events().last().unwrap()
270    }
271
272    fn event_count(&self) -> usize {
273        self.events().len()
274    }
275
276    fn venue_order_ids(&self) -> Vec<&VenueOrderId>;
277
278    fn trade_ids(&self) -> Vec<&TradeId>;
279
280    fn has_price(&self) -> bool;
281
282    fn is_buy(&self) -> bool {
283        self.order_side() == OrderSide::Buy
284    }
285
286    fn is_sell(&self) -> bool {
287        self.order_side() == OrderSide::Sell
288    }
289
290    fn is_passive(&self) -> bool {
291        self.order_type() != OrderType::Market
292    }
293
294    fn is_aggressive(&self) -> bool {
295        self.order_type() == OrderType::Market
296    }
297
298    fn is_emulated(&self) -> bool {
299        self.status() == OrderStatus::Emulated
300    }
301
302    fn is_active_local(&self) -> bool {
303        matches!(
304            self.status(),
305            OrderStatus::Initialized | OrderStatus::Emulated | OrderStatus::Released
306        )
307    }
308
309    fn is_primary(&self) -> bool {
310        // TODO: Guarantee `exec_spawn_id` is some if `exec_algorithm_id` is some
311        self.exec_algorithm_id().is_some()
312            && self.client_order_id() == self.exec_spawn_id().unwrap()
313    }
314
315    fn is_secondary(&self) -> bool {
316        // TODO: Guarantee `exec_spawn_id` is some if `exec_algorithm_id` is some
317        self.exec_algorithm_id().is_some()
318            && self.client_order_id() != self.exec_spawn_id().unwrap()
319    }
320
321    fn is_contingency(&self) -> bool {
322        self.contingency_type().is_some()
323    }
324
325    fn is_parent_order(&self) -> bool {
326        match self.contingency_type() {
327            Some(c) => c == ContingencyType::Oto,
328            None => false,
329        }
330    }
331
332    fn is_child_order(&self) -> bool {
333        self.parent_order_id().is_some()
334    }
335
336    fn is_open(&self) -> bool {
337        if let Some(emulation_trigger) = self.emulation_trigger() {
338            if emulation_trigger != TriggerType::NoTrigger {
339                return false;
340            }
341        }
342
343        matches!(
344            self.status(),
345            OrderStatus::Accepted
346                | OrderStatus::Triggered
347                | OrderStatus::PendingCancel
348                | OrderStatus::PendingUpdate
349                | OrderStatus::PartiallyFilled
350        )
351    }
352
353    fn is_canceled(&self) -> bool {
354        self.status() == OrderStatus::Canceled
355    }
356
357    fn is_closed(&self) -> bool {
358        matches!(
359            self.status(),
360            OrderStatus::Denied
361                | OrderStatus::Rejected
362                | OrderStatus::Canceled
363                | OrderStatus::Expired
364                | OrderStatus::Filled
365        )
366    }
367
368    fn is_inflight(&self) -> bool {
369        if let Some(emulation_trigger) = self.emulation_trigger() {
370            if emulation_trigger != TriggerType::NoTrigger {
371                return false;
372            }
373        }
374
375        matches!(
376            self.status(),
377            OrderStatus::Submitted | OrderStatus::PendingCancel | OrderStatus::PendingUpdate
378        )
379    }
380
381    fn is_pending_update(&self) -> bool {
382        self.status() == OrderStatus::PendingUpdate
383    }
384
385    fn is_pending_cancel(&self) -> bool {
386        self.status() == OrderStatus::PendingCancel
387    }
388
389    fn is_spawned(&self) -> bool {
390        self.exec_spawn_id()
391            .is_some_and(|exec_spawn_id| exec_spawn_id != self.client_order_id())
392    }
393
394    fn to_own_book_order(&self) -> OwnBookOrder {
395        OwnBookOrder::new(
396            self.trader_id(),
397            self.client_order_id(),
398            self.venue_order_id(),
399            self.order_side().as_specified(),
400            self.price().expect("`OwnBookOrder` must have a price"), // TBD
401            self.quantity(),
402            self.order_type(),
403            self.time_in_force(),
404            self.status(),
405            self.ts_last(),
406            self.ts_submitted().unwrap_or_default(),
407            self.ts_accepted().unwrap_or_default(),
408            self.ts_init(),
409        )
410    }
411
412    fn is_triggered(&self) -> Option<bool>; // TODO: Temporary on trait
413    fn set_position_id(&mut self, position_id: Option<PositionId>);
414    fn set_quantity(&mut self, quantity: Quantity);
415    fn set_leaves_qty(&mut self, leaves_qty: Quantity);
416    fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>);
417    fn set_is_quote_quantity(&mut self, is_quote_quantity: bool);
418    fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide);
419    fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool;
420    fn previous_status(&self) -> Option<OrderStatus>;
421}
422
423impl<T> From<&T> for OrderInitialized
424where
425    T: Order,
426{
427    fn from(order: &T) -> Self {
428        Self {
429            trader_id: order.trader_id(),
430            strategy_id: order.strategy_id(),
431            instrument_id: order.instrument_id(),
432            client_order_id: order.client_order_id(),
433            order_side: order.order_side(),
434            order_type: order.order_type(),
435            quantity: order.quantity(),
436            price: order.price(),
437            trigger_price: order.trigger_price(),
438            trigger_type: order.trigger_type(),
439            time_in_force: order.time_in_force(),
440            expire_time: order.expire_time(),
441            post_only: order.is_post_only(),
442            reduce_only: order.is_reduce_only(),
443            quote_quantity: order.is_quote_quantity(),
444            display_qty: order.display_qty(),
445            limit_offset: order.limit_offset(),
446            trailing_offset: order.trailing_offset(),
447            trailing_offset_type: order.trailing_offset_type(),
448            emulation_trigger: order.emulation_trigger(),
449            trigger_instrument_id: order.trigger_instrument_id(),
450            contingency_type: order.contingency_type(),
451            order_list_id: order.order_list_id(),
452            linked_order_ids: order.linked_order_ids().map(|x| x.to_vec()),
453            parent_order_id: order.parent_order_id(),
454            exec_algorithm_id: order.exec_algorithm_id(),
455            exec_algorithm_params: order.exec_algorithm_params().map(|x| x.to_owned()),
456            exec_spawn_id: order.exec_spawn_id(),
457            tags: order.tags().map(|x| x.to_vec()),
458            event_id: order.init_id(),
459            ts_event: order.ts_init(),
460            ts_init: order.ts_init(),
461            reconciliation: false,
462        }
463    }
464}
465
466#[derive(Clone, Debug, Serialize, Deserialize)]
467pub struct OrderCore {
468    pub events: Vec<OrderEventAny>,
469    pub commissions: IndexMap<Currency, Money>,
470    pub venue_order_ids: Vec<VenueOrderId>,
471    pub trade_ids: Vec<TradeId>,
472    pub previous_status: Option<OrderStatus>,
473    pub status: OrderStatus,
474    pub trader_id: TraderId,
475    pub strategy_id: StrategyId,
476    pub instrument_id: InstrumentId,
477    pub client_order_id: ClientOrderId,
478    pub venue_order_id: Option<VenueOrderId>,
479    pub position_id: Option<PositionId>,
480    pub account_id: Option<AccountId>,
481    pub last_trade_id: Option<TradeId>,
482    pub side: OrderSide,
483    pub order_type: OrderType,
484    pub quantity: Quantity,
485    pub time_in_force: TimeInForce,
486    pub liquidity_side: Option<LiquiditySide>,
487    pub is_reduce_only: bool,
488    pub is_quote_quantity: bool,
489    pub emulation_trigger: Option<TriggerType>,
490    pub contingency_type: Option<ContingencyType>,
491    pub order_list_id: Option<OrderListId>,
492    pub linked_order_ids: Option<Vec<ClientOrderId>>,
493    pub parent_order_id: Option<ClientOrderId>,
494    pub exec_algorithm_id: Option<ExecAlgorithmId>,
495    pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
496    pub exec_spawn_id: Option<ClientOrderId>,
497    pub tags: Option<Vec<Ustr>>,
498    pub filled_qty: Quantity,
499    pub leaves_qty: Quantity,
500    pub avg_px: Option<f64>,
501    pub slippage: Option<f64>,
502    pub init_id: UUID4,
503    pub ts_init: UnixNanos,
504    pub ts_submitted: Option<UnixNanos>,
505    pub ts_accepted: Option<UnixNanos>,
506    pub ts_closed: Option<UnixNanos>,
507    pub ts_last: UnixNanos,
508}
509
510impl OrderCore {
511    /// Creates a new [`OrderCore`] instance.
512    pub fn new(init: OrderInitialized) -> Self {
513        let events: Vec<OrderEventAny> = vec![OrderEventAny::Initialized(init.clone())];
514        Self {
515            events,
516            commissions: IndexMap::new(),
517            venue_order_ids: Vec::new(),
518            trade_ids: Vec::new(),
519            previous_status: None,
520            status: OrderStatus::Initialized,
521            trader_id: init.trader_id,
522            strategy_id: init.strategy_id,
523            instrument_id: init.instrument_id,
524            client_order_id: init.client_order_id,
525            venue_order_id: None,
526            position_id: None,
527            account_id: None,
528            last_trade_id: None,
529            side: init.order_side,
530            order_type: init.order_type,
531            quantity: init.quantity,
532            time_in_force: init.time_in_force,
533            liquidity_side: Some(LiquiditySide::NoLiquiditySide),
534            is_reduce_only: init.reduce_only,
535            is_quote_quantity: init.quote_quantity,
536            emulation_trigger: init.emulation_trigger.or(Some(TriggerType::NoTrigger)),
537            contingency_type: init
538                .contingency_type
539                .or(Some(ContingencyType::NoContingency)),
540            order_list_id: init.order_list_id,
541            linked_order_ids: init.linked_order_ids,
542            parent_order_id: init.parent_order_id,
543            exec_algorithm_id: init.exec_algorithm_id,
544            exec_algorithm_params: init.exec_algorithm_params,
545            exec_spawn_id: init.exec_spawn_id,
546            tags: init.tags,
547            filled_qty: Quantity::zero(init.quantity.precision),
548            leaves_qty: init.quantity,
549            avg_px: None,
550            slippage: None,
551            init_id: init.event_id,
552            ts_init: init.ts_event,
553            ts_submitted: None,
554            ts_accepted: None,
555            ts_closed: None,
556            ts_last: init.ts_event,
557        }
558    }
559
560    /// Applies the `event` to the order.
561    ///
562    /// # Errors
563    ///
564    /// Returns an error if the event is invalid for the current order status.
565    pub fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
566        assert_eq!(self.client_order_id, event.client_order_id());
567        assert_eq!(self.strategy_id, event.strategy_id());
568
569        let new_status = self.status.transition(&event)?;
570        self.previous_status = Some(self.status);
571        self.status = new_status;
572
573        match &event {
574            OrderEventAny::Initialized(_) => return Err(OrderError::AlreadyInitialized),
575            OrderEventAny::Denied(event) => self.denied(event),
576            OrderEventAny::Emulated(event) => self.emulated(event),
577            OrderEventAny::Released(event) => self.released(event),
578            OrderEventAny::Submitted(event) => self.submitted(event),
579            OrderEventAny::Rejected(event) => self.rejected(event),
580            OrderEventAny::Accepted(event) => self.accepted(event),
581            OrderEventAny::PendingUpdate(event) => self.pending_update(event),
582            OrderEventAny::PendingCancel(event) => self.pending_cancel(event),
583            OrderEventAny::ModifyRejected(event) => self.modify_rejected(event),
584            OrderEventAny::CancelRejected(event) => self.cancel_rejected(event),
585            OrderEventAny::Updated(event) => self.updated(event),
586            OrderEventAny::Triggered(event) => self.triggered(event),
587            OrderEventAny::Canceled(event) => self.canceled(event),
588            OrderEventAny::Expired(event) => self.expired(event),
589            OrderEventAny::Filled(event) => self.filled(event),
590        }
591
592        self.ts_last = event.ts_event();
593        self.events.push(event);
594        Ok(())
595    }
596
597    fn denied(&mut self, event: &OrderDenied) {
598        self.ts_closed = Some(event.ts_event);
599    }
600
601    fn emulated(&self, _event: &OrderEmulated) {
602        // Do nothing else
603    }
604
605    fn released(&mut self, _event: &OrderReleased) {
606        self.emulation_trigger = None;
607    }
608
609    fn submitted(&mut self, event: &OrderSubmitted) {
610        self.account_id = Some(event.account_id);
611        self.ts_submitted = Some(event.ts_event);
612    }
613
614    fn accepted(&mut self, event: &OrderAccepted) {
615        self.venue_order_id = Some(event.venue_order_id);
616        self.ts_accepted = Some(event.ts_event);
617    }
618
619    fn rejected(&mut self, event: &OrderRejected) {
620        self.ts_closed = Some(event.ts_event);
621    }
622
623    fn pending_update(&self, _event: &OrderPendingUpdate) {
624        // Do nothing else
625    }
626
627    fn pending_cancel(&self, _event: &OrderPendingCancel) {
628        // Do nothing else
629    }
630
631    fn modify_rejected(&mut self, _event: &OrderModifyRejected) {
632        self.status = self
633            .previous_status
634            .unwrap_or_else(|| panic!("{}", OrderError::NoPreviousState));
635    }
636
637    fn cancel_rejected(&mut self, _event: &OrderCancelRejected) {
638        self.status = self
639            .previous_status
640            .unwrap_or_else(|| panic!("{}", OrderError::NoPreviousState));
641    }
642
643    fn triggered(&mut self, _event: &OrderTriggered) {}
644
645    fn canceled(&mut self, event: &OrderCanceled) {
646        self.ts_closed = Some(event.ts_event);
647    }
648
649    fn expired(&mut self, event: &OrderExpired) {
650        self.ts_closed = Some(event.ts_event);
651    }
652
653    fn updated(&mut self, event: &OrderUpdated) {
654        if let Some(venue_order_id) = &event.venue_order_id {
655            if self.venue_order_id.is_none()
656                || venue_order_id != self.venue_order_id.as_ref().unwrap()
657            {
658                self.venue_order_id = Some(*venue_order_id);
659                self.venue_order_ids.push(*venue_order_id);
660            }
661        }
662    }
663
664    fn filled(&mut self, event: &OrderFilled) {
665        if self.filled_qty + event.last_qty < self.quantity {
666            self.status = OrderStatus::PartiallyFilled;
667        } else {
668            self.status = OrderStatus::Filled;
669            self.ts_closed = Some(event.ts_event);
670        }
671
672        self.venue_order_id = Some(event.venue_order_id);
673        self.position_id = event.position_id;
674        self.trade_ids.push(event.trade_id);
675        self.last_trade_id = Some(event.trade_id);
676        self.liquidity_side = Some(event.liquidity_side);
677        self.filled_qty += event.last_qty;
678        self.leaves_qty -= event.last_qty;
679        self.ts_last = event.ts_event;
680        if self.ts_accepted.is_none() {
681            // Set ts_accepted to time of first fill if not previously set
682            self.ts_accepted = Some(event.ts_event);
683        }
684
685        self.set_avg_px(event.last_qty, event.last_px);
686    }
687
688    fn set_avg_px(&mut self, last_qty: Quantity, last_px: Price) {
689        if self.avg_px.is_none() {
690            self.avg_px = Some(last_px.as_f64());
691        }
692
693        let filled_qty = self.filled_qty.as_f64();
694        let total_qty = filled_qty + last_qty.as_f64();
695
696        let avg_px = self
697            .avg_px
698            .unwrap()
699            .mul_add(filled_qty, last_px.as_f64() * last_qty.as_f64())
700            / total_qty;
701        self.avg_px = Some(avg_px);
702    }
703
704    pub fn set_slippage(&mut self, price: Price) {
705        self.slippage = self.avg_px.and_then(|avg_px| {
706            let current_price = price.as_f64();
707            match self.side {
708                OrderSide::Buy if avg_px > current_price => Some(avg_px - current_price),
709                OrderSide::Sell if avg_px < current_price => Some(current_price - avg_px),
710                _ => None,
711            }
712        });
713    }
714
715    #[must_use]
716    pub fn opposite_side(side: OrderSide) -> OrderSide {
717        match side {
718            OrderSide::Buy => OrderSide::Sell,
719            OrderSide::Sell => OrderSide::Buy,
720            OrderSide::NoOrderSide => OrderSide::NoOrderSide,
721        }
722    }
723
724    #[must_use]
725    pub fn closing_side(side: PositionSide) -> OrderSide {
726        match side {
727            PositionSide::Long => OrderSide::Sell,
728            PositionSide::Short => OrderSide::Buy,
729            PositionSide::Flat => OrderSide::NoOrderSide,
730            PositionSide::NoPositionSide => OrderSide::NoOrderSide,
731        }
732    }
733
734    #[must_use]
735    pub fn signed_decimal_qty(&self) -> Decimal {
736        match self.side {
737            OrderSide::Buy => self.quantity.as_decimal(),
738            OrderSide::Sell => -self.quantity.as_decimal(),
739            _ => panic!("Invalid order side"),
740        }
741    }
742
743    #[must_use]
744    pub fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
745        if side == PositionSide::Flat {
746            return false;
747        }
748
749        match (self.side, side) {
750            (OrderSide::Buy, PositionSide::Long) => false,
751            (OrderSide::Buy, PositionSide::Short) => self.leaves_qty <= position_qty,
752            (OrderSide::Sell, PositionSide::Short) => false,
753            (OrderSide::Sell, PositionSide::Long) => self.leaves_qty <= position_qty,
754            _ => true,
755        }
756    }
757
758    #[must_use]
759    pub fn commission(&self, currency: &Currency) -> Option<Money> {
760        self.commissions.get(currency).copied()
761    }
762
763    #[must_use]
764    pub fn commissions(&self) -> IndexMap<Currency, Money> {
765        self.commissions.clone()
766    }
767
768    #[must_use]
769    pub fn commissions_vec(&self) -> Vec<Money> {
770        self.commissions.values().cloned().collect()
771    }
772
773    #[must_use]
774    pub fn init_event(&self) -> Option<OrderEventAny> {
775        self.events.first().cloned()
776    }
777}
778
779////////////////////////////////////////////////////////////////////////////////
780// Tests
781////////////////////////////////////////////////////////////////////////////////
782#[cfg(test)]
783mod tests {
784    use rstest::rstest;
785    use rust_decimal_macros::dec;
786
787    use super::*;
788    use crate::{
789        enums::{OrderSide, OrderStatus, PositionSide},
790        events::order::{
791            accepted::OrderAcceptedBuilder, denied::OrderDeniedBuilder, filled::OrderFilledBuilder,
792            initialized::OrderInitializedBuilder, submitted::OrderSubmittedBuilder,
793        },
794        orders::MarketOrder,
795    };
796
797    // TODO: WIP
798    // fn test_initialize_market_order() {
799    //     let order = MarketOrder::default();
800    //     assert_eq!(order.events().len(), 1);
801    //     assert_eq!(
802    //         stringify!(order.events().get(0)),
803    //         stringify!(OrderInitialized)
804    //     );
805    // }
806
807    #[rstest]
808    #[case(OrderSide::Buy, OrderSide::Sell)]
809    #[case(OrderSide::Sell, OrderSide::Buy)]
810    #[case(OrderSide::NoOrderSide, OrderSide::NoOrderSide)]
811    fn test_order_opposite_side(#[case] order_side: OrderSide, #[case] expected_side: OrderSide) {
812        let result = OrderCore::opposite_side(order_side);
813        assert_eq!(result, expected_side);
814    }
815
816    #[rstest]
817    #[case(PositionSide::Long, OrderSide::Sell)]
818    #[case(PositionSide::Short, OrderSide::Buy)]
819    #[case(PositionSide::NoPositionSide, OrderSide::NoOrderSide)]
820    fn test_closing_side(#[case] position_side: PositionSide, #[case] expected_side: OrderSide) {
821        let result = OrderCore::closing_side(position_side);
822        assert_eq!(result, expected_side);
823    }
824
825    #[rstest]
826    #[case(OrderSide::Buy, dec!(10_000))]
827    #[case(OrderSide::Sell, dec!(-10_000))]
828    fn test_signed_decimal_qty(#[case] order_side: OrderSide, #[case] expected: Decimal) {
829        let order: MarketOrder = OrderInitializedBuilder::default()
830            .order_side(order_side)
831            .quantity(Quantity::from(10_000))
832            .build()
833            .unwrap()
834            .into();
835
836        let result = order.signed_decimal_qty();
837        assert_eq!(result, expected);
838    }
839
840    #[rustfmt::skip]
841    #[rstest]
842    #[case(OrderSide::Buy, Quantity::from(100), PositionSide::Long, Quantity::from(50), false)]
843    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(50), true)]
844    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(100), true)]
845    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
846    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
847    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(50), true)]
848    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(100), true)]
849    #[case(OrderSide::Sell, Quantity::from(100), PositionSide::Short, Quantity::from(50), false)]
850    fn test_would_reduce_only(
851        #[case] order_side: OrderSide,
852        #[case] order_qty: Quantity,
853        #[case] position_side: PositionSide,
854        #[case] position_qty: Quantity,
855        #[case] expected: bool,
856    ) {
857        let order: MarketOrder = OrderInitializedBuilder::default()
858            .order_side(order_side)
859            .quantity(order_qty)
860            .build()
861            .unwrap()
862            .into();
863
864        assert_eq!(
865            order.would_reduce_only(position_side, position_qty),
866            expected
867        );
868    }
869
870    #[rstest]
871    fn test_order_state_transition_denied() {
872        let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
873        let denied = OrderDeniedBuilder::default().build().unwrap();
874        let event = OrderEventAny::Denied(denied);
875
876        order.apply(event.clone()).unwrap();
877
878        assert_eq!(order.status, OrderStatus::Denied);
879        assert!(order.is_closed());
880        assert!(!order.is_open());
881        assert_eq!(order.event_count(), 2);
882        assert_eq!(order.last_event(), &event);
883    }
884
885    #[rstest]
886    fn test_order_life_cycle_to_filled() {
887        let init = OrderInitializedBuilder::default().build().unwrap();
888        let submitted = OrderSubmittedBuilder::default().build().unwrap();
889        let accepted = OrderAcceptedBuilder::default().build().unwrap();
890        let filled = OrderFilledBuilder::default().build().unwrap();
891
892        let mut order: MarketOrder = init.clone().into();
893        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
894        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
895        order.apply(OrderEventAny::Filled(filled)).unwrap();
896
897        assert_eq!(order.client_order_id, init.client_order_id);
898        assert_eq!(order.status(), OrderStatus::Filled);
899        assert_eq!(order.filled_qty(), Quantity::from(100_000));
900        assert_eq!(order.leaves_qty(), Quantity::from(0));
901        assert_eq!(order.avg_px(), Some(1.0));
902        assert!(!order.is_open());
903        assert!(order.is_closed());
904        assert_eq!(order.commission(&Currency::USD()), None);
905        assert_eq!(order.commissions(), &IndexMap::new());
906    }
907}