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