nautilus_model/orders/
mod.rs

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