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