Skip to main content

nautilus_model/orders/
mod.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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;
19#[cfg(any(test, feature = "stubs"))]
20pub mod builder;
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::{
40    UUID4, UnixNanos,
41    correctness::{CorrectnessError, check_predicate_false},
42};
43use rust_decimal::Decimal;
44use serde::{Deserialize, Serialize};
45use ustr::Ustr;
46
47#[cfg(any(test, feature = "stubs"))]
48pub use crate::orders::builder::OrderTestBuilder;
49pub use crate::orders::{
50    any::{LimitOrderAny, OrderAny, PassiveOrderAny, StopOrderAny},
51    limit::LimitOrder,
52    limit_if_touched::LimitIfTouchedOrder,
53    list::OrderList,
54    market::MarketOrder,
55    market_if_touched::MarketIfTouchedOrder,
56    market_to_limit::MarketToLimitOrder,
57    stop_limit::StopLimitOrder,
58    stop_market::StopMarketOrder,
59    trailing_stop_limit::TrailingStopLimitOrder,
60    trailing_stop_market::TrailingStopMarketOrder,
61};
62use crate::{
63    enums::{
64        ContingencyType, LiquiditySide, OrderSide, OrderSideSpecified, OrderStatus, OrderType,
65        PositionSide, TimeInForce, TrailingOffsetType, TriggerType,
66    },
67    events::{
68        OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated,
69        OrderEventAny, OrderExpired, OrderFilled, OrderInitialized, OrderModifyRejected,
70        OrderPendingCancel, OrderPendingUpdate, OrderRejected, OrderReleased, OrderSubmitted,
71        OrderTriggered, OrderUpdated,
72    },
73    identifiers::{
74        AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
75        StrategyId, Symbol, TradeId, TraderId, Venue, VenueOrderId,
76    },
77    orderbook::OwnBookOrder,
78    types::{Currency, Money, Price, Quantity},
79};
80
81/// Order types that have stop/trigger prices.
82pub const STOP_ORDER_TYPES: &[OrderType] = &[
83    OrderType::StopMarket,
84    OrderType::StopLimit,
85    OrderType::MarketIfTouched,
86    OrderType::LimitIfTouched,
87];
88
89/// Order types that have limit prices.
90pub const LIMIT_ORDER_TYPES: &[OrderType] = &[
91    OrderType::Limit,
92    OrderType::StopLimit,
93    OrderType::LimitIfTouched,
94    OrderType::TrailingStopLimit,
95];
96
97/// Order types that support the TRIGGERED order status.
98///
99/// Market-style stops (`StopMarket`, `MarketIfTouched`, `TrailingStopMarket`) execute
100/// immediately on trigger and have no intermediate TRIGGERED state.
101pub const TRIGGERABLE_ORDER_TYPES: &[OrderType] = &[
102    OrderType::StopLimit,
103    OrderType::TrailingStopLimit,
104    OrderType::LimitIfTouched,
105];
106
107/// Order statuses for locally active orders (pre-submission to venue).
108pub const LOCAL_ACTIVE_ORDER_STATUSES: &[OrderStatus] = &[
109    OrderStatus::Initialized,
110    OrderStatus::Emulated,
111    OrderStatus::Released,
112];
113
114/// Order statuses that are safe for cancellation queries.
115///
116/// These are statuses where an order is working on the venue but not already
117/// in the process of being cancelled. Including `PENDING_CANCEL` in cancellation
118/// filters can cause duplicate cancel attempts or incorrect open order counts.
119///
120/// Note: `PENDING_UPDATE` is included as orders being updated can typically still
121/// be cancelled (update and cancel are independent operations on most venues).
122pub const CANCELLABLE_ORDER_STATUSES: &[OrderStatus] = &[
123    OrderStatus::Accepted,
124    OrderStatus::Triggered,
125    OrderStatus::PendingUpdate,
126    OrderStatus::PartiallyFilled,
127];
128
129/// Returns a cached `AHashSet` of cancellable order statuses for O(1) lookups.
130///
131/// For the small set (4 elements), using `CANCELLABLE_ORDER_STATUSES.contains()` may be
132/// equally fast due to better cache locality. Use this function when you need set operations
133/// or are building HashSet-based filters.
134///
135/// Note: This is a module-level convenience function. You can also use
136/// `OrderStatus::cancellable_statuses_set()` directly.
137#[must_use]
138pub fn cancellable_order_statuses_set() -> &'static AHashSet<OrderStatus> {
139    OrderStatus::cancellable_statuses_set()
140}
141
142#[derive(thiserror::Error, Debug)]
143pub enum OrderError {
144    #[error("Order not found: {0}")]
145    NotFound(ClientOrderId),
146    #[error("Order invariant failed: must have a side for this operation")]
147    NoOrderSide,
148    #[error("Invalid event for order type")]
149    InvalidOrderEvent,
150    #[error("Invalid order state transition")]
151    InvalidStateTransition,
152    #[error("Order was already initialized")]
153    AlreadyInitialized,
154    #[error("Order had no previous state")]
155    NoPreviousState,
156    #[error("Duplicate fill: trade_id {0} already applied to order")]
157    DuplicateFill(TradeId),
158    #[error("{0}")]
159    Invariant(#[from] CorrectnessError),
160}
161
162/// Converts an `IndexMap` with `Ustr` keys and values to `String` keys and values.
163#[must_use]
164pub fn ustr_indexmap_to_str(h: IndexMap<Ustr, Ustr>) -> IndexMap<String, String> {
165    h.into_iter()
166        .map(|(k, v)| (k.to_string(), v.to_string()))
167        .collect()
168}
169
170/// Converts an `IndexMap` with `String` keys and values to `Ustr` keys and values.
171#[must_use]
172pub fn str_indexmap_to_ustr(h: IndexMap<String, String>) -> IndexMap<Ustr, Ustr> {
173    h.into_iter()
174        .map(|(k, v)| (Ustr::from(&k), Ustr::from(&v)))
175        .collect()
176}
177
178#[inline]
179pub(crate) fn check_display_qty(
180    display_qty: Option<Quantity>,
181    quantity: Quantity,
182) -> Result<(), OrderError> {
183    if let Some(q) = display_qty {
184        check_predicate_false(q > quantity, "`display_qty` may not exceed `quantity`")?;
185    }
186    Ok(())
187}
188
189#[inline]
190pub(crate) fn check_time_in_force(
191    time_in_force: TimeInForce,
192    expire_time: Option<UnixNanos>,
193) -> Result<(), OrderError> {
194    check_predicate_false(
195        time_in_force == TimeInForce::Gtd && expire_time.unwrap_or_default() == 0,
196        "`expire_time` is required for `GTD` order",
197    )?;
198    Ok(())
199}
200
201impl OrderStatus {
202    /// Transitions the order state machine based on the given `event`.
203    ///
204    /// # Errors
205    ///
206    /// Returns an error if the state transition is invalid from the current status.
207    #[rustfmt::skip]
208    pub fn transition(&mut self, event: &OrderEventAny) -> Result<Self, OrderError> {
209        let new_state = match (self, event) {
210            (Self::Initialized, OrderEventAny::Denied(_)) => Self::Denied,
211            (Self::Initialized, OrderEventAny::Emulated(_)) => Self::Emulated,  // Emulated orders
212            (Self::Initialized, OrderEventAny::Released(_)) => Self::Released,  // Emulated orders
213            (Self::Initialized, OrderEventAny::Submitted(_)) => Self::Submitted,
214            (Self::Initialized, OrderEventAny::Rejected(_)) => Self::Rejected,  // External orders
215            (Self::Initialized, OrderEventAny::Accepted(_)) => Self::Accepted,  // External orders
216            (Self::Initialized, OrderEventAny::Canceled(_)) => Self::Canceled,  // External orders
217            (Self::Initialized, OrderEventAny::Expired(_)) => Self::Expired,  // External orders
218            (Self::Initialized, OrderEventAny::Triggered(_)) => Self::Triggered, // External orders
219            (Self::Initialized, OrderEventAny::Updated(_)) => Self::Initialized, // In-place modification
220            (Self::Emulated, OrderEventAny::Canceled(_)) => Self::Canceled,  // Emulated orders
221            (Self::Emulated, OrderEventAny::Expired(_)) => Self::Expired,  // Emulated orders
222            (Self::Emulated, OrderEventAny::Released(_)) => Self::Released,  // Emulated orders
223            (Self::Released, OrderEventAny::Submitted(_)) => Self::Submitted,  // Emulated orders
224            (Self::Released, OrderEventAny::Denied(_)) => Self::Denied,  // Emulated orders
225            (Self::Released, OrderEventAny::Canceled(_)) => Self::Canceled,  // Execution algo
226            (Self::Released, OrderEventAny::Updated(_)) => Self::Released, // In-place modification
227            (Self::Submitted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
228            (Self::Submitted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
229            (Self::Submitted, OrderEventAny::Rejected(_)) => Self::Rejected,
230            (Self::Submitted, OrderEventAny::Canceled(_)) => Self::Canceled,  // FOK and IOC cases
231            (Self::Submitted, OrderEventAny::Accepted(_)) => Self::Accepted,
232            (Self::Submitted, OrderEventAny::Updated(_)) => Self::Submitted,
233            (Self::Submitted, OrderEventAny::Filled(_)) => Self::Filled,
234            (Self::Accepted, OrderEventAny::Rejected(_)) => Self::Rejected,  // StopLimit order
235            (Self::Accepted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
236            (Self::Accepted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
237            (Self::Accepted, OrderEventAny::Canceled(_)) => Self::Canceled,
238            (Self::Accepted, OrderEventAny::Triggered(_)) => Self::Triggered,
239            (Self::Accepted, OrderEventAny::Updated(_)) => Self::Accepted,  // Updates should preserve state
240            (Self::Accepted, OrderEventAny::Expired(_)) => Self::Expired,
241            (Self::Accepted, OrderEventAny::Filled(_)) => Self::Filled,
242            (Self::Canceled, OrderEventAny::Filled(_)) => Self::Filled,  // Real world possibility
243            (Self::PendingUpdate, OrderEventAny::Rejected(_)) => Self::Rejected,
244            (Self::PendingUpdate, OrderEventAny::Accepted(_)) => Self::Accepted,
245            (Self::PendingUpdate, OrderEventAny::Canceled(_)) => Self::Canceled,
246            (Self::PendingUpdate, OrderEventAny::Expired(_)) => Self::Expired,
247            (Self::PendingUpdate, OrderEventAny::Triggered(_)) => Self::Triggered,
248            (Self::PendingUpdate, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,  // Allow multiple requests
249            (Self::PendingUpdate, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
250            (Self::PendingUpdate, OrderEventAny::ModifyRejected(_)) => Self::PendingUpdate,  // Handled by modify_rejected to restore previous_status
251            (Self::PendingUpdate, OrderEventAny::Updated(_)) => Self::PendingUpdate,  // Handled by updated to restore previous_status
252            (Self::PendingUpdate, OrderEventAny::Filled(_)) => Self::Filled,
253            (Self::PendingCancel, OrderEventAny::Rejected(_)) => Self::Rejected,
254            (Self::PendingCancel, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,  // Allow multiple requests
255            (Self::PendingCancel, OrderEventAny::CancelRejected(_)) => Self::PendingCancel,  // Handled by cancel_rejected to restore previous_status
256            (Self::PendingCancel, OrderEventAny::Canceled(_)) => Self::Canceled,
257            (Self::PendingCancel, OrderEventAny::Expired(_)) => Self::Expired,
258            (Self::PendingCancel, OrderEventAny::Accepted(_)) => Self::Accepted,  // Allow failed cancel requests
259            (Self::PendingCancel, OrderEventAny::Filled(_)) => Self::Filled,
260            (Self::Triggered, OrderEventAny::Rejected(_)) => Self::Rejected,
261            (Self::Triggered, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
262            (Self::Triggered, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
263            (Self::Triggered, OrderEventAny::Canceled(_)) => Self::Canceled,
264            (Self::Triggered, OrderEventAny::Expired(_)) => Self::Expired,
265            (Self::Triggered, OrderEventAny::Filled(_)) => Self::Filled,
266            (Self::Triggered, OrderEventAny::Updated(_)) => Self::Triggered,
267            (Self::PartiallyFilled, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
268            (Self::PartiallyFilled, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
269            (Self::PartiallyFilled, OrderEventAny::Canceled(_)) => Self::Canceled,
270            (Self::PartiallyFilled, OrderEventAny::Expired(_)) => Self::Expired,
271            (Self::PartiallyFilled, OrderEventAny::Filled(_)) => Self::Filled,
272            (Self::PartiallyFilled, OrderEventAny::Accepted(_)) => Self::Accepted,
273            (Self::PartiallyFilled, OrderEventAny::Updated(_)) => Self::PartiallyFilled,
274            _ => return Err(OrderError::InvalidStateTransition),
275        };
276        Ok(new_state)
277    }
278}
279
280#[enum_dispatch]
281pub trait Order: 'static + Send {
282    fn into_any(self) -> OrderAny;
283    fn status(&self) -> OrderStatus;
284    fn trader_id(&self) -> TraderId;
285    fn strategy_id(&self) -> StrategyId;
286    fn instrument_id(&self) -> InstrumentId;
287    fn symbol(&self) -> Symbol;
288    fn venue(&self) -> Venue;
289    fn client_order_id(&self) -> ClientOrderId;
290    fn venue_order_id(&self) -> Option<VenueOrderId>;
291    fn position_id(&self) -> Option<PositionId>;
292    fn account_id(&self) -> Option<AccountId>;
293    fn last_trade_id(&self) -> Option<TradeId>;
294    fn order_side(&self) -> OrderSide;
295    fn order_type(&self) -> OrderType;
296    fn quantity(&self) -> Quantity;
297    fn time_in_force(&self) -> TimeInForce;
298    fn expire_time(&self) -> Option<UnixNanos>;
299    fn price(&self) -> Option<Price>;
300    fn trigger_price(&self) -> Option<Price>;
301    fn activation_price(&self) -> Option<Price> {
302        None
303    }
304    fn trigger_type(&self) -> Option<TriggerType>;
305    fn liquidity_side(&self) -> Option<LiquiditySide>;
306    fn is_post_only(&self) -> bool;
307    fn is_reduce_only(&self) -> bool;
308    fn is_quote_quantity(&self) -> bool;
309    fn display_qty(&self) -> Option<Quantity>;
310    fn limit_offset(&self) -> Option<Decimal>;
311    fn trailing_offset(&self) -> Option<Decimal>;
312    fn trailing_offset_type(&self) -> Option<TrailingOffsetType>;
313    fn emulation_trigger(&self) -> Option<TriggerType>;
314    fn trigger_instrument_id(&self) -> Option<InstrumentId>;
315    fn contingency_type(&self) -> Option<ContingencyType>;
316    fn order_list_id(&self) -> Option<OrderListId>;
317    fn linked_order_ids(&self) -> Option<&[ClientOrderId]>;
318    fn parent_order_id(&self) -> Option<ClientOrderId>;
319    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId>;
320    fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>>;
321    fn exec_spawn_id(&self) -> Option<ClientOrderId>;
322    fn tags(&self) -> Option<&[Ustr]>;
323    fn filled_qty(&self) -> Quantity;
324    fn leaves_qty(&self) -> Quantity;
325    fn overfill_qty(&self) -> Quantity;
326
327    /// Calculates potential overfill quantity without mutating order state.
328    fn calculate_overfill(&self, fill_qty: Quantity) -> Quantity {
329        let potential_filled = self.filled_qty() + fill_qty;
330        let quantity = self.quantity();
331        if potential_filled > quantity {
332            potential_filled - quantity
333        } else {
334            Quantity::zero(fill_qty.precision)
335        }
336    }
337
338    fn avg_px(&self) -> Option<f64>;
339    fn slippage(&self) -> Option<f64>;
340    fn init_id(&self) -> UUID4;
341    fn ts_init(&self) -> UnixNanos;
342    fn ts_submitted(&self) -> Option<UnixNanos>;
343    fn ts_accepted(&self) -> Option<UnixNanos>;
344    fn ts_closed(&self) -> Option<UnixNanos>;
345    fn ts_last(&self) -> UnixNanos;
346
347    fn order_side_specified(&self) -> OrderSideSpecified {
348        self.order_side().as_specified()
349    }
350    fn commissions(&self) -> &IndexMap<Currency, Money>;
351
352    /// Applies the `event` to the order.
353    ///
354    /// # Errors
355    ///
356    /// Returns an error if the event is invalid for the current order status.
357    fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError>;
358    fn update(&mut self, event: &OrderUpdated);
359
360    fn events(&self) -> Vec<&OrderEventAny>;
361
362    fn last_event(&self) -> &OrderEventAny {
363        self.events()
364            .last()
365            .expect("Order invariant violated: no events")
366    }
367
368    fn event_count(&self) -> usize {
369        self.events().len()
370    }
371
372    fn venue_order_ids(&self) -> Vec<&VenueOrderId>;
373
374    fn trade_ids(&self) -> Vec<&TradeId>;
375
376    fn has_price(&self) -> bool;
377
378    /// Returns `true` if a fill with matching `trade_id`, side, qty, and price already exists.
379    fn is_duplicate_fill(&self, fill: &OrderFilled) -> bool {
380        self.events().iter().any(|event| {
381            if let OrderEventAny::Filled(existing) = event {
382                existing.trade_id == fill.trade_id
383                    && existing.order_side == fill.order_side
384                    && existing.last_qty == fill.last_qty
385                    && existing.last_px == fill.last_px
386            } else {
387                false
388            }
389        })
390    }
391
392    fn is_buy(&self) -> bool {
393        self.order_side() == OrderSide::Buy
394    }
395
396    fn is_sell(&self) -> bool {
397        self.order_side() == OrderSide::Sell
398    }
399
400    fn is_passive(&self) -> bool {
401        self.order_type() != OrderType::Market
402    }
403
404    fn is_aggressive(&self) -> bool {
405        self.order_type() == OrderType::Market
406    }
407
408    fn is_emulated(&self) -> bool {
409        self.status() == OrderStatus::Emulated
410    }
411
412    fn is_active_local(&self) -> bool {
413        matches!(
414            self.status(),
415            OrderStatus::Initialized | OrderStatus::Emulated | OrderStatus::Released
416        )
417    }
418
419    fn is_primary(&self) -> bool {
420        self.exec_algorithm_id().is_some()
421            && self
422                .exec_spawn_id()
423                .is_some_and(|spawn_id| self.client_order_id() == spawn_id)
424    }
425
426    fn is_spawned(&self) -> bool {
427        self.exec_algorithm_id().is_some()
428            && self
429                .exec_spawn_id()
430                .is_some_and(|spawn_id| self.client_order_id() != spawn_id)
431    }
432
433    fn is_contingency(&self) -> bool {
434        self.contingency_type().is_some()
435    }
436
437    fn is_parent_order(&self) -> bool {
438        match self.contingency_type() {
439            Some(c) => c == ContingencyType::Oto,
440            None => false,
441        }
442    }
443
444    fn is_child_order(&self) -> bool {
445        self.parent_order_id().is_some()
446    }
447
448    fn is_open(&self) -> bool {
449        if let Some(emulation_trigger) = self.emulation_trigger()
450            && emulation_trigger != TriggerType::NoTrigger
451        {
452            return false;
453        }
454
455        matches!(
456            self.status(),
457            OrderStatus::Accepted
458                | OrderStatus::Triggered
459                | OrderStatus::PendingCancel
460                | OrderStatus::PendingUpdate
461                | OrderStatus::PartiallyFilled
462        )
463    }
464
465    fn is_canceled(&self) -> bool {
466        self.status() == OrderStatus::Canceled
467    }
468
469    fn is_closed(&self) -> bool {
470        matches!(
471            self.status(),
472            OrderStatus::Denied
473                | OrderStatus::Rejected
474                | OrderStatus::Canceled
475                | OrderStatus::Expired
476                | OrderStatus::Filled
477        )
478    }
479
480    fn is_inflight(&self) -> bool {
481        if let Some(emulation_trigger) = self.emulation_trigger()
482            && emulation_trigger != TriggerType::NoTrigger
483        {
484            return false;
485        }
486
487        matches!(
488            self.status(),
489            OrderStatus::Submitted | OrderStatus::PendingCancel | OrderStatus::PendingUpdate
490        )
491    }
492
493    fn is_pending_update(&self) -> bool {
494        self.status() == OrderStatus::PendingUpdate
495    }
496
497    fn is_pending_cancel(&self) -> bool {
498        self.status() == OrderStatus::PendingCancel
499    }
500
501    fn to_own_book_order(&self) -> OwnBookOrder {
502        OwnBookOrder::new(
503            self.trader_id(),
504            self.client_order_id(),
505            self.venue_order_id(),
506            self.order_side().as_specified(),
507            self.price().expect("`OwnBookOrder` must have a price"), // TBD
508            self.quantity(),
509            self.order_type(),
510            self.time_in_force(),
511            self.status(),
512            self.ts_last(),
513            self.ts_accepted().unwrap_or_default(),
514            self.ts_submitted().unwrap_or_default(),
515            self.ts_init(),
516        )
517    }
518
519    fn is_triggered(&self) -> Option<bool>; // TODO: Temporary on trait
520    fn set_position_id(&mut self, position_id: Option<PositionId>);
521    fn set_quantity(&mut self, quantity: Quantity);
522    fn set_leaves_qty(&mut self, leaves_qty: Quantity);
523    fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>);
524    fn set_is_quote_quantity(&mut self, is_quote_quantity: bool);
525    fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide);
526    fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool;
527    fn previous_status(&self) -> Option<OrderStatus>;
528}
529
530impl<T> From<&T> for OrderInitialized
531where
532    T: Order,
533{
534    fn from(order: &T) -> Self {
535        Self {
536            trader_id: order.trader_id(),
537            strategy_id: order.strategy_id(),
538            instrument_id: order.instrument_id(),
539            client_order_id: order.client_order_id(),
540            order_side: order.order_side(),
541            order_type: order.order_type(),
542            quantity: order.quantity(),
543            price: order.price(),
544            trigger_price: order.trigger_price(),
545            trigger_type: order.trigger_type(),
546            time_in_force: order.time_in_force(),
547            expire_time: order.expire_time(),
548            post_only: order.is_post_only(),
549            reduce_only: order.is_reduce_only(),
550            quote_quantity: order.is_quote_quantity(),
551            display_qty: order.display_qty(),
552            limit_offset: order.limit_offset(),
553            trailing_offset: order.trailing_offset(),
554            trailing_offset_type: order.trailing_offset_type(),
555            emulation_trigger: order.emulation_trigger(),
556            trigger_instrument_id: order.trigger_instrument_id(),
557            contingency_type: order.contingency_type(),
558            order_list_id: order.order_list_id(),
559            linked_order_ids: order.linked_order_ids().map(|x| x.to_vec()),
560            parent_order_id: order.parent_order_id(),
561            exec_algorithm_id: order.exec_algorithm_id(),
562            exec_algorithm_params: order.exec_algorithm_params().map(|x| x.to_owned()),
563            exec_spawn_id: order.exec_spawn_id(),
564            tags: order.tags().map(|x| x.to_vec()),
565            event_id: order.init_id(),
566            ts_event: order.ts_init(),
567            ts_init: order.ts_init(),
568            reconciliation: false,
569        }
570    }
571}
572
573#[derive(Clone, Debug, Serialize, Deserialize)]
574pub struct OrderCore {
575    pub events: Vec<OrderEventAny>,
576    pub commissions: IndexMap<Currency, Money>,
577    pub venue_order_ids: Vec<VenueOrderId>,
578    pub trade_ids: Vec<TradeId>,
579    pub previous_status: Option<OrderStatus>,
580    pub status: OrderStatus,
581    pub trader_id: TraderId,
582    pub strategy_id: StrategyId,
583    pub instrument_id: InstrumentId,
584    pub client_order_id: ClientOrderId,
585    pub venue_order_id: Option<VenueOrderId>,
586    pub position_id: Option<PositionId>,
587    pub account_id: Option<AccountId>,
588    pub last_trade_id: Option<TradeId>,
589    pub side: OrderSide,
590    pub order_type: OrderType,
591    pub quantity: Quantity,
592    pub time_in_force: TimeInForce,
593    pub liquidity_side: Option<LiquiditySide>,
594    pub is_reduce_only: bool,
595    pub is_quote_quantity: bool,
596    pub emulation_trigger: Option<TriggerType>,
597    pub contingency_type: Option<ContingencyType>,
598    pub order_list_id: Option<OrderListId>,
599    pub linked_order_ids: Option<Vec<ClientOrderId>>,
600    pub parent_order_id: Option<ClientOrderId>,
601    pub exec_algorithm_id: Option<ExecAlgorithmId>,
602    pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
603    pub exec_spawn_id: Option<ClientOrderId>,
604    pub tags: Option<Vec<Ustr>>,
605    pub filled_qty: Quantity,
606    pub leaves_qty: Quantity,
607    pub overfill_qty: Quantity,
608    pub avg_px: Option<f64>,
609    pub slippage: Option<f64>,
610    pub init_id: UUID4,
611    pub ts_init: UnixNanos,
612    pub ts_submitted: Option<UnixNanos>,
613    pub ts_accepted: Option<UnixNanos>,
614    pub ts_closed: Option<UnixNanos>,
615    pub ts_last: UnixNanos,
616}
617
618impl OrderCore {
619    /// Creates a new [`OrderCore`] instance.
620    #[must_use]
621    pub fn new(init: OrderInitialized) -> Self {
622        let events: Vec<OrderEventAny> = vec![OrderEventAny::Initialized(init.clone())];
623        Self {
624            events,
625            commissions: IndexMap::new(),
626            venue_order_ids: Vec::new(),
627            trade_ids: Vec::new(),
628            previous_status: None,
629            status: OrderStatus::Initialized,
630            trader_id: init.trader_id,
631            strategy_id: init.strategy_id,
632            instrument_id: init.instrument_id,
633            client_order_id: init.client_order_id,
634            venue_order_id: None,
635            position_id: None,
636            account_id: None,
637            last_trade_id: None,
638            side: init.order_side,
639            order_type: init.order_type,
640            quantity: init.quantity,
641            time_in_force: init.time_in_force,
642            liquidity_side: Some(LiquiditySide::NoLiquiditySide),
643            is_reduce_only: init.reduce_only,
644            is_quote_quantity: init.quote_quantity,
645            emulation_trigger: init.emulation_trigger.or(Some(TriggerType::NoTrigger)),
646            contingency_type: init
647                .contingency_type
648                .or(Some(ContingencyType::NoContingency)),
649            order_list_id: init.order_list_id,
650            linked_order_ids: init.linked_order_ids,
651            parent_order_id: init.parent_order_id,
652            exec_algorithm_id: init.exec_algorithm_id,
653            exec_algorithm_params: init.exec_algorithm_params,
654            exec_spawn_id: init.exec_spawn_id,
655            tags: init.tags,
656            filled_qty: Quantity::zero(init.quantity.precision),
657            leaves_qty: init.quantity,
658            overfill_qty: Quantity::zero(init.quantity.precision),
659            avg_px: None,
660            slippage: None,
661            init_id: init.event_id,
662            ts_init: init.ts_event,
663            ts_submitted: None,
664            ts_accepted: None,
665            ts_closed: None,
666            ts_last: init.ts_event,
667        }
668    }
669
670    /// Applies the `event` to the order.
671    ///
672    /// # Errors
673    ///
674    /// Returns an error if the event is invalid for the current order status, or if
675    /// `event.client_order_id()` or `event.strategy_id()` does not match the order.
676    pub fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
677        if self.client_order_id != event.client_order_id() {
678            return Err(CorrectnessError::PredicateViolation {
679                message: format!(
680                    "Event client_order_id {} does not match order client_order_id {}",
681                    event.client_order_id(),
682                    self.client_order_id
683                ),
684            }
685            .into());
686        }
687
688        if self.strategy_id != event.strategy_id() {
689            return Err(CorrectnessError::PredicateViolation {
690                message: format!(
691                    "Event strategy_id {} does not match order strategy_id {}",
692                    event.strategy_id(),
693                    self.strategy_id
694                ),
695            }
696            .into());
697        }
698
699        // Save current status as previous_status for ALL transitions except:
700        // - Initialized (no prior state exists)
701        // - ModifyRejected/CancelRejected (need to preserve the pre Pending state)
702        // - When already in Pending* state (avoid overwriting the pre Pending state when receiving multiple pending requests)
703        if !matches!(
704            event,
705            OrderEventAny::Initialized(_)
706                | OrderEventAny::ModifyRejected(_)
707                | OrderEventAny::CancelRejected(_)
708        ) && !matches!(
709            self.status,
710            OrderStatus::PendingUpdate | OrderStatus::PendingCancel
711        ) {
712            self.previous_status = Some(self.status);
713        }
714
715        // Check for duplicate fill before state transition to maintain consistency
716        if let OrderEventAny::Filled(fill) = &event
717            && self.trade_ids.contains(&fill.trade_id)
718        {
719            return Err(OrderError::DuplicateFill(fill.trade_id));
720        }
721
722        if matches!(event, OrderEventAny::Triggered(_))
723            && !TRIGGERABLE_ORDER_TYPES.contains(&self.order_type)
724        {
725            return Err(OrderError::InvalidOrderEvent);
726        }
727
728        let new_status = self.status.transition(&event)?;
729        self.status = new_status;
730
731        match &event {
732            OrderEventAny::Initialized(_) => return Err(OrderError::AlreadyInitialized),
733            OrderEventAny::Denied(event) => self.denied(event),
734            OrderEventAny::Emulated(event) => self.emulated(event),
735            OrderEventAny::Released(event) => self.released(event),
736            OrderEventAny::Submitted(event) => self.submitted(event),
737            OrderEventAny::Rejected(event) => self.rejected(event),
738            OrderEventAny::Accepted(event) => self.accepted(event),
739            OrderEventAny::PendingUpdate(event) => self.pending_update(event),
740            OrderEventAny::PendingCancel(event) => self.pending_cancel(event),
741            OrderEventAny::ModifyRejected(event) => self.modify_rejected(event)?,
742            OrderEventAny::CancelRejected(event) => self.cancel_rejected(event)?,
743            OrderEventAny::Updated(event) => self.updated(event),
744            OrderEventAny::Triggered(event) => self.triggered(event),
745            OrderEventAny::Canceled(event) => self.canceled(event),
746            OrderEventAny::Expired(event) => self.expired(event),
747            OrderEventAny::Filled(event) => self.filled(event),
748        }
749
750        self.ts_last = event.ts_event();
751        self.events.push(event);
752        Ok(())
753    }
754
755    fn denied(&mut self, event: &OrderDenied) {
756        self.ts_closed = Some(event.ts_event);
757    }
758
759    fn emulated(&self, _event: &OrderEmulated) {
760        // Do nothing else
761    }
762
763    fn released(&mut self, _event: &OrderReleased) {
764        self.emulation_trigger = None;
765    }
766
767    fn submitted(&mut self, event: &OrderSubmitted) {
768        self.account_id = Some(event.account_id);
769        self.ts_submitted = Some(event.ts_event);
770    }
771
772    fn accepted(&mut self, event: &OrderAccepted) {
773        self.account_id = Some(event.account_id);
774        self.venue_order_id = Some(event.venue_order_id);
775        self.venue_order_ids.push(event.venue_order_id);
776        self.ts_accepted = Some(event.ts_event);
777    }
778
779    fn rejected(&mut self, event: &OrderRejected) {
780        self.ts_closed = Some(event.ts_event);
781    }
782
783    fn pending_update(&self, _event: &OrderPendingUpdate) {
784        // Do nothing else
785    }
786
787    fn pending_cancel(&self, _event: &OrderPendingCancel) {
788        // Do nothing else
789    }
790
791    fn modify_rejected(&mut self, _event: &OrderModifyRejected) -> Result<(), OrderError> {
792        self.status = self.previous_status.ok_or(OrderError::NoPreviousState)?;
793        Ok(())
794    }
795
796    fn cancel_rejected(&mut self, _event: &OrderCancelRejected) -> Result<(), OrderError> {
797        self.status = self.previous_status.ok_or(OrderError::NoPreviousState)?;
798        Ok(())
799    }
800
801    fn triggered(&self, _event: &OrderTriggered) {}
802
803    fn canceled(&mut self, event: &OrderCanceled) {
804        self.ts_closed = Some(event.ts_event);
805    }
806
807    fn expired(&mut self, event: &OrderExpired) {
808        self.ts_closed = Some(event.ts_event);
809    }
810
811    fn updated(&mut self, event: &OrderUpdated) {
812        if self.status == OrderStatus::PendingUpdate
813            && let Some(previous) = self.previous_status
814        {
815            self.status = previous;
816        }
817
818        if let Some(venue_order_id) = &event.venue_order_id
819            && (self.venue_order_id.is_none()
820                || venue_order_id != self.venue_order_id.as_ref().unwrap())
821        {
822            self.venue_order_id = Some(*venue_order_id);
823            self.venue_order_ids.push(*venue_order_id);
824        }
825
826        self.is_quote_quantity = event.is_quote_quantity;
827    }
828
829    fn filled(&mut self, event: &OrderFilled) {
830        // Use saturating arithmetic to prevent overflow
831        let new_filled_qty = Quantity::from_raw(
832            self.filled_qty.raw.saturating_add(event.last_qty.raw),
833            self.filled_qty.precision,
834        );
835
836        // Calculate overfill if any
837        if new_filled_qty > self.quantity {
838            let overfill_raw = new_filled_qty.raw - self.quantity.raw;
839            self.overfill_qty = Quantity::from_raw(
840                self.overfill_qty.raw.saturating_add(overfill_raw),
841                self.filled_qty.precision,
842            );
843        }
844
845        if new_filled_qty < self.quantity {
846            self.status = OrderStatus::PartiallyFilled;
847        } else {
848            self.status = OrderStatus::Filled;
849            self.ts_closed = Some(event.ts_event);
850        }
851
852        self.venue_order_id = Some(event.venue_order_id);
853        self.position_id = event.position_id;
854        self.trade_ids.push(event.trade_id);
855        self.last_trade_id = Some(event.trade_id);
856        self.liquidity_side = Some(event.liquidity_side);
857        self.filled_qty = new_filled_qty;
858        self.leaves_qty = self.leaves_qty.saturating_sub(event.last_qty);
859        self.ts_last = event.ts_event;
860
861        if self.ts_accepted.is_none() {
862            // Set ts_accepted to time of first fill if not previously set
863            self.ts_accepted = Some(event.ts_event);
864        }
865
866        self.set_avg_px(event.last_qty, event.last_px);
867
868        debug_assert!(
869            matches!(
870                self.status,
871                OrderStatus::PartiallyFilled | OrderStatus::Filled
872            ),
873            "Invariant: status must be PartiallyFilled or Filled after fill handler (status={:?})",
874            self.status
875        );
876        debug_assert!(
877            self.venue_order_id.is_some()
878                && self.last_trade_id.is_some()
879                && !self.trade_ids.is_empty(),
880            "Invariant: venue_order_id, last_trade_id and trade_ids must be set after fill"
881        );
882        debug_assert!(
883            self.filled_qty.raw.saturating_add(self.leaves_qty.raw) >= self.quantity.raw,
884            "Invariant: filled_qty + leaves_qty >= quantity (filled={}, leaves={}, quantity={})",
885            self.filled_qty,
886            self.leaves_qty,
887            self.quantity
888        );
889    }
890
891    fn set_avg_px(&mut self, last_qty: Quantity, last_px: Price) {
892        if self.avg_px.is_none() {
893            self.avg_px = Some(last_px.as_f64());
894            return;
895        }
896
897        // Use previous filled quantity (before current fill) to avoid double-counting
898        let prev_filled_qty = (self.filled_qty - last_qty).as_f64();
899        let last_qty_f64 = last_qty.as_f64();
900        let total_qty = prev_filled_qty + last_qty_f64;
901
902        debug_assert!(
903            total_qty > 0.0,
904            "Invariant: avg_px calc requires positive total_qty (prev={prev_filled_qty}, last={last_qty_f64})"
905        );
906
907        let avg_px = self
908            .avg_px
909            .unwrap()
910            .mul_add(prev_filled_qty, last_px.as_f64() * last_qty_f64)
911            / total_qty;
912        self.avg_px = Some(avg_px);
913    }
914
915    pub fn set_slippage(&mut self, price: Price) {
916        self.slippage = self.avg_px.and_then(|avg_px| {
917            let current_price = price.as_f64();
918            match self.side {
919                OrderSide::Buy if avg_px > current_price => Some(avg_px - current_price),
920                OrderSide::Sell if avg_px < current_price => Some(current_price - avg_px),
921                _ => None,
922            }
923        });
924    }
925
926    /// Returns the opposite order side.
927    #[must_use]
928    pub fn opposite_side(side: OrderSide) -> OrderSide {
929        match side {
930            OrderSide::Buy => OrderSide::Sell,
931            OrderSide::Sell => OrderSide::Buy,
932            OrderSide::NoOrderSide => OrderSide::NoOrderSide,
933        }
934    }
935
936    /// Returns the order side needed to close a position.
937    #[must_use]
938    pub fn closing_side(side: PositionSide) -> OrderSide {
939        match side {
940            PositionSide::Long => OrderSide::Sell,
941            PositionSide::Short => OrderSide::Buy,
942            PositionSide::Flat => OrderSide::NoOrderSide,
943            PositionSide::NoPositionSide => OrderSide::NoOrderSide,
944        }
945    }
946
947    /// # Panics
948    ///
949    /// Panics if the order side is neither `Buy` nor `Sell`.
950    #[must_use]
951    pub fn signed_decimal_qty(&self) -> Decimal {
952        match self.side {
953            OrderSide::Buy => self.quantity.as_decimal(),
954            OrderSide::Sell => -self.quantity.as_decimal(),
955            OrderSide::NoOrderSide => panic!("Invalid order side"),
956        }
957    }
958
959    #[must_use]
960    pub fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
961        if side == PositionSide::Flat {
962            return false;
963        }
964
965        match (self.side, side) {
966            (OrderSide::Buy, PositionSide::Long) => false,
967            (OrderSide::Buy, PositionSide::Short) => self.leaves_qty <= position_qty,
968            (OrderSide::Sell, PositionSide::Short) => false,
969            (OrderSide::Sell, PositionSide::Long) => self.leaves_qty <= position_qty,
970            _ => true,
971        }
972    }
973
974    #[must_use]
975    pub fn commission(&self, currency: &Currency) -> Option<Money> {
976        self.commissions.get(currency).copied()
977    }
978
979    #[must_use]
980    pub fn commissions(&self) -> IndexMap<Currency, Money> {
981        self.commissions.clone()
982    }
983
984    #[must_use]
985    pub fn commissions_vec(&self) -> Vec<Money> {
986        self.commissions.values().copied().collect()
987    }
988
989    #[must_use]
990    pub fn init_event(&self) -> Option<OrderEventAny> {
991        self.events.first().cloned()
992    }
993}
994
995#[cfg(test)]
996mod tests {
997    use rstest::rstest;
998    use rust_decimal_macros::dec;
999
1000    use super::*;
1001    use crate::{
1002        enums::{OrderSide, OrderStatus, PositionSide, TriggerType},
1003        events::order::spec::{
1004            OrderAcceptedSpec, OrderCanceledSpec, OrderDeniedSpec, OrderFilledSpec,
1005            OrderInitializedSpec, OrderPendingUpdateSpec, OrderSubmittedSpec, OrderTriggeredSpec,
1006            OrderUpdatedSpec,
1007        },
1008        identifiers::InstrumentId,
1009        orders::{MarketOrder, builder::OrderTestBuilder},
1010        types::{Price, Quantity},
1011    };
1012
1013    // TODO: WIP
1014    // fn test_display_market_order() {
1015    //     let order = MarketOrder::default();
1016    //     assert_eq!(order.events().len(), 1);
1017    //     assert_eq!(
1018    //         stringify!(order.events().get(0)),
1019    //         stringify!(OrderInitialized)
1020    //     );
1021    // }
1022
1023    #[rstest]
1024    #[case(OrderSide::Buy, OrderSide::Sell)]
1025    #[case(OrderSide::Sell, OrderSide::Buy)]
1026    #[case(OrderSide::NoOrderSide, OrderSide::NoOrderSide)]
1027    fn test_order_opposite_side(#[case] order_side: OrderSide, #[case] expected_side: OrderSide) {
1028        let result = OrderCore::opposite_side(order_side);
1029        assert_eq!(result, expected_side);
1030    }
1031
1032    #[rstest]
1033    #[case(PositionSide::Long, OrderSide::Sell)]
1034    #[case(PositionSide::Short, OrderSide::Buy)]
1035    #[case(PositionSide::NoPositionSide, OrderSide::NoOrderSide)]
1036    fn test_closing_side(#[case] position_side: PositionSide, #[case] expected_side: OrderSide) {
1037        let result = OrderCore::closing_side(position_side);
1038        assert_eq!(result, expected_side);
1039    }
1040
1041    #[rstest]
1042    #[case(OrderSide::Buy, dec!(10_000))]
1043    #[case(OrderSide::Sell, dec!(-10_000))]
1044    fn test_signed_decimal_qty(#[case] order_side: OrderSide, #[case] expected: Decimal) {
1045        let order: MarketOrder = OrderInitializedSpec::builder()
1046            .order_side(order_side)
1047            .quantity(Quantity::from(10_000))
1048            .build()
1049            .into();
1050
1051        let result = order.signed_decimal_qty();
1052        assert_eq!(result, expected);
1053    }
1054
1055    #[rustfmt::skip]
1056    #[rstest]
1057    #[case(OrderSide::Buy, Quantity::from(100), PositionSide::Long, Quantity::from(50), false)]
1058    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(50), true)]
1059    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(100), true)]
1060    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1061    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1062    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(50), true)]
1063    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(100), true)]
1064    #[case(OrderSide::Sell, Quantity::from(100), PositionSide::Short, Quantity::from(50), false)]
1065    fn test_would_reduce_only(
1066        #[case] order_side: OrderSide,
1067        #[case] order_qty: Quantity,
1068        #[case] position_side: PositionSide,
1069        #[case] position_qty: Quantity,
1070        #[case] expected: bool,
1071    ) {
1072        let order: MarketOrder = OrderInitializedSpec::builder()
1073            .order_side(order_side)
1074            .quantity(order_qty)
1075            .build()
1076            .into();
1077
1078        assert_eq!(
1079            order.would_reduce_only(position_side, position_qty),
1080            expected
1081        );
1082    }
1083
1084    #[rstest]
1085    fn test_order_state_transition_denied() {
1086        let mut order: MarketOrder = OrderInitializedSpec::builder().build().into();
1087        let denied = OrderDeniedSpec::builder().build();
1088        let event = OrderEventAny::Denied(denied);
1089
1090        order.apply(event.clone()).unwrap();
1091
1092        assert_eq!(order.status, OrderStatus::Denied);
1093        assert!(order.is_closed());
1094        assert!(!order.is_open());
1095        assert_eq!(order.event_count(), 2);
1096        assert_eq!(order.last_event(), &event);
1097    }
1098
1099    #[rstest]
1100    fn test_order_life_cycle_to_filled() {
1101        let init = OrderInitializedSpec::builder().build();
1102        let submitted = OrderSubmittedSpec::builder().build();
1103        let accepted = OrderAcceptedSpec::builder().build();
1104        let filled = OrderFilledSpec::builder().build();
1105
1106        let mut order: MarketOrder = init.clone().into();
1107        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1108        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1109        order.apply(OrderEventAny::Filled(filled)).unwrap();
1110
1111        assert_eq!(order.client_order_id, init.client_order_id);
1112        assert_eq!(order.status(), OrderStatus::Filled);
1113        assert_eq!(order.filled_qty(), Quantity::from(100_000));
1114        assert_eq!(order.leaves_qty(), Quantity::from(0));
1115        assert_eq!(order.avg_px(), Some(1.0));
1116        assert!(!order.is_open());
1117        assert!(order.is_closed());
1118        assert_eq!(order.commission(&Currency::USD()), None);
1119        assert_eq!(order.commissions(), &IndexMap::new());
1120    }
1121
1122    #[rstest]
1123    fn test_order_life_cycle_fills_with_negative_prices() {
1124        // Options and spreads can legitimately trade at negative prices. The
1125        // weighted average-price update must not panic when `last_px` or the
1126        // prior `avg_px` is below zero.
1127        let init = OrderInitializedSpec::builder()
1128            .quantity(Quantity::from(100_000))
1129            .build();
1130        let submitted = OrderSubmittedSpec::builder().build();
1131        let accepted = OrderAcceptedSpec::builder().build();
1132        let fill1 = OrderFilledSpec::builder()
1133            .last_qty(Quantity::from(50_000))
1134            .last_px(Price::from("-5.00000"))
1135            .trade_id(TradeId::from("TRADE-1"))
1136            .build();
1137        let fill2 = OrderFilledSpec::builder()
1138            .last_qty(Quantity::from(50_000))
1139            .last_px(Price::from("-7.00000"))
1140            .trade_id(TradeId::from("TRADE-2"))
1141            .build();
1142
1143        let mut order: MarketOrder = init.into();
1144        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1145        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1146        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1147        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1148
1149        assert_eq!(order.status(), OrderStatus::Filled);
1150        assert_eq!(order.filled_qty(), Quantity::from(100_000));
1151        assert_eq!(order.leaves_qty(), Quantity::from(0));
1152        // Weighted avg: (50_000 * -5.0 + 50_000 * -7.0) / 100_000 = -6.0
1153        assert_eq!(order.avg_px(), Some(-6.0));
1154    }
1155
1156    #[rstest]
1157    fn test_order_state_transition_to_canceled() {
1158        let mut order: MarketOrder = OrderInitializedSpec::builder().build().into();
1159        let submitted = OrderSubmittedSpec::builder().build();
1160        let canceled = OrderCanceledSpec::builder().build();
1161
1162        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1163        order.apply(OrderEventAny::Canceled(canceled)).unwrap();
1164
1165        assert_eq!(order.status(), OrderStatus::Canceled);
1166        assert!(order.is_closed());
1167        assert!(!order.is_open());
1168    }
1169
1170    #[rstest]
1171    fn test_order_life_cycle_to_partially_filled() {
1172        let init = OrderInitializedSpec::builder().build();
1173        let submitted = OrderSubmittedSpec::builder().build();
1174        let accepted = OrderAcceptedSpec::builder().build();
1175        let filled = OrderFilledSpec::builder()
1176            .last_qty(Quantity::from(50_000))
1177            .build();
1178
1179        let mut order: MarketOrder = init.clone().into();
1180        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1181        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1182        order.apply(OrderEventAny::Filled(filled)).unwrap();
1183
1184        assert_eq!(order.client_order_id, init.client_order_id);
1185        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1186        assert_eq!(order.filled_qty(), Quantity::from(50_000));
1187        assert_eq!(order.leaves_qty(), Quantity::from(50_000));
1188        assert!(order.is_open());
1189        assert!(!order.is_closed());
1190    }
1191
1192    #[rstest]
1193    fn test_order_commission_calculation() {
1194        let mut order: MarketOrder = OrderInitializedSpec::builder().build().into();
1195        order
1196            .commissions
1197            .insert(Currency::USD(), Money::new(10.0, Currency::USD()));
1198
1199        assert_eq!(
1200            order.commission(&Currency::USD()),
1201            Some(Money::new(10.0, Currency::USD()))
1202        );
1203        assert_eq!(
1204            order.commissions_vec(),
1205            vec![Money::new(10.0, Currency::USD())]
1206        );
1207    }
1208
1209    #[rstest]
1210    fn test_order_is_primary() {
1211        let order: MarketOrder = OrderInitializedSpec::builder()
1212            .exec_algorithm_id(ExecAlgorithmId::from("ALGO-001"))
1213            .exec_spawn_id(ClientOrderId::from("O-001"))
1214            .client_order_id(ClientOrderId::from("O-001"))
1215            .build()
1216            .into();
1217
1218        assert!(order.is_primary());
1219        assert!(!order.is_spawned());
1220    }
1221
1222    #[rstest]
1223    fn test_order_is_spawned() {
1224        let order: MarketOrder = OrderInitializedSpec::builder()
1225            .exec_algorithm_id(ExecAlgorithmId::from("ALGO-001"))
1226            .exec_spawn_id(ClientOrderId::from("O-002"))
1227            .client_order_id(ClientOrderId::from("O-001"))
1228            .build()
1229            .into();
1230
1231        assert!(!order.is_primary());
1232        assert!(order.is_spawned());
1233    }
1234
1235    #[rstest]
1236    fn test_order_is_contingency() {
1237        let order: MarketOrder = OrderInitializedSpec::builder()
1238            .contingency_type(ContingencyType::Oto)
1239            .build()
1240            .into();
1241
1242        assert!(order.is_contingency());
1243        assert!(order.is_parent_order());
1244        assert!(!order.is_child_order());
1245    }
1246
1247    #[rstest]
1248    fn test_order_is_child_order() {
1249        let order: MarketOrder = OrderInitializedSpec::builder()
1250            .parent_order_id(ClientOrderId::from("PARENT-001"))
1251            .build()
1252            .into();
1253
1254        assert!(order.is_child_order());
1255        assert!(!order.is_parent_order());
1256    }
1257
1258    #[rstest]
1259    fn test_to_own_book_order_timestamp_ordering() {
1260        use crate::orders::limit::LimitOrder;
1261
1262        // Create order with distinct timestamps to verify parameter ordering
1263        let init = OrderInitializedSpec::builder()
1264            .price(Price::from("100.00"))
1265            .build();
1266        let submitted = OrderSubmittedSpec::builder()
1267            .ts_event(UnixNanos::from(1_000_000))
1268            .build();
1269        let accepted = OrderAcceptedSpec::builder()
1270            .ts_event(UnixNanos::from(2_000_000))
1271            .build();
1272
1273        let mut order: LimitOrder = init.into();
1274        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1275        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1276
1277        let own_book_order = order.to_own_book_order();
1278
1279        // Verify timestamps are in correct positions
1280        assert_eq!(own_book_order.ts_submitted, UnixNanos::from(1_000_000));
1281        assert_eq!(own_book_order.ts_accepted, UnixNanos::from(2_000_000));
1282        assert_eq!(own_book_order.ts_last, UnixNanos::from(2_000_000));
1283    }
1284
1285    #[rstest]
1286    fn test_order_accepted_without_submitted_sets_account_id() {
1287        // Test external order flow: Initialized -> Accepted (no Submitted)
1288        let init = OrderInitializedSpec::builder().build();
1289        let accepted = OrderAcceptedSpec::builder()
1290            .account_id(AccountId::from("EXTERNAL-001"))
1291            .build();
1292
1293        let mut order: MarketOrder = init.into();
1294
1295        // Verify account_id is initially None
1296        assert_eq!(order.account_id(), None);
1297
1298        // Apply accepted event directly (external order case)
1299        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1300
1301        // Verify account_id is now set from the accepted event
1302        assert_eq!(order.account_id(), Some(AccountId::from("EXTERNAL-001")));
1303        assert_eq!(order.status(), OrderStatus::Accepted);
1304    }
1305
1306    #[rstest]
1307    fn test_order_accepted_after_submitted_preserves_account_id() {
1308        // Test normal order flow: Initialized -> Submitted -> Accepted
1309        let init = OrderInitializedSpec::builder().build();
1310        let submitted = OrderSubmittedSpec::builder()
1311            .account_id(AccountId::from("SUBMITTED-001"))
1312            .build();
1313        let accepted = OrderAcceptedSpec::builder()
1314            .account_id(AccountId::from("ACCEPTED-001"))
1315            .build();
1316
1317        let mut order: MarketOrder = init.into();
1318        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1319
1320        // After submitted, account_id should be set
1321        assert_eq!(order.account_id(), Some(AccountId::from("SUBMITTED-001")));
1322
1323        // Apply accepted event
1324        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1325
1326        // account_id should now be updated to the accepted event's account_id
1327        assert_eq!(order.account_id(), Some(AccountId::from("ACCEPTED-001")));
1328        assert_eq!(order.status(), OrderStatus::Accepted);
1329    }
1330
1331    #[rstest]
1332    fn test_overfill_tracks_overfill_qty() {
1333        // Test that overfill is tracked on the order
1334        let init = OrderInitializedSpec::builder()
1335            .quantity(Quantity::from(100_000))
1336            .build();
1337        let submitted = OrderSubmittedSpec::builder().build();
1338        let accepted = OrderAcceptedSpec::builder().build();
1339        let overfill = OrderFilledSpec::builder()
1340            .last_qty(Quantity::from(110_000)) // Overfill: 110k > 100k
1341            .build();
1342
1343        let mut order: MarketOrder = init.into();
1344        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1345        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1346        order.apply(OrderEventAny::Filled(overfill)).unwrap();
1347
1348        // Order should track overfill
1349        assert_eq!(order.overfill_qty(), Quantity::from(10_000));
1350        assert_eq!(order.filled_qty(), Quantity::from(110_000));
1351        assert_eq!(order.leaves_qty(), Quantity::from(0));
1352        assert_eq!(order.status(), OrderStatus::Filled);
1353    }
1354
1355    #[rstest]
1356    fn test_partial_fill_then_overfill() {
1357        // Test multiple fills resulting in overfill
1358        let init = OrderInitializedSpec::builder()
1359            .quantity(Quantity::from(100_000))
1360            .build();
1361        let submitted = OrderSubmittedSpec::builder().build();
1362        let accepted = OrderAcceptedSpec::builder().build();
1363        let fill1 = OrderFilledSpec::builder()
1364            .last_qty(Quantity::from(80_000))
1365            .trade_id(TradeId::from("TRADE-1"))
1366            .build();
1367        let fill2 = OrderFilledSpec::builder()
1368            .last_qty(Quantity::from(30_000)) // Total 110k > 100k
1369            .trade_id(TradeId::from("TRADE-2"))
1370            .build();
1371
1372        let mut order: MarketOrder = init.into();
1373        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1374        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1375        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1376
1377        // After first fill, no overfill
1378        assert_eq!(order.overfill_qty(), Quantity::from(0));
1379        assert_eq!(order.filled_qty(), Quantity::from(80_000));
1380        assert_eq!(order.leaves_qty(), Quantity::from(20_000));
1381
1382        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1383
1384        // After second fill, overfill detected
1385        assert_eq!(order.overfill_qty(), Quantity::from(10_000));
1386        assert_eq!(order.filled_qty(), Quantity::from(110_000));
1387        assert_eq!(order.leaves_qty(), Quantity::from(0));
1388        assert_eq!(order.status(), OrderStatus::Filled);
1389    }
1390
1391    #[rstest]
1392    fn test_exact_fill_no_overfill() {
1393        // Test that exact fill doesn't trigger overfill tracking
1394        let init = OrderInitializedSpec::builder()
1395            .quantity(Quantity::from(100_000))
1396            .build();
1397        let submitted = OrderSubmittedSpec::builder().build();
1398        let accepted = OrderAcceptedSpec::builder().build();
1399        let filled = OrderFilledSpec::builder()
1400            .last_qty(Quantity::from(100_000)) // Exact fill
1401            .build();
1402
1403        let mut order: MarketOrder = init.into();
1404        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1405        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1406        order.apply(OrderEventAny::Filled(filled)).unwrap();
1407
1408        // No overfill
1409        assert_eq!(order.overfill_qty(), Quantity::from(0));
1410        assert_eq!(order.filled_qty(), Quantity::from(100_000));
1411        assert_eq!(order.leaves_qty(), Quantity::from(0));
1412    }
1413
1414    #[rstest]
1415    fn test_partial_fill_then_overfill_with_fractional_quantities() {
1416        // Simulates real exchange scenario with fractional fills:
1417        // Order for 2450.5 units, partially filled 1202.5, then fill of 1285.5 arrives
1418        // Total filled: 2488.0, overfill: 37.5
1419        let init = OrderInitializedSpec::builder()
1420            .quantity(Quantity::from("2450.5"))
1421            .build();
1422        let submitted = OrderSubmittedSpec::builder().build();
1423        let accepted = OrderAcceptedSpec::builder().build();
1424        let fill1 = OrderFilledSpec::builder()
1425            .last_qty(Quantity::from("1202.5"))
1426            .trade_id(TradeId::from("TRADE-1"))
1427            .build();
1428        let fill2 = OrderFilledSpec::builder()
1429            .last_qty(Quantity::from("1285.5")) // 1202.5 + 1285.5 = 2488 > 2450.5
1430            .trade_id(TradeId::from("TRADE-2"))
1431            .build();
1432
1433        let mut order: MarketOrder = init.into();
1434        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1435        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1436        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1437
1438        // After first fill, no overfill
1439        assert_eq!(order.overfill_qty(), Quantity::from(0));
1440        assert_eq!(order.filled_qty(), Quantity::from("1202.5"));
1441        assert_eq!(order.leaves_qty(), Quantity::from("1248.0"));
1442        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1443
1444        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1445
1446        // After second fill, overfill detected and tracked
1447        assert_eq!(order.overfill_qty(), Quantity::from("37.5"));
1448        assert_eq!(order.filled_qty(), Quantity::from("2488.0"));
1449        assert_eq!(order.leaves_qty(), Quantity::from(0));
1450        assert_eq!(order.status(), OrderStatus::Filled);
1451    }
1452
1453    #[rstest]
1454    fn test_calculate_overfill_returns_zero_when_no_overfill() {
1455        let order: MarketOrder = OrderInitializedSpec::builder()
1456            .quantity(Quantity::from(100_000))
1457            .build()
1458            .into();
1459
1460        // Fill qty less than order qty - no overfill
1461        let overfill = order.calculate_overfill(Quantity::from(50_000));
1462        assert_eq!(overfill, Quantity::from(0));
1463
1464        // Fill qty equals order qty - no overfill
1465        let overfill = order.calculate_overfill(Quantity::from(100_000));
1466        assert_eq!(overfill, Quantity::from(0));
1467    }
1468
1469    #[rstest]
1470    fn test_calculate_overfill_returns_overfill_amount() {
1471        let order: MarketOrder = OrderInitializedSpec::builder()
1472            .quantity(Quantity::from(100_000))
1473            .build()
1474            .into();
1475
1476        // Fill qty exceeds order qty
1477        let overfill = order.calculate_overfill(Quantity::from(110_000));
1478        assert_eq!(overfill, Quantity::from(10_000));
1479    }
1480
1481    #[rstest]
1482    fn test_calculate_overfill_accounts_for_existing_fills() {
1483        let init = OrderInitializedSpec::builder()
1484            .quantity(Quantity::from(100_000))
1485            .build();
1486        let submitted = OrderSubmittedSpec::builder().build();
1487        let accepted = OrderAcceptedSpec::builder().build();
1488        let partial_fill = OrderFilledSpec::builder()
1489            .last_qty(Quantity::from(60_000))
1490            .build();
1491
1492        let mut order: MarketOrder = init.into();
1493        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1494        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1495        order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1496
1497        // Order is 60k filled, 40k remaining
1498        // Fill of 50k would overfill by 10k
1499        let overfill = order.calculate_overfill(Quantity::from(50_000));
1500        assert_eq!(overfill, Quantity::from(10_000));
1501
1502        // Fill of 40k would not overfill
1503        let overfill = order.calculate_overfill(Quantity::from(40_000));
1504        assert_eq!(overfill, Quantity::from(0));
1505    }
1506
1507    #[rstest]
1508    fn test_calculate_overfill_with_fractional_quantities() {
1509        let order: MarketOrder = OrderInitializedSpec::builder()
1510            .quantity(Quantity::from("2450.5"))
1511            .build()
1512            .into();
1513
1514        // Simulates the exact scenario from user's log
1515        // Order for 2450.5, if fill of 2488.0 arrives
1516        let overfill = order.calculate_overfill(Quantity::from("2488.0"));
1517        assert_eq!(overfill, Quantity::from("37.5"));
1518    }
1519
1520    #[rstest]
1521    fn test_calculate_overfill_zero_after_fractional_partial_fill() {
1522        let init = OrderInitializedSpec::builder()
1523            .quantity(Quantity::from("1.000"))
1524            .build();
1525        let submitted = OrderSubmittedSpec::builder().build();
1526        let accepted = OrderAcceptedSpec::builder().build();
1527        let partial_fill = OrderFilledSpec::builder()
1528            .last_qty(Quantity::from("0.072"))
1529            .build();
1530
1531        let mut order: MarketOrder = init.into();
1532        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1533        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1534        order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1535
1536        // After filling 0.072 of 1.000, another 0.072 fill should not overfill
1537        let overfill = order.calculate_overfill(Quantity::from("0.072"));
1538        assert_eq!(overfill, Quantity::from("0.000"));
1539    }
1540
1541    #[rstest]
1542    fn test_duplicate_fill_rejected() {
1543        let init = OrderInitializedSpec::builder()
1544            .quantity(Quantity::from(100_000))
1545            .build();
1546        let submitted = OrderSubmittedSpec::builder().build();
1547        let accepted = OrderAcceptedSpec::builder().build();
1548        let fill1 = OrderFilledSpec::builder()
1549            .last_qty(Quantity::from(50_000))
1550            .trade_id(TradeId::from("TRADE-001"))
1551            .build();
1552        let fill2_duplicate = OrderFilledSpec::builder()
1553            .last_qty(Quantity::from(50_000))
1554            .trade_id(TradeId::from("TRADE-001")) // Same trade_id as fill1
1555            .build();
1556
1557        let mut order: MarketOrder = init.into();
1558        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1559        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1560        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1561
1562        // Verify first fill applied successfully
1563        assert_eq!(order.filled_qty(), Quantity::from(50_000));
1564        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1565
1566        // Applying duplicate fill should return DuplicateFill error
1567        let result = order.apply(OrderEventAny::Filled(fill2_duplicate));
1568        assert!(result.is_err());
1569        match result.unwrap_err() {
1570            OrderError::DuplicateFill(trade_id) => {
1571                assert_eq!(trade_id, TradeId::from("TRADE-001"));
1572            }
1573            e => panic!("Expected DuplicateFill error, was: {e:?}"),
1574        }
1575
1576        // Order state should be unchanged after rejected duplicate
1577        assert_eq!(order.filled_qty(), Quantity::from(50_000));
1578        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1579    }
1580
1581    #[rstest]
1582    fn test_check_display_qty_returns_typed_invariant_with_stable_display() {
1583        let error = check_display_qty(Some(Quantity::from(2)), Quantity::from(1)).unwrap_err();
1584
1585        match error {
1586            OrderError::Invariant(CorrectnessError::PredicateViolation { ref message }) => {
1587                assert_eq!(message, "`display_qty` may not exceed `quantity`");
1588            }
1589            other => panic!("Expected typed invariant error, was: {other:?}"),
1590        }
1591
1592        assert_eq!(error.to_string(), "`display_qty` may not exceed `quantity`");
1593    }
1594
1595    #[rstest]
1596    fn test_check_time_in_force_returns_typed_invariant_with_stable_display() {
1597        let error = check_time_in_force(TimeInForce::Gtd, None).unwrap_err();
1598
1599        match error {
1600            OrderError::Invariant(CorrectnessError::PredicateViolation { ref message }) => {
1601                assert_eq!(message, "`expire_time` is required for `GTD` order");
1602            }
1603            other => panic!("Expected typed invariant error, was: {other:?}"),
1604        }
1605
1606        assert_eq!(
1607            error.to_string(),
1608            "`expire_time` is required for `GTD` order"
1609        );
1610    }
1611
1612    #[rstest]
1613    fn test_different_trade_ids_allowed() {
1614        let init = OrderInitializedSpec::builder()
1615            .quantity(Quantity::from(100_000))
1616            .build();
1617        let submitted = OrderSubmittedSpec::builder().build();
1618        let accepted = OrderAcceptedSpec::builder().build();
1619        let fill1 = OrderFilledSpec::builder()
1620            .last_qty(Quantity::from(50_000))
1621            .trade_id(TradeId::from("TRADE-001"))
1622            .build();
1623        let fill2 = OrderFilledSpec::builder()
1624            .last_qty(Quantity::from(50_000))
1625            .trade_id(TradeId::from("TRADE-002")) // Different trade_id
1626            .build();
1627
1628        let mut order: MarketOrder = init.into();
1629        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1630        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1631        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1632        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1633
1634        // Both fills should be applied
1635        assert_eq!(order.filled_qty(), Quantity::from(100_000));
1636        assert_eq!(order.status(), OrderStatus::Filled);
1637        assert_eq!(order.trade_ids.len(), 2);
1638    }
1639
1640    #[rstest]
1641    fn test_pending_update_order_restores_status_on_updated() {
1642        let init = OrderInitializedSpec::builder()
1643            .quantity(Quantity::from(100_000))
1644            .build();
1645        let submitted = OrderSubmittedSpec::builder().build();
1646        let accepted = OrderAcceptedSpec::builder().build();
1647        let pending_update = OrderPendingUpdateSpec::builder().build();
1648        let updated = OrderUpdatedSpec::builder()
1649            .quantity(Quantity::from(50_000))
1650            .build();
1651
1652        let mut order: MarketOrder = init.into();
1653        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1654        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1655
1656        assert_eq!(order.status(), OrderStatus::Accepted);
1657
1658        order
1659            .apply(OrderEventAny::PendingUpdate(pending_update))
1660            .unwrap();
1661        assert_eq!(order.status(), OrderStatus::PendingUpdate);
1662
1663        order.apply(OrderEventAny::Updated(updated)).unwrap();
1664
1665        assert_eq!(order.status(), OrderStatus::Accepted);
1666        assert_eq!(order.quantity(), Quantity::from(50_000));
1667    }
1668
1669    #[rstest]
1670    fn test_partially_filled_order_can_be_updated() {
1671        // Test that a partially filled order can receive an Updated event
1672        // and remain in PartiallyFilled status
1673        let init = OrderInitializedSpec::builder()
1674            .quantity(Quantity::from(100_000))
1675            .build();
1676        let submitted = OrderSubmittedSpec::builder().build();
1677        let accepted = OrderAcceptedSpec::builder().build();
1678        let partial_fill = OrderFilledSpec::builder()
1679            .last_qty(Quantity::from(40_000))
1680            .build();
1681        let updated = OrderUpdatedSpec::builder()
1682            .quantity(Quantity::from(80_000)) // Reduce to 80k (still > 40k filled)
1683            .build();
1684
1685        let mut order: MarketOrder = init.into();
1686        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1687        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1688        order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1689
1690        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1691        assert_eq!(order.filled_qty(), Quantity::from(40_000));
1692
1693        order.apply(OrderEventAny::Updated(updated)).unwrap();
1694
1695        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1696        assert_eq!(order.quantity(), Quantity::from(80_000));
1697        assert_eq!(order.leaves_qty(), Quantity::from(40_000)); // 80k - 40k filled
1698    }
1699
1700    #[rstest]
1701    fn test_triggered_order_can_be_updated() {
1702        // Test that a triggered order can receive an Updated event
1703        // and remain in Triggered status
1704        let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
1705        let submitted = OrderSubmittedSpec::builder().build();
1706        let accepted = OrderAcceptedSpec::builder().build();
1707        let triggered = OrderTriggeredSpec::builder().build();
1708        let updated = OrderUpdatedSpec::builder()
1709            .quantity(Quantity::from(80_000))
1710            .build();
1711
1712        let mut order = OrderTestBuilder::new(OrderType::StopLimit)
1713            .instrument_id(instrument_id)
1714            .quantity(Quantity::from(100_000))
1715            .price(Price::from("0.99500"))
1716            .trigger_price(Price::from("1.00000"))
1717            .trigger_type(TriggerType::LastPrice)
1718            .build();
1719        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1720        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1721        order.apply(OrderEventAny::Triggered(triggered)).unwrap();
1722
1723        assert_eq!(order.status(), OrderStatus::Triggered);
1724
1725        order.apply(OrderEventAny::Updated(updated)).unwrap();
1726
1727        assert_eq!(order.status(), OrderStatus::Triggered);
1728        assert_eq!(order.quantity(), Quantity::from(80_000));
1729    }
1730
1731    #[rstest]
1732    fn test_order_updated_with_is_quote_quantity_clears_flag() {
1733        let init = OrderInitializedSpec::builder()
1734            .quantity(Quantity::new(10.0, 6))
1735            .quote_quantity(true)
1736            .build();
1737        let submitted = OrderSubmittedSpec::builder().build();
1738        let accepted = OrderAcceptedSpec::builder().build();
1739        let updated = OrderUpdatedSpec::builder()
1740            .quantity(Quantity::new(47.393_365, 6))
1741            .is_quote_quantity(false)
1742            .build();
1743
1744        let mut order: MarketOrder = init.into();
1745        assert!(order.is_quote_quantity());
1746
1747        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1748        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1749        order.apply(OrderEventAny::Updated(updated)).unwrap();
1750
1751        assert!(!order.is_quote_quantity());
1752        assert_eq!(order.quantity(), Quantity::new(47.393_365, 6));
1753        assert_eq!(order.leaves_qty(), Quantity::new(47.393_365, 6));
1754    }
1755
1756    #[rstest]
1757    fn test_order_updated_default_is_quote_quantity_clears_flag() {
1758        let init = OrderInitializedSpec::builder()
1759            .quantity(Quantity::new(10.0, 6))
1760            .quote_quantity(true)
1761            .build();
1762        let submitted = OrderSubmittedSpec::builder().build();
1763        let accepted = OrderAcceptedSpec::builder().build();
1764        // Builder defaults is_quote_quantity to false
1765        let updated = OrderUpdatedSpec::builder()
1766            .quantity(Quantity::new(8.0, 6))
1767            .build();
1768
1769        let mut order: MarketOrder = init.into();
1770        assert!(order.is_quote_quantity());
1771
1772        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1773        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1774        order.apply(OrderEventAny::Updated(updated)).unwrap();
1775
1776        assert!(!order.is_quote_quantity());
1777        assert_eq!(order.quantity(), Quantity::new(8.0, 6));
1778    }
1779
1780    #[rstest]
1781    fn test_canceled_then_partial_fill_then_canceled() {
1782        let mut order: MarketOrder = OrderInitializedSpec::builder().build().into();
1783        let submitted = OrderSubmittedSpec::builder().build();
1784        let accepted = OrderAcceptedSpec::builder().build();
1785        let canceled1 = OrderCanceledSpec::builder().build();
1786        let fill = OrderFilledSpec::builder()
1787            .last_qty(Quantity::from(50_000))
1788            .trade_id(TradeId::from("FILL-1"))
1789            .build();
1790        let canceled2 = OrderCanceledSpec::builder().build();
1791
1792        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1793        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1794        order.apply(OrderEventAny::Canceled(canceled1)).unwrap();
1795        assert_eq!(order.status(), OrderStatus::Canceled);
1796        assert!(order.is_closed());
1797
1798        // Fill arrives after cancel (real-world race condition)
1799        order.apply(OrderEventAny::Filled(fill)).unwrap();
1800        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1801        assert_eq!(order.filled_qty(), Quantity::from(50_000));
1802        assert!(order.is_open());
1803
1804        // Re-emitted cancel restores terminal state
1805        order.apply(OrderEventAny::Canceled(canceled2)).unwrap();
1806        assert_eq!(order.status(), OrderStatus::Canceled);
1807        assert!(order.is_closed());
1808    }
1809
1810    #[rstest]
1811    fn test_apply_triggered_to_stop_market_order_returns_error() {
1812        let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
1813        let submitted = OrderSubmittedSpec::builder().build();
1814        let accepted = OrderAcceptedSpec::builder().build();
1815        let triggered = OrderTriggeredSpec::builder().build();
1816
1817        let mut order = OrderTestBuilder::new(OrderType::StopMarket)
1818            .instrument_id(instrument_id)
1819            .quantity(Quantity::from(1))
1820            .trigger_price(Price::from("1.00000"))
1821            .trigger_type(TriggerType::LastPrice)
1822            .build();
1823        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1824        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1825
1826        let result = order.apply(OrderEventAny::Triggered(triggered));
1827        assert!(result.is_err());
1828        assert_eq!(order.status(), OrderStatus::Accepted);
1829    }
1830
1831    #[rstest]
1832    fn test_apply_triggered_to_stop_limit_order_succeeds() {
1833        let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
1834        let submitted = OrderSubmittedSpec::builder().build();
1835        let accepted = OrderAcceptedSpec::builder().build();
1836        let triggered = OrderTriggeredSpec::builder().build();
1837
1838        let mut order = OrderTestBuilder::new(OrderType::StopLimit)
1839            .instrument_id(instrument_id)
1840            .quantity(Quantity::from(1))
1841            .price(Price::from("0.99500"))
1842            .trigger_price(Price::from("1.00000"))
1843            .trigger_type(TriggerType::LastPrice)
1844            .build();
1845        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1846        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1847        order.apply(OrderEventAny::Triggered(triggered)).unwrap();
1848
1849        assert_eq!(order.status(), OrderStatus::Triggered);
1850    }
1851}