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(any(test, 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    #[error("{0}")]
114    Invariant(#[from] anyhow::Error),
115}
116
117/// Converts an IndexMap with `Ustr` keys and values to `String` keys and values.
118#[must_use]
119pub fn ustr_indexmap_to_str(h: IndexMap<Ustr, Ustr>) -> IndexMap<String, String> {
120    h.into_iter()
121        .map(|(k, v)| (k.to_string(), v.to_string()))
122        .collect()
123}
124
125/// Converts an IndexMap with `String` keys and values to `Ustr` keys and values.
126#[must_use]
127pub fn str_indexmap_to_ustr(h: IndexMap<String, String>) -> IndexMap<Ustr, Ustr> {
128    h.into_iter()
129        .map(|(k, v)| (Ustr::from(&k), Ustr::from(&v)))
130        .collect()
131}
132
133#[inline]
134pub(crate) fn check_display_qty(
135    display_qty: Option<Quantity>,
136    quantity: Quantity,
137) -> Result<(), OrderError> {
138    if let Some(q) = display_qty
139        && q > quantity
140    {
141        return Err(OrderError::Invariant(anyhow::anyhow!(
142            "`display_qty` may not exceed `quantity`"
143        )));
144    }
145    Ok(())
146}
147
148#[inline]
149pub(crate) fn check_time_in_force(
150    time_in_force: TimeInForce,
151    expire_time: Option<UnixNanos>,
152) -> Result<(), OrderError> {
153    if time_in_force == TimeInForce::Gtd && expire_time.unwrap_or_default() == 0 {
154        return Err(OrderError::Invariant(anyhow::anyhow!(
155            "`expire_time` is required for `GTD` order"
156        )));
157    }
158    Ok(())
159}
160
161impl OrderStatus {
162    /// Transitions the order state machine based on the given `event`.
163    ///
164    /// # Errors
165    ///
166    /// Returns an error if the state transition is invalid from the current status.
167    #[rustfmt::skip]
168    pub fn transition(&mut self, event: &OrderEventAny) -> Result<Self, OrderError> {
169        let new_state = match (self, event) {
170            (Self::Initialized, OrderEventAny::Denied(_)) => Self::Denied,
171            (Self::Initialized, OrderEventAny::Emulated(_)) => Self::Emulated,  // Emulated orders
172            (Self::Initialized, OrderEventAny::Released(_)) => Self::Released,  // Emulated orders
173            (Self::Initialized, OrderEventAny::Submitted(_)) => Self::Submitted,
174            (Self::Initialized, OrderEventAny::Rejected(_)) => Self::Rejected,  // External orders
175            (Self::Initialized, OrderEventAny::Accepted(_)) => Self::Accepted,  // External orders
176            (Self::Initialized, OrderEventAny::Canceled(_)) => Self::Canceled,  // External orders
177            (Self::Initialized, OrderEventAny::Expired(_)) => Self::Expired,  // External orders
178            (Self::Initialized, OrderEventAny::Triggered(_)) => Self::Triggered, // External orders
179            (Self::Emulated, OrderEventAny::Canceled(_)) => Self::Canceled,  // Emulated orders
180            (Self::Emulated, OrderEventAny::Expired(_)) => Self::Expired,  // Emulated orders
181            (Self::Emulated, OrderEventAny::Released(_)) => Self::Released,  // Emulated orders
182            (Self::Released, OrderEventAny::Submitted(_)) => Self::Submitted,  // Emulated orders
183            (Self::Released, OrderEventAny::Denied(_)) => Self::Denied,  // Emulated orders
184            (Self::Released, OrderEventAny::Canceled(_)) => Self::Canceled,  // Execution algo
185            (Self::Submitted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
186            (Self::Submitted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
187            (Self::Submitted, OrderEventAny::Rejected(_)) => Self::Rejected,
188            (Self::Submitted, OrderEventAny::Canceled(_)) => Self::Canceled,  // FOK and IOC cases
189            (Self::Submitted, OrderEventAny::Accepted(_)) => Self::Accepted,
190            (Self::Submitted, OrderEventAny::Filled(_)) => Self::Filled,
191            (Self::Submitted, OrderEventAny::Updated(_)) => Self::Submitted,
192            (Self::Accepted, OrderEventAny::Rejected(_)) => Self::Rejected,  // StopLimit order
193            (Self::Accepted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
194            (Self::Accepted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
195            (Self::Accepted, OrderEventAny::Canceled(_)) => Self::Canceled,
196            (Self::Accepted, OrderEventAny::Triggered(_)) => Self::Triggered,
197            (Self::Accepted, OrderEventAny::Expired(_)) => Self::Expired,
198            (Self::Accepted, OrderEventAny::Filled(_)) => Self::Filled,
199            (Self::Accepted, OrderEventAny::Updated(_)) => Self::Accepted,  // Updates should preserve state
200            (Self::Canceled, OrderEventAny::Filled(_)) => Self::Filled,  // Real world possibility
201            (Self::PendingUpdate, OrderEventAny::Rejected(_)) => Self::Rejected,
202            (Self::PendingUpdate, OrderEventAny::Accepted(_)) => Self::Accepted,
203            (Self::PendingUpdate, OrderEventAny::Canceled(_)) => Self::Canceled,
204            (Self::PendingUpdate, OrderEventAny::Expired(_)) => Self::Expired,
205            (Self::PendingUpdate, OrderEventAny::Triggered(_)) => Self::Triggered,
206            (Self::PendingUpdate, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,  // Allow multiple requests
207            (Self::PendingUpdate, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
208            (Self::PendingUpdate, OrderEventAny::Filled(_)) => Self::Filled,
209            (Self::PendingCancel, OrderEventAny::Rejected(_)) => Self::Rejected,
210            (Self::PendingCancel, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,  // Allow multiple requests
211            (Self::PendingCancel, OrderEventAny::Canceled(_)) => Self::Canceled,
212            (Self::PendingCancel, OrderEventAny::Expired(_)) => Self::Expired,
213            (Self::PendingCancel, OrderEventAny::Accepted(_)) => Self::Accepted,  // Allow failed cancel requests
214            (Self::PendingCancel, OrderEventAny::Filled(_)) => Self::Filled,
215            (Self::Triggered, OrderEventAny::Rejected(_)) => Self::Rejected,
216            (Self::Triggered, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
217            (Self::Triggered, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
218            (Self::Triggered, OrderEventAny::Canceled(_)) => Self::Canceled,
219            (Self::Triggered, OrderEventAny::Expired(_)) => Self::Expired,
220            (Self::Triggered, OrderEventAny::Filled(_)) => Self::Filled,
221            (Self::PartiallyFilled, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
222            (Self::PartiallyFilled, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
223            (Self::PartiallyFilled, OrderEventAny::Canceled(_)) => Self::Canceled,
224            (Self::PartiallyFilled, OrderEventAny::Expired(_)) => Self::Expired,
225            (Self::PartiallyFilled, OrderEventAny::Filled(_)) => Self::Filled,
226            (Self::PartiallyFilled, OrderEventAny::Accepted(_)) => Self::Accepted,
227            _ => return Err(OrderError::InvalidStateTransition),
228        };
229        Ok(new_state)
230    }
231}
232
233#[enum_dispatch]
234pub trait Order: 'static + Send {
235    fn into_any(self) -> OrderAny;
236    fn status(&self) -> OrderStatus;
237    fn trader_id(&self) -> TraderId;
238    fn strategy_id(&self) -> StrategyId;
239    fn instrument_id(&self) -> InstrumentId;
240    fn symbol(&self) -> Symbol;
241    fn venue(&self) -> Venue;
242    fn client_order_id(&self) -> ClientOrderId;
243    fn venue_order_id(&self) -> Option<VenueOrderId>;
244    fn position_id(&self) -> Option<PositionId>;
245    fn account_id(&self) -> Option<AccountId>;
246    fn last_trade_id(&self) -> Option<TradeId>;
247    fn order_side(&self) -> OrderSide;
248    fn order_type(&self) -> OrderType;
249    fn quantity(&self) -> Quantity;
250    fn time_in_force(&self) -> TimeInForce;
251    fn expire_time(&self) -> Option<UnixNanos>;
252    fn price(&self) -> Option<Price>;
253    fn trigger_price(&self) -> Option<Price>;
254    fn activation_price(&self) -> Option<Price> {
255        None
256    }
257    fn trigger_type(&self) -> Option<TriggerType>;
258    fn liquidity_side(&self) -> Option<LiquiditySide>;
259    fn is_post_only(&self) -> bool;
260    fn is_reduce_only(&self) -> bool;
261    fn is_quote_quantity(&self) -> bool;
262    fn display_qty(&self) -> Option<Quantity>;
263    fn limit_offset(&self) -> Option<Decimal>;
264    fn trailing_offset(&self) -> Option<Decimal>;
265    fn trailing_offset_type(&self) -> Option<TrailingOffsetType>;
266    fn emulation_trigger(&self) -> Option<TriggerType>;
267    fn trigger_instrument_id(&self) -> Option<InstrumentId>;
268    fn contingency_type(&self) -> Option<ContingencyType>;
269    fn order_list_id(&self) -> Option<OrderListId>;
270    fn linked_order_ids(&self) -> Option<&[ClientOrderId]>;
271    fn parent_order_id(&self) -> Option<ClientOrderId>;
272    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId>;
273    fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>>;
274    fn exec_spawn_id(&self) -> Option<ClientOrderId>;
275    fn tags(&self) -> Option<&[Ustr]>;
276    fn filled_qty(&self) -> Quantity;
277    fn leaves_qty(&self) -> Quantity;
278    fn avg_px(&self) -> Option<f64>;
279    fn slippage(&self) -> Option<f64>;
280    fn init_id(&self) -> UUID4;
281    fn ts_init(&self) -> UnixNanos;
282    fn ts_submitted(&self) -> Option<UnixNanos>;
283    fn ts_accepted(&self) -> Option<UnixNanos>;
284    fn ts_closed(&self) -> Option<UnixNanos>;
285    fn ts_last(&self) -> UnixNanos;
286
287    fn order_side_specified(&self) -> OrderSideSpecified {
288        self.order_side().as_specified()
289    }
290    fn commissions(&self) -> &IndexMap<Currency, Money>;
291
292    /// Applies the `event` to the order.
293    ///
294    /// # Errors
295    ///
296    /// Returns an error if the event is invalid for the current order status.
297    fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError>;
298    fn update(&mut self, event: &OrderUpdated);
299
300    fn events(&self) -> Vec<&OrderEventAny>;
301
302    fn last_event(&self) -> &OrderEventAny {
303        // SAFETY: Unwrap safe as `Order` specification guarantees at least one event (`OrderInitialized`)
304        self.events().last().unwrap()
305    }
306
307    fn event_count(&self) -> usize {
308        self.events().len()
309    }
310
311    fn venue_order_ids(&self) -> Vec<&VenueOrderId>;
312
313    fn trade_ids(&self) -> Vec<&TradeId>;
314
315    fn has_price(&self) -> bool;
316
317    fn is_buy(&self) -> bool {
318        self.order_side() == OrderSide::Buy
319    }
320
321    fn is_sell(&self) -> bool {
322        self.order_side() == OrderSide::Sell
323    }
324
325    fn is_passive(&self) -> bool {
326        self.order_type() != OrderType::Market
327    }
328
329    fn is_aggressive(&self) -> bool {
330        self.order_type() == OrderType::Market
331    }
332
333    fn is_emulated(&self) -> bool {
334        self.status() == OrderStatus::Emulated
335    }
336
337    fn is_active_local(&self) -> bool {
338        matches!(
339            self.status(),
340            OrderStatus::Initialized | OrderStatus::Emulated | OrderStatus::Released
341        )
342    }
343
344    fn is_primary(&self) -> bool {
345        // TODO: Guarantee `exec_spawn_id` is some if `exec_algorithm_id` is some
346        self.exec_algorithm_id().is_some()
347            && self.client_order_id() == self.exec_spawn_id().unwrap()
348    }
349
350    fn is_secondary(&self) -> bool {
351        // TODO: Guarantee `exec_spawn_id` is some if `exec_algorithm_id` is some
352        self.exec_algorithm_id().is_some()
353            && self.client_order_id() != self.exec_spawn_id().unwrap()
354    }
355
356    fn is_contingency(&self) -> bool {
357        self.contingency_type().is_some()
358    }
359
360    fn is_parent_order(&self) -> bool {
361        match self.contingency_type() {
362            Some(c) => c == ContingencyType::Oto,
363            None => false,
364        }
365    }
366
367    fn is_child_order(&self) -> bool {
368        self.parent_order_id().is_some()
369    }
370
371    fn is_open(&self) -> bool {
372        if let Some(emulation_trigger) = self.emulation_trigger()
373            && emulation_trigger != TriggerType::NoTrigger
374        {
375            return false;
376        }
377
378        matches!(
379            self.status(),
380            OrderStatus::Accepted
381                | OrderStatus::Triggered
382                | OrderStatus::PendingCancel
383                | OrderStatus::PendingUpdate
384                | OrderStatus::PartiallyFilled
385        )
386    }
387
388    fn is_canceled(&self) -> bool {
389        self.status() == OrderStatus::Canceled
390    }
391
392    fn is_closed(&self) -> bool {
393        matches!(
394            self.status(),
395            OrderStatus::Denied
396                | OrderStatus::Rejected
397                | OrderStatus::Canceled
398                | OrderStatus::Expired
399                | OrderStatus::Filled
400        )
401    }
402
403    fn is_inflight(&self) -> bool {
404        if let Some(emulation_trigger) = self.emulation_trigger()
405            && emulation_trigger != TriggerType::NoTrigger
406        {
407            return false;
408        }
409
410        matches!(
411            self.status(),
412            OrderStatus::Submitted | OrderStatus::PendingCancel | OrderStatus::PendingUpdate
413        )
414    }
415
416    fn is_pending_update(&self) -> bool {
417        self.status() == OrderStatus::PendingUpdate
418    }
419
420    fn is_pending_cancel(&self) -> bool {
421        self.status() == OrderStatus::PendingCancel
422    }
423
424    fn is_spawned(&self) -> bool {
425        self.exec_spawn_id()
426            .is_some_and(|exec_spawn_id| exec_spawn_id != self.client_order_id())
427    }
428
429    fn to_own_book_order(&self) -> OwnBookOrder {
430        OwnBookOrder::new(
431            self.trader_id(),
432            self.client_order_id(),
433            self.venue_order_id(),
434            self.order_side().as_specified(),
435            self.price().expect("`OwnBookOrder` must have a price"), // TBD
436            self.quantity(),
437            self.order_type(),
438            self.time_in_force(),
439            self.status(),
440            self.ts_last(),
441            self.ts_submitted().unwrap_or_default(),
442            self.ts_accepted().unwrap_or_default(),
443            self.ts_init(),
444        )
445    }
446
447    fn is_triggered(&self) -> Option<bool>; // TODO: Temporary on trait
448    fn set_position_id(&mut self, position_id: Option<PositionId>);
449    fn set_quantity(&mut self, quantity: Quantity);
450    fn set_leaves_qty(&mut self, leaves_qty: Quantity);
451    fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>);
452    fn set_is_quote_quantity(&mut self, is_quote_quantity: bool);
453    fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide);
454    fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool;
455    fn previous_status(&self) -> Option<OrderStatus>;
456}
457
458impl<T> From<&T> for OrderInitialized
459where
460    T: Order,
461{
462    fn from(order: &T) -> Self {
463        Self {
464            trader_id: order.trader_id(),
465            strategy_id: order.strategy_id(),
466            instrument_id: order.instrument_id(),
467            client_order_id: order.client_order_id(),
468            order_side: order.order_side(),
469            order_type: order.order_type(),
470            quantity: order.quantity(),
471            price: order.price(),
472            trigger_price: order.trigger_price(),
473            trigger_type: order.trigger_type(),
474            time_in_force: order.time_in_force(),
475            expire_time: order.expire_time(),
476            post_only: order.is_post_only(),
477            reduce_only: order.is_reduce_only(),
478            quote_quantity: order.is_quote_quantity(),
479            display_qty: order.display_qty(),
480            limit_offset: order.limit_offset(),
481            trailing_offset: order.trailing_offset(),
482            trailing_offset_type: order.trailing_offset_type(),
483            emulation_trigger: order.emulation_trigger(),
484            trigger_instrument_id: order.trigger_instrument_id(),
485            contingency_type: order.contingency_type(),
486            order_list_id: order.order_list_id(),
487            linked_order_ids: order.linked_order_ids().map(|x| x.to_vec()),
488            parent_order_id: order.parent_order_id(),
489            exec_algorithm_id: order.exec_algorithm_id(),
490            exec_algorithm_params: order.exec_algorithm_params().map(|x| x.to_owned()),
491            exec_spawn_id: order.exec_spawn_id(),
492            tags: order.tags().map(|x| x.to_vec()),
493            event_id: order.init_id(),
494            ts_event: order.ts_init(),
495            ts_init: order.ts_init(),
496            reconciliation: false,
497        }
498    }
499}
500
501#[derive(Clone, Debug, Serialize, Deserialize)]
502pub struct OrderCore {
503    pub events: Vec<OrderEventAny>,
504    pub commissions: IndexMap<Currency, Money>,
505    pub venue_order_ids: Vec<VenueOrderId>,
506    pub trade_ids: Vec<TradeId>,
507    pub previous_status: Option<OrderStatus>,
508    pub status: OrderStatus,
509    pub trader_id: TraderId,
510    pub strategy_id: StrategyId,
511    pub instrument_id: InstrumentId,
512    pub client_order_id: ClientOrderId,
513    pub venue_order_id: Option<VenueOrderId>,
514    pub position_id: Option<PositionId>,
515    pub account_id: Option<AccountId>,
516    pub last_trade_id: Option<TradeId>,
517    pub side: OrderSide,
518    pub order_type: OrderType,
519    pub quantity: Quantity,
520    pub time_in_force: TimeInForce,
521    pub liquidity_side: Option<LiquiditySide>,
522    pub is_reduce_only: bool,
523    pub is_quote_quantity: bool,
524    pub emulation_trigger: Option<TriggerType>,
525    pub contingency_type: Option<ContingencyType>,
526    pub order_list_id: Option<OrderListId>,
527    pub linked_order_ids: Option<Vec<ClientOrderId>>,
528    pub parent_order_id: Option<ClientOrderId>,
529    pub exec_algorithm_id: Option<ExecAlgorithmId>,
530    pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
531    pub exec_spawn_id: Option<ClientOrderId>,
532    pub tags: Option<Vec<Ustr>>,
533    pub filled_qty: Quantity,
534    pub leaves_qty: Quantity,
535    pub avg_px: Option<f64>,
536    pub slippage: Option<f64>,
537    pub init_id: UUID4,
538    pub ts_init: UnixNanos,
539    pub ts_submitted: Option<UnixNanos>,
540    pub ts_accepted: Option<UnixNanos>,
541    pub ts_closed: Option<UnixNanos>,
542    pub ts_last: UnixNanos,
543}
544
545impl OrderCore {
546    /// Creates a new [`OrderCore`] instance.
547    pub fn new(init: OrderInitialized) -> Self {
548        let events: Vec<OrderEventAny> = vec![OrderEventAny::Initialized(init.clone())];
549        Self {
550            events,
551            commissions: IndexMap::new(),
552            venue_order_ids: Vec::new(),
553            trade_ids: Vec::new(),
554            previous_status: None,
555            status: OrderStatus::Initialized,
556            trader_id: init.trader_id,
557            strategy_id: init.strategy_id,
558            instrument_id: init.instrument_id,
559            client_order_id: init.client_order_id,
560            venue_order_id: None,
561            position_id: None,
562            account_id: None,
563            last_trade_id: None,
564            side: init.order_side,
565            order_type: init.order_type,
566            quantity: init.quantity,
567            time_in_force: init.time_in_force,
568            liquidity_side: Some(LiquiditySide::NoLiquiditySide),
569            is_reduce_only: init.reduce_only,
570            is_quote_quantity: init.quote_quantity,
571            emulation_trigger: init.emulation_trigger.or(Some(TriggerType::NoTrigger)),
572            contingency_type: init
573                .contingency_type
574                .or(Some(ContingencyType::NoContingency)),
575            order_list_id: init.order_list_id,
576            linked_order_ids: init.linked_order_ids,
577            parent_order_id: init.parent_order_id,
578            exec_algorithm_id: init.exec_algorithm_id,
579            exec_algorithm_params: init.exec_algorithm_params,
580            exec_spawn_id: init.exec_spawn_id,
581            tags: init.tags,
582            filled_qty: Quantity::zero(init.quantity.precision),
583            leaves_qty: init.quantity,
584            avg_px: None,
585            slippage: None,
586            init_id: init.event_id,
587            ts_init: init.ts_event,
588            ts_submitted: None,
589            ts_accepted: None,
590            ts_closed: None,
591            ts_last: init.ts_event,
592        }
593    }
594
595    /// Applies the `event` to the order.
596    ///
597    /// # Errors
598    ///
599    /// Returns an error if the event is invalid for the current order status.
600    ///
601    /// # Panics
602    ///
603    /// Panics if `event.client_order_id()` or `event.strategy_id()` does not match the order.
604    pub fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
605        assert_eq!(self.client_order_id, event.client_order_id());
606        assert_eq!(self.strategy_id, event.strategy_id());
607
608        let new_status = self.status.transition(&event)?;
609        self.previous_status = Some(self.status);
610        self.status = new_status;
611
612        match &event {
613            OrderEventAny::Initialized(_) => return Err(OrderError::AlreadyInitialized),
614            OrderEventAny::Denied(event) => self.denied(event),
615            OrderEventAny::Emulated(event) => self.emulated(event),
616            OrderEventAny::Released(event) => self.released(event),
617            OrderEventAny::Submitted(event) => self.submitted(event),
618            OrderEventAny::Rejected(event) => self.rejected(event),
619            OrderEventAny::Accepted(event) => self.accepted(event),
620            OrderEventAny::PendingUpdate(event) => self.pending_update(event),
621            OrderEventAny::PendingCancel(event) => self.pending_cancel(event),
622            OrderEventAny::ModifyRejected(event) => self.modify_rejected(event),
623            OrderEventAny::CancelRejected(event) => self.cancel_rejected(event),
624            OrderEventAny::Updated(event) => self.updated(event),
625            OrderEventAny::Triggered(event) => self.triggered(event),
626            OrderEventAny::Canceled(event) => self.canceled(event),
627            OrderEventAny::Expired(event) => self.expired(event),
628            OrderEventAny::Filled(event) => self.filled(event),
629        }
630
631        self.ts_last = event.ts_event();
632        self.events.push(event);
633        Ok(())
634    }
635
636    fn denied(&mut self, event: &OrderDenied) {
637        self.ts_closed = Some(event.ts_event);
638    }
639
640    fn emulated(&self, _event: &OrderEmulated) {
641        // Do nothing else
642    }
643
644    fn released(&mut self, _event: &OrderReleased) {
645        self.emulation_trigger = None;
646    }
647
648    fn submitted(&mut self, event: &OrderSubmitted) {
649        self.account_id = Some(event.account_id);
650        self.ts_submitted = Some(event.ts_event);
651    }
652
653    fn accepted(&mut self, event: &OrderAccepted) {
654        self.venue_order_id = Some(event.venue_order_id);
655        self.ts_accepted = Some(event.ts_event);
656    }
657
658    fn rejected(&mut self, event: &OrderRejected) {
659        self.ts_closed = Some(event.ts_event);
660    }
661
662    fn pending_update(&self, _event: &OrderPendingUpdate) {
663        // Do nothing else
664    }
665
666    fn pending_cancel(&self, _event: &OrderPendingCancel) {
667        // Do nothing else
668    }
669
670    fn modify_rejected(&mut self, _event: &OrderModifyRejected) {
671        self.status = self
672            .previous_status
673            .unwrap_or_else(|| panic!("{}", OrderError::NoPreviousState));
674    }
675
676    fn cancel_rejected(&mut self, _event: &OrderCancelRejected) {
677        self.status = self
678            .previous_status
679            .unwrap_or_else(|| panic!("{}", OrderError::NoPreviousState));
680    }
681
682    fn triggered(&mut self, _event: &OrderTriggered) {}
683
684    fn canceled(&mut self, event: &OrderCanceled) {
685        self.ts_closed = Some(event.ts_event);
686    }
687
688    fn expired(&mut self, event: &OrderExpired) {
689        self.ts_closed = Some(event.ts_event);
690    }
691
692    fn updated(&mut self, event: &OrderUpdated) {
693        if let Some(venue_order_id) = &event.venue_order_id
694            && (self.venue_order_id.is_none()
695                || venue_order_id != self.venue_order_id.as_ref().unwrap())
696        {
697            self.venue_order_id = Some(*venue_order_id);
698            self.venue_order_ids.push(*venue_order_id);
699        }
700    }
701
702    fn filled(&mut self, event: &OrderFilled) {
703        if self.filled_qty + event.last_qty < self.quantity {
704            self.status = OrderStatus::PartiallyFilled;
705        } else {
706            self.status = OrderStatus::Filled;
707            self.ts_closed = Some(event.ts_event);
708        }
709
710        self.venue_order_id = Some(event.venue_order_id);
711        self.position_id = event.position_id;
712        self.trade_ids.push(event.trade_id);
713        self.last_trade_id = Some(event.trade_id);
714        self.liquidity_side = Some(event.liquidity_side);
715        self.filled_qty += event.last_qty;
716        self.leaves_qty -= event.last_qty;
717        self.ts_last = event.ts_event;
718        if self.ts_accepted.is_none() {
719            // Set ts_accepted to time of first fill if not previously set
720            self.ts_accepted = Some(event.ts_event);
721        }
722
723        self.set_avg_px(event.last_qty, event.last_px);
724    }
725
726    fn set_avg_px(&mut self, last_qty: Quantity, last_px: Price) {
727        if self.avg_px.is_none() {
728            self.avg_px = Some(last_px.as_f64());
729        }
730
731        let filled_qty = self.filled_qty.as_f64();
732        let total_qty = filled_qty + last_qty.as_f64();
733
734        let avg_px = self
735            .avg_px
736            .unwrap()
737            .mul_add(filled_qty, last_px.as_f64() * last_qty.as_f64())
738            / total_qty;
739        self.avg_px = Some(avg_px);
740    }
741
742    pub fn set_slippage(&mut self, price: Price) {
743        self.slippage = self.avg_px.and_then(|avg_px| {
744            let current_price = price.as_f64();
745            match self.side {
746                OrderSide::Buy if avg_px > current_price => Some(avg_px - current_price),
747                OrderSide::Sell if avg_px < current_price => Some(current_price - avg_px),
748                _ => None,
749            }
750        });
751    }
752
753    /// Returns the opposite order side.
754    #[must_use]
755    pub fn opposite_side(side: OrderSide) -> OrderSide {
756        match side {
757            OrderSide::Buy => OrderSide::Sell,
758            OrderSide::Sell => OrderSide::Buy,
759            OrderSide::NoOrderSide => OrderSide::NoOrderSide,
760        }
761    }
762
763    /// Returns the order side needed to close a position.
764    #[must_use]
765    pub fn closing_side(side: PositionSide) -> OrderSide {
766        match side {
767            PositionSide::Long => OrderSide::Sell,
768            PositionSide::Short => OrderSide::Buy,
769            PositionSide::Flat => OrderSide::NoOrderSide,
770            PositionSide::NoPositionSide => OrderSide::NoOrderSide,
771        }
772    }
773
774    /// # Panics
775    ///
776    /// Panics if the order side is neither `Buy` nor `Sell`.
777    #[must_use]
778    pub fn signed_decimal_qty(&self) -> Decimal {
779        match self.side {
780            OrderSide::Buy => self.quantity.as_decimal(),
781            OrderSide::Sell => -self.quantity.as_decimal(),
782            _ => panic!("Invalid order side"),
783        }
784    }
785
786    #[must_use]
787    pub fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
788        if side == PositionSide::Flat {
789            return false;
790        }
791
792        match (self.side, side) {
793            (OrderSide::Buy, PositionSide::Long) => false,
794            (OrderSide::Buy, PositionSide::Short) => self.leaves_qty <= position_qty,
795            (OrderSide::Sell, PositionSide::Short) => false,
796            (OrderSide::Sell, PositionSide::Long) => self.leaves_qty <= position_qty,
797            _ => true,
798        }
799    }
800
801    #[must_use]
802    pub fn commission(&self, currency: &Currency) -> Option<Money> {
803        self.commissions.get(currency).copied()
804    }
805
806    #[must_use]
807    pub fn commissions(&self) -> IndexMap<Currency, Money> {
808        self.commissions.clone()
809    }
810
811    #[must_use]
812    pub fn commissions_vec(&self) -> Vec<Money> {
813        self.commissions.values().cloned().collect()
814    }
815
816    #[must_use]
817    pub fn init_event(&self) -> Option<OrderEventAny> {
818        self.events.first().cloned()
819    }
820}
821
822////////////////////////////////////////////////////////////////////////////////
823// Tests
824////////////////////////////////////////////////////////////////////////////////
825#[cfg(test)]
826mod tests {
827    use rstest::rstest;
828    use rust_decimal_macros::dec;
829
830    use super::*;
831    use crate::{
832        enums::{OrderSide, OrderStatus, PositionSide},
833        events::order::{
834            accepted::OrderAcceptedBuilder, canceled::OrderCanceledBuilder,
835            denied::OrderDeniedBuilder, filled::OrderFilledBuilder,
836            initialized::OrderInitializedBuilder, submitted::OrderSubmittedBuilder,
837        },
838        orders::MarketOrder,
839    };
840
841    // TODO: WIP
842    // fn test_display_market_order() {
843    //     let order = MarketOrder::default();
844    //     assert_eq!(order.events().len(), 1);
845    //     assert_eq!(
846    //         stringify!(order.events().get(0)),
847    //         stringify!(OrderInitialized)
848    //     );
849    // }
850
851    #[rstest]
852    #[case(OrderSide::Buy, OrderSide::Sell)]
853    #[case(OrderSide::Sell, OrderSide::Buy)]
854    #[case(OrderSide::NoOrderSide, OrderSide::NoOrderSide)]
855    fn test_order_opposite_side(#[case] order_side: OrderSide, #[case] expected_side: OrderSide) {
856        let result = OrderCore::opposite_side(order_side);
857        assert_eq!(result, expected_side);
858    }
859
860    #[rstest]
861    #[case(PositionSide::Long, OrderSide::Sell)]
862    #[case(PositionSide::Short, OrderSide::Buy)]
863    #[case(PositionSide::NoPositionSide, OrderSide::NoOrderSide)]
864    fn test_closing_side(#[case] position_side: PositionSide, #[case] expected_side: OrderSide) {
865        let result = OrderCore::closing_side(position_side);
866        assert_eq!(result, expected_side);
867    }
868
869    #[rstest]
870    #[case(OrderSide::Buy, dec!(10_000))]
871    #[case(OrderSide::Sell, dec!(-10_000))]
872    fn test_signed_decimal_qty(#[case] order_side: OrderSide, #[case] expected: Decimal) {
873        let order: MarketOrder = OrderInitializedBuilder::default()
874            .order_side(order_side)
875            .quantity(Quantity::from(10_000))
876            .build()
877            .unwrap()
878            .into();
879
880        let result = order.signed_decimal_qty();
881        assert_eq!(result, expected);
882    }
883
884    #[rustfmt::skip]
885    #[rstest]
886    #[case(OrderSide::Buy, Quantity::from(100), PositionSide::Long, Quantity::from(50), false)]
887    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(50), true)]
888    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(100), true)]
889    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
890    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
891    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(50), true)]
892    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(100), true)]
893    #[case(OrderSide::Sell, Quantity::from(100), PositionSide::Short, Quantity::from(50), false)]
894    fn test_would_reduce_only(
895        #[case] order_side: OrderSide,
896        #[case] order_qty: Quantity,
897        #[case] position_side: PositionSide,
898        #[case] position_qty: Quantity,
899        #[case] expected: bool,
900    ) {
901        let order: MarketOrder = OrderInitializedBuilder::default()
902            .order_side(order_side)
903            .quantity(order_qty)
904            .build()
905            .unwrap()
906            .into();
907
908        assert_eq!(
909            order.would_reduce_only(position_side, position_qty),
910            expected
911        );
912    }
913
914    #[rstest]
915    fn test_order_state_transition_denied() {
916        let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
917        let denied = OrderDeniedBuilder::default().build().unwrap();
918        let event = OrderEventAny::Denied(denied);
919
920        order.apply(event.clone()).unwrap();
921
922        assert_eq!(order.status, OrderStatus::Denied);
923        assert!(order.is_closed());
924        assert!(!order.is_open());
925        assert_eq!(order.event_count(), 2);
926        assert_eq!(order.last_event(), &event);
927    }
928
929    #[rstest]
930    fn test_order_life_cycle_to_filled() {
931        let init = OrderInitializedBuilder::default().build().unwrap();
932        let submitted = OrderSubmittedBuilder::default().build().unwrap();
933        let accepted = OrderAcceptedBuilder::default().build().unwrap();
934        let filled = OrderFilledBuilder::default().build().unwrap();
935
936        let mut order: MarketOrder = init.clone().into();
937        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
938        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
939        order.apply(OrderEventAny::Filled(filled)).unwrap();
940
941        assert_eq!(order.client_order_id, init.client_order_id);
942        assert_eq!(order.status(), OrderStatus::Filled);
943        assert_eq!(order.filled_qty(), Quantity::from(100_000));
944        assert_eq!(order.leaves_qty(), Quantity::from(0));
945        assert_eq!(order.avg_px(), Some(1.0));
946        assert!(!order.is_open());
947        assert!(order.is_closed());
948        assert_eq!(order.commission(&Currency::USD()), None);
949        assert_eq!(order.commissions(), &IndexMap::new());
950    }
951
952    #[rstest]
953    fn test_order_state_transition_to_canceled() {
954        let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
955        let submitted = OrderSubmittedBuilder::default().build().unwrap();
956        let canceled = OrderCanceledBuilder::default().build().unwrap();
957
958        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
959        order.apply(OrderEventAny::Canceled(canceled)).unwrap();
960
961        assert_eq!(order.status(), OrderStatus::Canceled);
962        assert!(order.is_closed());
963        assert!(!order.is_open());
964    }
965
966    #[rstest]
967    fn test_order_life_cycle_to_partially_filled() {
968        let init = OrderInitializedBuilder::default().build().unwrap();
969        let submitted = OrderSubmittedBuilder::default().build().unwrap();
970        let accepted = OrderAcceptedBuilder::default().build().unwrap();
971        let filled = OrderFilledBuilder::default()
972            .last_qty(Quantity::from(50_000))
973            .build()
974            .unwrap();
975
976        let mut order: MarketOrder = init.clone().into();
977        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
978        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
979        order.apply(OrderEventAny::Filled(filled)).unwrap();
980
981        assert_eq!(order.client_order_id, init.client_order_id);
982        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
983        assert_eq!(order.filled_qty(), Quantity::from(50_000));
984        assert_eq!(order.leaves_qty(), Quantity::from(50_000));
985        assert!(order.is_open());
986        assert!(!order.is_closed());
987    }
988
989    #[rstest]
990    fn test_order_commission_calculation() {
991        let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
992        order
993            .commissions
994            .insert(Currency::USD(), Money::new(10.0, Currency::USD()));
995
996        assert_eq!(
997            order.commission(&Currency::USD()),
998            Some(Money::new(10.0, Currency::USD()))
999        );
1000        assert_eq!(
1001            order.commissions_vec(),
1002            vec![Money::new(10.0, Currency::USD())]
1003        );
1004    }
1005
1006    #[rstest]
1007    fn test_order_is_primary() {
1008        let order: MarketOrder = OrderInitializedBuilder::default()
1009            .exec_algorithm_id(Some(ExecAlgorithmId::from("ALGO-001")))
1010            .exec_spawn_id(Some(ClientOrderId::from("O-001")))
1011            .client_order_id(ClientOrderId::from("O-001"))
1012            .build()
1013            .unwrap()
1014            .into();
1015
1016        assert!(order.is_primary());
1017        assert!(!order.is_secondary());
1018    }
1019
1020    #[rstest]
1021    fn test_order_is_secondary() {
1022        let order: MarketOrder = OrderInitializedBuilder::default()
1023            .exec_algorithm_id(Some(ExecAlgorithmId::from("ALGO-001")))
1024            .exec_spawn_id(Some(ClientOrderId::from("O-002")))
1025            .client_order_id(ClientOrderId::from("O-001"))
1026            .build()
1027            .unwrap()
1028            .into();
1029
1030        assert!(!order.is_primary());
1031        assert!(order.is_secondary());
1032    }
1033
1034    #[rstest]
1035    fn test_order_is_contingency() {
1036        let order: MarketOrder = OrderInitializedBuilder::default()
1037            .contingency_type(Some(ContingencyType::Oto))
1038            .build()
1039            .unwrap()
1040            .into();
1041
1042        assert!(order.is_contingency());
1043        assert!(order.is_parent_order());
1044        assert!(!order.is_child_order());
1045    }
1046
1047    #[rstest]
1048    fn test_order_is_child_order() {
1049        let order: MarketOrder = OrderInitializedBuilder::default()
1050            .parent_order_id(Some(ClientOrderId::from("PARENT-001")))
1051            .build()
1052            .unwrap()
1053            .into();
1054
1055        assert!(order.is_child_order());
1056        assert!(!order.is_parent_order());
1057    }
1058}