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            causation_id: None,
570        }
571    }
572}
573
574#[derive(Clone, Debug, Serialize, Deserialize)]
575pub struct OrderCore {
576    pub events: Vec<OrderEventAny>,
577    pub commissions: IndexMap<Currency, Money>,
578    pub venue_order_ids: Vec<VenueOrderId>,
579    pub trade_ids: Vec<TradeId>,
580    pub previous_status: Option<OrderStatus>,
581    pub status: OrderStatus,
582    pub trader_id: TraderId,
583    pub strategy_id: StrategyId,
584    pub instrument_id: InstrumentId,
585    pub client_order_id: ClientOrderId,
586    pub venue_order_id: Option<VenueOrderId>,
587    pub position_id: Option<PositionId>,
588    pub account_id: Option<AccountId>,
589    pub last_trade_id: Option<TradeId>,
590    pub side: OrderSide,
591    pub order_type: OrderType,
592    pub quantity: Quantity,
593    pub time_in_force: TimeInForce,
594    pub liquidity_side: Option<LiquiditySide>,
595    pub is_reduce_only: bool,
596    pub is_quote_quantity: bool,
597    pub emulation_trigger: Option<TriggerType>,
598    pub contingency_type: Option<ContingencyType>,
599    pub order_list_id: Option<OrderListId>,
600    pub linked_order_ids: Option<Vec<ClientOrderId>>,
601    pub parent_order_id: Option<ClientOrderId>,
602    pub exec_algorithm_id: Option<ExecAlgorithmId>,
603    pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
604    pub exec_spawn_id: Option<ClientOrderId>,
605    pub tags: Option<Vec<Ustr>>,
606    pub filled_qty: Quantity,
607    pub leaves_qty: Quantity,
608    pub overfill_qty: Quantity,
609    pub avg_px: Option<f64>,
610    pub slippage: Option<f64>,
611    pub init_id: UUID4,
612    pub ts_init: UnixNanos,
613    pub ts_submitted: Option<UnixNanos>,
614    pub ts_accepted: Option<UnixNanos>,
615    pub ts_closed: Option<UnixNanos>,
616    pub ts_last: UnixNanos,
617}
618
619impl OrderCore {
620    /// Creates a new [`OrderCore`] instance.
621    #[must_use]
622    pub fn new(init: OrderInitialized) -> Self {
623        let events: Vec<OrderEventAny> = vec![OrderEventAny::Initialized(init.clone())];
624        Self {
625            events,
626            commissions: IndexMap::new(),
627            venue_order_ids: Vec::new(),
628            trade_ids: Vec::new(),
629            previous_status: None,
630            status: OrderStatus::Initialized,
631            trader_id: init.trader_id,
632            strategy_id: init.strategy_id,
633            instrument_id: init.instrument_id,
634            client_order_id: init.client_order_id,
635            venue_order_id: None,
636            position_id: None,
637            account_id: None,
638            last_trade_id: None,
639            side: init.order_side,
640            order_type: init.order_type,
641            quantity: init.quantity,
642            time_in_force: init.time_in_force,
643            liquidity_side: Some(LiquiditySide::NoLiquiditySide),
644            is_reduce_only: init.reduce_only,
645            is_quote_quantity: init.quote_quantity,
646            emulation_trigger: init.emulation_trigger.or(Some(TriggerType::NoTrigger)),
647            contingency_type: init
648                .contingency_type
649                .or(Some(ContingencyType::NoContingency)),
650            order_list_id: init.order_list_id,
651            linked_order_ids: init.linked_order_ids,
652            parent_order_id: init.parent_order_id,
653            exec_algorithm_id: init.exec_algorithm_id,
654            exec_algorithm_params: init.exec_algorithm_params,
655            exec_spawn_id: init.exec_spawn_id,
656            tags: init.tags,
657            filled_qty: Quantity::zero(init.quantity.precision),
658            leaves_qty: init.quantity,
659            overfill_qty: Quantity::zero(init.quantity.precision),
660            avg_px: None,
661            slippage: None,
662            init_id: init.event_id,
663            ts_init: init.ts_event,
664            ts_submitted: None,
665            ts_accepted: None,
666            ts_closed: None,
667            ts_last: init.ts_event,
668        }
669    }
670
671    /// Applies the `event` to the order.
672    ///
673    /// # Errors
674    ///
675    /// Returns an error if the event is invalid for the current order status, or if
676    /// `event.client_order_id()` or `event.strategy_id()` does not match the order.
677    pub fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
678        if self.client_order_id != event.client_order_id() {
679            return Err(CorrectnessError::PredicateViolation {
680                message: format!(
681                    "Event client_order_id {} does not match order client_order_id {}",
682                    event.client_order_id(),
683                    self.client_order_id
684                ),
685            }
686            .into());
687        }
688
689        if self.strategy_id != event.strategy_id() {
690            return Err(CorrectnessError::PredicateViolation {
691                message: format!(
692                    "Event strategy_id {} does not match order strategy_id {}",
693                    event.strategy_id(),
694                    self.strategy_id
695                ),
696            }
697            .into());
698        }
699
700        // Save current status as previous_status for ALL transitions except:
701        // - Initialized (no prior state exists)
702        // - ModifyRejected/CancelRejected (need to preserve the pre Pending state)
703        // - When already in Pending* state (avoid overwriting the pre Pending state when receiving multiple pending requests)
704        if !matches!(
705            event,
706            OrderEventAny::Initialized(_)
707                | OrderEventAny::ModifyRejected(_)
708                | OrderEventAny::CancelRejected(_)
709        ) && !matches!(
710            self.status,
711            OrderStatus::PendingUpdate | OrderStatus::PendingCancel
712        ) {
713            self.previous_status = Some(self.status);
714        }
715
716        // Check for duplicate fill before state transition to maintain consistency
717        if let OrderEventAny::Filled(fill) = &event
718            && self.trade_ids.contains(&fill.trade_id)
719        {
720            return Err(OrderError::DuplicateFill(fill.trade_id));
721        }
722
723        if matches!(event, OrderEventAny::Triggered(_))
724            && !TRIGGERABLE_ORDER_TYPES.contains(&self.order_type)
725        {
726            return Err(OrderError::InvalidOrderEvent);
727        }
728
729        let new_status = self.status.transition(&event)?;
730        self.status = new_status;
731
732        match &event {
733            OrderEventAny::Initialized(_) => return Err(OrderError::AlreadyInitialized),
734            OrderEventAny::Denied(event) => self.denied(event),
735            OrderEventAny::Emulated(event) => self.emulated(event),
736            OrderEventAny::Released(event) => self.released(event),
737            OrderEventAny::Submitted(event) => self.submitted(event),
738            OrderEventAny::Rejected(event) => self.rejected(event),
739            OrderEventAny::Accepted(event) => self.accepted(event),
740            OrderEventAny::PendingUpdate(event) => self.pending_update(event),
741            OrderEventAny::PendingCancel(event) => self.pending_cancel(event),
742            OrderEventAny::ModifyRejected(event) => self.modify_rejected(event)?,
743            OrderEventAny::CancelRejected(event) => self.cancel_rejected(event)?,
744            OrderEventAny::Updated(event) => self.updated(event),
745            OrderEventAny::Triggered(event) => self.triggered(event),
746            OrderEventAny::Canceled(event) => self.canceled(event),
747            OrderEventAny::Expired(event) => self.expired(event),
748            OrderEventAny::Filled(event) => self.filled(event),
749        }
750
751        self.ts_last = event.ts_event();
752        self.events.push(event);
753        Ok(())
754    }
755
756    fn denied(&mut self, event: &OrderDenied) {
757        self.ts_closed = Some(event.ts_event);
758    }
759
760    fn emulated(&self, _event: &OrderEmulated) {
761        // Do nothing else
762    }
763
764    fn released(&mut self, _event: &OrderReleased) {
765        self.emulation_trigger = None;
766    }
767
768    fn submitted(&mut self, event: &OrderSubmitted) {
769        self.account_id = Some(event.account_id);
770        self.ts_submitted = Some(event.ts_event);
771    }
772
773    fn accepted(&mut self, event: &OrderAccepted) {
774        self.account_id = Some(event.account_id);
775        self.venue_order_id = Some(event.venue_order_id);
776        self.venue_order_ids.push(event.venue_order_id);
777        self.ts_accepted = Some(event.ts_event);
778    }
779
780    fn rejected(&mut self, event: &OrderRejected) {
781        self.ts_closed = Some(event.ts_event);
782    }
783
784    fn pending_update(&self, _event: &OrderPendingUpdate) {
785        // Do nothing else
786    }
787
788    fn pending_cancel(&self, _event: &OrderPendingCancel) {
789        // Do nothing else
790    }
791
792    fn modify_rejected(&mut self, _event: &OrderModifyRejected) -> Result<(), OrderError> {
793        self.status = self.previous_status.ok_or(OrderError::NoPreviousState)?;
794        Ok(())
795    }
796
797    fn cancel_rejected(&mut self, _event: &OrderCancelRejected) -> Result<(), OrderError> {
798        self.status = self.previous_status.ok_or(OrderError::NoPreviousState)?;
799        Ok(())
800    }
801
802    fn triggered(&self, _event: &OrderTriggered) {}
803
804    fn canceled(&mut self, event: &OrderCanceled) {
805        self.ts_closed = Some(event.ts_event);
806    }
807
808    fn expired(&mut self, event: &OrderExpired) {
809        self.ts_closed = Some(event.ts_event);
810    }
811
812    fn updated(&mut self, event: &OrderUpdated) {
813        if self.status == OrderStatus::PendingUpdate
814            && let Some(previous) = self.previous_status
815        {
816            self.status = previous;
817        }
818
819        if let Some(venue_order_id) = &event.venue_order_id
820            && (self.venue_order_id.is_none()
821                || venue_order_id != self.venue_order_id.as_ref().unwrap())
822        {
823            self.venue_order_id = Some(*venue_order_id);
824            self.venue_order_ids.push(*venue_order_id);
825        }
826
827        self.is_quote_quantity = event.is_quote_quantity;
828    }
829
830    fn filled(&mut self, event: &OrderFilled) {
831        // Use saturating arithmetic to prevent overflow
832        let new_filled_qty = Quantity::from_raw(
833            self.filled_qty.raw.saturating_add(event.last_qty.raw),
834            self.filled_qty.precision,
835        );
836
837        // Calculate overfill if any
838        if new_filled_qty > self.quantity {
839            let overfill_raw = new_filled_qty.raw - self.quantity.raw;
840            self.overfill_qty = Quantity::from_raw(
841                self.overfill_qty.raw.saturating_add(overfill_raw),
842                self.filled_qty.precision,
843            );
844        }
845
846        if new_filled_qty < self.quantity {
847            self.status = OrderStatus::PartiallyFilled;
848        } else {
849            self.status = OrderStatus::Filled;
850            self.ts_closed = Some(event.ts_event);
851        }
852
853        self.venue_order_id = Some(event.venue_order_id);
854        self.position_id = event.position_id;
855        self.trade_ids.push(event.trade_id);
856        self.last_trade_id = Some(event.trade_id);
857        self.liquidity_side = Some(event.liquidity_side);
858        self.filled_qty = new_filled_qty;
859        self.leaves_qty = self.leaves_qty.saturating_sub(event.last_qty);
860        self.ts_last = event.ts_event;
861
862        if let Some(commission) = event.commission {
863            let commission_currency = commission.currency;
864            if let Some(existing_commission) = self.commissions.get_mut(&commission_currency) {
865                *existing_commission = *existing_commission + commission;
866            } else {
867                self.commissions.insert(commission_currency, commission);
868            }
869        }
870
871        if self.ts_accepted.is_none() {
872            // Set ts_accepted to time of first fill if not previously set
873            self.ts_accepted = Some(event.ts_event);
874        }
875
876        self.set_avg_px(event.last_qty, event.last_px);
877
878        debug_assert!(
879            matches!(
880                self.status,
881                OrderStatus::PartiallyFilled | OrderStatus::Filled
882            ),
883            "Invariant: status must be PartiallyFilled or Filled after fill handler (status={:?})",
884            self.status
885        );
886        debug_assert!(
887            self.venue_order_id.is_some()
888                && self.last_trade_id.is_some()
889                && !self.trade_ids.is_empty(),
890            "Invariant: venue_order_id, last_trade_id and trade_ids must be set after fill"
891        );
892        debug_assert!(
893            self.filled_qty.raw.saturating_add(self.leaves_qty.raw) >= self.quantity.raw,
894            "Invariant: filled_qty + leaves_qty >= quantity (filled={}, leaves={}, quantity={})",
895            self.filled_qty,
896            self.leaves_qty,
897            self.quantity
898        );
899    }
900
901    fn set_avg_px(&mut self, last_qty: Quantity, last_px: Price) {
902        if self.avg_px.is_none() {
903            self.avg_px = Some(last_px.as_f64());
904            return;
905        }
906
907        // Use previous filled quantity (before current fill) to avoid double-counting
908        let prev_filled_qty = (self.filled_qty - last_qty).as_f64();
909        let last_qty_f64 = last_qty.as_f64();
910        let total_qty = prev_filled_qty + last_qty_f64;
911
912        debug_assert!(
913            total_qty > 0.0,
914            "Invariant: avg_px calc requires positive total_qty (prev={prev_filled_qty}, last={last_qty_f64})"
915        );
916
917        let avg_px = self
918            .avg_px
919            .unwrap()
920            .mul_add(prev_filled_qty, last_px.as_f64() * last_qty_f64)
921            / total_qty;
922        self.avg_px = Some(avg_px);
923    }
924
925    pub fn set_slippage(&mut self, price: Price) {
926        self.slippage = self.avg_px.and_then(|avg_px| {
927            let current_price = price.as_f64();
928            match self.side {
929                OrderSide::Buy if avg_px > current_price => Some(avg_px - current_price),
930                OrderSide::Sell if avg_px < current_price => Some(current_price - avg_px),
931                _ => None,
932            }
933        });
934    }
935
936    /// Returns the opposite order side.
937    #[must_use]
938    pub fn opposite_side(side: OrderSide) -> OrderSide {
939        match side {
940            OrderSide::Buy => OrderSide::Sell,
941            OrderSide::Sell => OrderSide::Buy,
942            OrderSide::NoOrderSide => OrderSide::NoOrderSide,
943        }
944    }
945
946    /// Returns the order side needed to close a position.
947    #[must_use]
948    pub fn closing_side(side: PositionSide) -> OrderSide {
949        match side {
950            PositionSide::Long => OrderSide::Sell,
951            PositionSide::Short => OrderSide::Buy,
952            PositionSide::Flat => OrderSide::NoOrderSide,
953            PositionSide::NoPositionSide => OrderSide::NoOrderSide,
954        }
955    }
956
957    /// # Panics
958    ///
959    /// Panics if the order side is neither `Buy` nor `Sell`.
960    #[must_use]
961    pub fn signed_decimal_qty(&self) -> Decimal {
962        match self.side {
963            OrderSide::Buy => self.quantity.as_decimal(),
964            OrderSide::Sell => -self.quantity.as_decimal(),
965            OrderSide::NoOrderSide => panic!("Invalid order side"),
966        }
967    }
968
969    #[must_use]
970    pub fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
971        if side == PositionSide::Flat {
972            return false;
973        }
974
975        match (self.side, side) {
976            (OrderSide::Buy, PositionSide::Long) => false,
977            (OrderSide::Buy, PositionSide::Short) => self.leaves_qty <= position_qty,
978            (OrderSide::Sell, PositionSide::Short) => false,
979            (OrderSide::Sell, PositionSide::Long) => self.leaves_qty <= position_qty,
980            _ => true,
981        }
982    }
983
984    #[must_use]
985    pub fn commission(&self, currency: &Currency) -> Option<Money> {
986        self.commissions.get(currency).copied()
987    }
988
989    #[must_use]
990    pub fn commissions(&self) -> IndexMap<Currency, Money> {
991        self.commissions.clone()
992    }
993
994    #[must_use]
995    pub fn commissions_vec(&self) -> Vec<Money> {
996        self.commissions.values().copied().collect()
997    }
998
999    #[must_use]
1000    pub fn init_event(&self) -> Option<OrderEventAny> {
1001        self.events.first().cloned()
1002    }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007    use rstest::rstest;
1008    use rust_decimal_macros::dec;
1009
1010    use super::*;
1011    use crate::{
1012        enums::{OrderSide, OrderStatus, PositionSide, TriggerType},
1013        events::order::spec::{
1014            OrderAcceptedSpec, OrderCanceledSpec, OrderDeniedSpec, OrderFilledSpec,
1015            OrderInitializedSpec, OrderPendingUpdateSpec, OrderSubmittedSpec, OrderTriggeredSpec,
1016            OrderUpdatedSpec,
1017        },
1018        identifiers::InstrumentId,
1019        orders::{MarketOrder, builder::OrderTestBuilder},
1020        types::{Price, Quantity},
1021    };
1022
1023    // TODO: WIP
1024    // fn test_display_market_order() {
1025    //     let order = MarketOrder::default();
1026    //     assert_eq!(order.events().len(), 1);
1027    //     assert_eq!(
1028    //         stringify!(order.events().get(0)),
1029    //         stringify!(OrderInitialized)
1030    //     );
1031    // }
1032
1033    #[rstest]
1034    #[case(OrderSide::Buy, OrderSide::Sell)]
1035    #[case(OrderSide::Sell, OrderSide::Buy)]
1036    #[case(OrderSide::NoOrderSide, OrderSide::NoOrderSide)]
1037    fn test_order_opposite_side(#[case] order_side: OrderSide, #[case] expected_side: OrderSide) {
1038        let result = OrderCore::opposite_side(order_side);
1039        assert_eq!(result, expected_side);
1040    }
1041
1042    #[rstest]
1043    #[case(PositionSide::Long, OrderSide::Sell)]
1044    #[case(PositionSide::Short, OrderSide::Buy)]
1045    #[case(PositionSide::NoPositionSide, OrderSide::NoOrderSide)]
1046    fn test_closing_side(#[case] position_side: PositionSide, #[case] expected_side: OrderSide) {
1047        let result = OrderCore::closing_side(position_side);
1048        assert_eq!(result, expected_side);
1049    }
1050
1051    #[rstest]
1052    #[case(OrderSide::Buy, dec!(10_000))]
1053    #[case(OrderSide::Sell, dec!(-10_000))]
1054    fn test_signed_decimal_qty(#[case] order_side: OrderSide, #[case] expected: Decimal) {
1055        let order: MarketOrder = OrderInitializedSpec::builder()
1056            .order_side(order_side)
1057            .quantity(Quantity::from(10_000))
1058            .build()
1059            .try_into()
1060            .unwrap();
1061
1062        let result = order.signed_decimal_qty();
1063        assert_eq!(result, expected);
1064    }
1065
1066    #[rustfmt::skip]
1067    #[rstest]
1068    #[case(OrderSide::Buy, Quantity::from(100), PositionSide::Long, Quantity::from(50), false)]
1069    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(50), true)]
1070    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(100), true)]
1071    #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1072    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1073    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(50), true)]
1074    #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(100), true)]
1075    #[case(OrderSide::Sell, Quantity::from(100), PositionSide::Short, Quantity::from(50), false)]
1076    fn test_would_reduce_only(
1077        #[case] order_side: OrderSide,
1078        #[case] order_qty: Quantity,
1079        #[case] position_side: PositionSide,
1080        #[case] position_qty: Quantity,
1081        #[case] expected: bool,
1082    ) {
1083        let order: MarketOrder = OrderInitializedSpec::builder()
1084            .order_side(order_side)
1085            .quantity(order_qty)
1086            .build()
1087            .try_into()
1088            .unwrap();
1089
1090        assert_eq!(
1091            order.would_reduce_only(position_side, position_qty),
1092            expected
1093        );
1094    }
1095
1096    #[rstest]
1097    fn test_order_state_transition_denied() {
1098        let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1099        let denied = OrderDeniedSpec::builder().build();
1100        let event = OrderEventAny::Denied(denied);
1101
1102        order.apply(event.clone()).unwrap();
1103
1104        assert_eq!(order.status, OrderStatus::Denied);
1105        assert!(order.is_closed());
1106        assert!(!order.is_open());
1107        assert_eq!(order.event_count(), 2);
1108        assert_eq!(order.last_event(), &event);
1109    }
1110
1111    #[rstest]
1112    fn test_order_life_cycle_to_filled() {
1113        let init = OrderInitializedSpec::builder().build();
1114        let submitted = OrderSubmittedSpec::builder().build();
1115        let accepted = OrderAcceptedSpec::builder().build();
1116        let filled = OrderFilledSpec::builder().build();
1117
1118        let mut order: MarketOrder = init.clone().try_into().unwrap();
1119        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1120        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1121        order.apply(OrderEventAny::Filled(filled)).unwrap();
1122
1123        assert_eq!(order.client_order_id, init.client_order_id);
1124        assert_eq!(order.status(), OrderStatus::Filled);
1125        assert_eq!(order.filled_qty(), Quantity::from(100_000));
1126        assert_eq!(order.leaves_qty(), Quantity::from(0));
1127        assert_eq!(order.avg_px(), Some(1.0));
1128        assert!(!order.is_open());
1129        assert!(order.is_closed());
1130        assert_eq!(order.commission(&Currency::USD()), None);
1131        assert_eq!(order.commissions(), &IndexMap::new());
1132    }
1133
1134    #[rstest]
1135    fn test_order_life_cycle_fills_with_negative_prices() {
1136        // Options and spreads can legitimately trade at negative prices. The
1137        // weighted average-price update must not panic when `last_px` or the
1138        // prior `avg_px` is below zero.
1139        let init = OrderInitializedSpec::builder()
1140            .quantity(Quantity::from(100_000))
1141            .build();
1142        let submitted = OrderSubmittedSpec::builder().build();
1143        let accepted = OrderAcceptedSpec::builder().build();
1144        let fill1 = OrderFilledSpec::builder()
1145            .last_qty(Quantity::from(50_000))
1146            .last_px(Price::from("-5.00000"))
1147            .trade_id(TradeId::from("TRADE-1"))
1148            .build();
1149        let fill2 = OrderFilledSpec::builder()
1150            .last_qty(Quantity::from(50_000))
1151            .last_px(Price::from("-7.00000"))
1152            .trade_id(TradeId::from("TRADE-2"))
1153            .build();
1154
1155        let mut order: MarketOrder = init.try_into().unwrap();
1156        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1157        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1158        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1159        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1160
1161        assert_eq!(order.status(), OrderStatus::Filled);
1162        assert_eq!(order.filled_qty(), Quantity::from(100_000));
1163        assert_eq!(order.leaves_qty(), Quantity::from(0));
1164        // Weighted avg: (50_000 * -5.0 + 50_000 * -7.0) / 100_000 = -6.0
1165        assert_eq!(order.avg_px(), Some(-6.0));
1166    }
1167
1168    #[rstest]
1169    fn test_order_life_cycle_accumulates_fill_commissions() {
1170        let init = OrderInitializedSpec::builder()
1171            .quantity(Quantity::from(100_000))
1172            .build();
1173        let submitted = OrderSubmittedSpec::builder().build();
1174        let accepted = OrderAcceptedSpec::builder().build();
1175        let fill1 = OrderFilledSpec::builder()
1176            .last_qty(Quantity::from(50_000))
1177            .trade_id(TradeId::from("TRADE-1"))
1178            .commission(Money::from("1.25 USD"))
1179            .build();
1180        let fill2 = OrderFilledSpec::builder()
1181            .last_qty(Quantity::from(50_000))
1182            .trade_id(TradeId::from("TRADE-2"))
1183            .commission(Money::from("1.35 USD"))
1184            .build();
1185
1186        let mut order: MarketOrder = init.try_into().unwrap();
1187        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1188        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1189        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1190        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1191
1192        assert_eq!(order.status(), OrderStatus::Filled);
1193        assert_eq!(
1194            order.commission(&Currency::USD()),
1195            Some(Money::from("2.60 USD"))
1196        );
1197        assert_eq!(order.commissions_vec(), vec![Money::from("2.60 USD")]);
1198    }
1199
1200    #[rstest]
1201    fn test_order_state_transition_to_canceled() {
1202        let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1203        let submitted = OrderSubmittedSpec::builder().build();
1204        let canceled = OrderCanceledSpec::builder().build();
1205
1206        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1207        order.apply(OrderEventAny::Canceled(canceled)).unwrap();
1208
1209        assert_eq!(order.status(), OrderStatus::Canceled);
1210        assert!(order.is_closed());
1211        assert!(!order.is_open());
1212    }
1213
1214    #[rstest]
1215    fn test_order_life_cycle_to_partially_filled() {
1216        let init = OrderInitializedSpec::builder().build();
1217        let submitted = OrderSubmittedSpec::builder().build();
1218        let accepted = OrderAcceptedSpec::builder().build();
1219        let filled = OrderFilledSpec::builder()
1220            .last_qty(Quantity::from(50_000))
1221            .build();
1222
1223        let mut order: MarketOrder = init.clone().try_into().unwrap();
1224        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1225        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1226        order.apply(OrderEventAny::Filled(filled)).unwrap();
1227
1228        assert_eq!(order.client_order_id, init.client_order_id);
1229        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1230        assert_eq!(order.filled_qty(), Quantity::from(50_000));
1231        assert_eq!(order.leaves_qty(), Quantity::from(50_000));
1232        assert!(order.is_open());
1233        assert!(!order.is_closed());
1234    }
1235
1236    #[rstest]
1237    fn test_order_commission_calculation() {
1238        let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1239        order
1240            .commissions
1241            .insert(Currency::USD(), Money::new(10.0, Currency::USD()));
1242
1243        assert_eq!(
1244            order.commission(&Currency::USD()),
1245            Some(Money::new(10.0, Currency::USD()))
1246        );
1247        assert_eq!(
1248            order.commissions_vec(),
1249            vec![Money::new(10.0, Currency::USD())]
1250        );
1251    }
1252
1253    #[rstest]
1254    fn test_order_is_primary() {
1255        let order: MarketOrder = OrderInitializedSpec::builder()
1256            .exec_algorithm_id(ExecAlgorithmId::from("ALGO-001"))
1257            .exec_spawn_id(ClientOrderId::from("O-001"))
1258            .client_order_id(ClientOrderId::from("O-001"))
1259            .build()
1260            .try_into()
1261            .unwrap();
1262
1263        assert!(order.is_primary());
1264        assert!(!order.is_spawned());
1265    }
1266
1267    #[rstest]
1268    fn test_order_is_spawned() {
1269        let order: MarketOrder = OrderInitializedSpec::builder()
1270            .exec_algorithm_id(ExecAlgorithmId::from("ALGO-001"))
1271            .exec_spawn_id(ClientOrderId::from("O-002"))
1272            .client_order_id(ClientOrderId::from("O-001"))
1273            .build()
1274            .try_into()
1275            .unwrap();
1276
1277        assert!(!order.is_primary());
1278        assert!(order.is_spawned());
1279    }
1280
1281    #[rstest]
1282    fn test_order_is_contingency() {
1283        let order: MarketOrder = OrderInitializedSpec::builder()
1284            .contingency_type(ContingencyType::Oto)
1285            .build()
1286            .try_into()
1287            .unwrap();
1288
1289        assert!(order.is_contingency());
1290        assert!(order.is_parent_order());
1291        assert!(!order.is_child_order());
1292    }
1293
1294    #[rstest]
1295    fn test_order_is_child_order() {
1296        let order: MarketOrder = OrderInitializedSpec::builder()
1297            .parent_order_id(ClientOrderId::from("PARENT-001"))
1298            .build()
1299            .try_into()
1300            .unwrap();
1301
1302        assert!(order.is_child_order());
1303        assert!(!order.is_parent_order());
1304    }
1305
1306    #[rstest]
1307    fn test_to_own_book_order_timestamp_ordering() {
1308        use crate::orders::limit::LimitOrder;
1309
1310        // Create order with distinct timestamps to verify parameter ordering
1311        let init = OrderInitializedSpec::builder()
1312            .price(Price::from("100.00"))
1313            .build();
1314        let submitted = OrderSubmittedSpec::builder()
1315            .ts_event(UnixNanos::from(1_000_000))
1316            .build();
1317        let accepted = OrderAcceptedSpec::builder()
1318            .ts_event(UnixNanos::from(2_000_000))
1319            .build();
1320
1321        let mut order: LimitOrder = init.try_into().unwrap();
1322        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1323        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1324
1325        let own_book_order = order.to_own_book_order();
1326
1327        // Verify timestamps are in correct positions
1328        assert_eq!(own_book_order.ts_submitted, UnixNanos::from(1_000_000));
1329        assert_eq!(own_book_order.ts_accepted, UnixNanos::from(2_000_000));
1330        assert_eq!(own_book_order.ts_last, UnixNanos::from(2_000_000));
1331    }
1332
1333    #[rstest]
1334    fn test_order_accepted_without_submitted_sets_account_id() {
1335        // Test external order flow: Initialized -> Accepted (no Submitted)
1336        let init = OrderInitializedSpec::builder().build();
1337        let accepted = OrderAcceptedSpec::builder()
1338            .account_id(AccountId::from("EXTERNAL-001"))
1339            .build();
1340
1341        let mut order: MarketOrder = init.try_into().unwrap();
1342
1343        // Verify account_id is initially None
1344        assert_eq!(order.account_id(), None);
1345
1346        // Apply accepted event directly (external order case)
1347        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1348
1349        // Verify account_id is now set from the accepted event
1350        assert_eq!(order.account_id(), Some(AccountId::from("EXTERNAL-001")));
1351        assert_eq!(order.status(), OrderStatus::Accepted);
1352    }
1353
1354    #[rstest]
1355    fn test_order_accepted_after_submitted_preserves_account_id() {
1356        // Test normal order flow: Initialized -> Submitted -> Accepted
1357        let init = OrderInitializedSpec::builder().build();
1358        let submitted = OrderSubmittedSpec::builder()
1359            .account_id(AccountId::from("SUBMITTED-001"))
1360            .build();
1361        let accepted = OrderAcceptedSpec::builder()
1362            .account_id(AccountId::from("ACCEPTED-001"))
1363            .build();
1364
1365        let mut order: MarketOrder = init.try_into().unwrap();
1366        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1367
1368        // After submitted, account_id should be set
1369        assert_eq!(order.account_id(), Some(AccountId::from("SUBMITTED-001")));
1370
1371        // Apply accepted event
1372        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1373
1374        // account_id should now be updated to the accepted event's account_id
1375        assert_eq!(order.account_id(), Some(AccountId::from("ACCEPTED-001")));
1376        assert_eq!(order.status(), OrderStatus::Accepted);
1377    }
1378
1379    #[rstest]
1380    fn test_overfill_tracks_overfill_qty() {
1381        // Test that overfill is tracked on the order
1382        let init = OrderInitializedSpec::builder()
1383            .quantity(Quantity::from(100_000))
1384            .build();
1385        let submitted = OrderSubmittedSpec::builder().build();
1386        let accepted = OrderAcceptedSpec::builder().build();
1387        let overfill = OrderFilledSpec::builder()
1388            .last_qty(Quantity::from(110_000)) // Overfill: 110k > 100k
1389            .build();
1390
1391        let mut order: MarketOrder = init.try_into().unwrap();
1392        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1393        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1394        order.apply(OrderEventAny::Filled(overfill)).unwrap();
1395
1396        // Order should track overfill
1397        assert_eq!(order.overfill_qty(), Quantity::from(10_000));
1398        assert_eq!(order.filled_qty(), Quantity::from(110_000));
1399        assert_eq!(order.leaves_qty(), Quantity::from(0));
1400        assert_eq!(order.status(), OrderStatus::Filled);
1401    }
1402
1403    #[rstest]
1404    fn test_partial_fill_then_overfill() {
1405        // Test multiple fills resulting in overfill
1406        let init = OrderInitializedSpec::builder()
1407            .quantity(Quantity::from(100_000))
1408            .build();
1409        let submitted = OrderSubmittedSpec::builder().build();
1410        let accepted = OrderAcceptedSpec::builder().build();
1411        let fill1 = OrderFilledSpec::builder()
1412            .last_qty(Quantity::from(80_000))
1413            .trade_id(TradeId::from("TRADE-1"))
1414            .build();
1415        let fill2 = OrderFilledSpec::builder()
1416            .last_qty(Quantity::from(30_000)) // Total 110k > 100k
1417            .trade_id(TradeId::from("TRADE-2"))
1418            .build();
1419
1420        let mut order: MarketOrder = init.try_into().unwrap();
1421        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1422        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1423        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1424
1425        // After first fill, no overfill
1426        assert_eq!(order.overfill_qty(), Quantity::from(0));
1427        assert_eq!(order.filled_qty(), Quantity::from(80_000));
1428        assert_eq!(order.leaves_qty(), Quantity::from(20_000));
1429
1430        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1431
1432        // After second fill, overfill detected
1433        assert_eq!(order.overfill_qty(), Quantity::from(10_000));
1434        assert_eq!(order.filled_qty(), Quantity::from(110_000));
1435        assert_eq!(order.leaves_qty(), Quantity::from(0));
1436        assert_eq!(order.status(), OrderStatus::Filled);
1437    }
1438
1439    #[rstest]
1440    fn test_exact_fill_no_overfill() {
1441        // Test that exact fill doesn't trigger overfill tracking
1442        let init = OrderInitializedSpec::builder()
1443            .quantity(Quantity::from(100_000))
1444            .build();
1445        let submitted = OrderSubmittedSpec::builder().build();
1446        let accepted = OrderAcceptedSpec::builder().build();
1447        let filled = OrderFilledSpec::builder()
1448            .last_qty(Quantity::from(100_000)) // Exact fill
1449            .build();
1450
1451        let mut order: MarketOrder = init.try_into().unwrap();
1452        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1453        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1454        order.apply(OrderEventAny::Filled(filled)).unwrap();
1455
1456        // No overfill
1457        assert_eq!(order.overfill_qty(), Quantity::from(0));
1458        assert_eq!(order.filled_qty(), Quantity::from(100_000));
1459        assert_eq!(order.leaves_qty(), Quantity::from(0));
1460    }
1461
1462    #[rstest]
1463    fn test_partial_fill_then_overfill_with_fractional_quantities() {
1464        // Simulates real exchange scenario with fractional fills:
1465        // Order for 2450.5 units, partially filled 1202.5, then fill of 1285.5 arrives
1466        // Total filled: 2488.0, overfill: 37.5
1467        let init = OrderInitializedSpec::builder()
1468            .quantity(Quantity::from("2450.5"))
1469            .build();
1470        let submitted = OrderSubmittedSpec::builder().build();
1471        let accepted = OrderAcceptedSpec::builder().build();
1472        let fill1 = OrderFilledSpec::builder()
1473            .last_qty(Quantity::from("1202.5"))
1474            .trade_id(TradeId::from("TRADE-1"))
1475            .build();
1476        let fill2 = OrderFilledSpec::builder()
1477            .last_qty(Quantity::from("1285.5")) // 1202.5 + 1285.5 = 2488 > 2450.5
1478            .trade_id(TradeId::from("TRADE-2"))
1479            .build();
1480
1481        let mut order: MarketOrder = init.try_into().unwrap();
1482        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1483        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1484        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1485
1486        // After first fill, no overfill
1487        assert_eq!(order.overfill_qty(), Quantity::from(0));
1488        assert_eq!(order.filled_qty(), Quantity::from("1202.5"));
1489        assert_eq!(order.leaves_qty(), Quantity::from("1248.0"));
1490        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1491
1492        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1493
1494        // After second fill, overfill detected and tracked
1495        assert_eq!(order.overfill_qty(), Quantity::from("37.5"));
1496        assert_eq!(order.filled_qty(), Quantity::from("2488.0"));
1497        assert_eq!(order.leaves_qty(), Quantity::from(0));
1498        assert_eq!(order.status(), OrderStatus::Filled);
1499    }
1500
1501    #[rstest]
1502    fn test_calculate_overfill_returns_zero_when_no_overfill() {
1503        let order: MarketOrder = OrderInitializedSpec::builder()
1504            .quantity(Quantity::from(100_000))
1505            .build()
1506            .try_into()
1507            .unwrap();
1508
1509        // Fill qty less than order qty - no overfill
1510        let overfill = order.calculate_overfill(Quantity::from(50_000));
1511        assert_eq!(overfill, Quantity::from(0));
1512
1513        // Fill qty equals order qty - no overfill
1514        let overfill = order.calculate_overfill(Quantity::from(100_000));
1515        assert_eq!(overfill, Quantity::from(0));
1516    }
1517
1518    #[rstest]
1519    fn test_calculate_overfill_returns_overfill_amount() {
1520        let order: MarketOrder = OrderInitializedSpec::builder()
1521            .quantity(Quantity::from(100_000))
1522            .build()
1523            .try_into()
1524            .unwrap();
1525
1526        // Fill qty exceeds order qty
1527        let overfill = order.calculate_overfill(Quantity::from(110_000));
1528        assert_eq!(overfill, Quantity::from(10_000));
1529    }
1530
1531    #[rstest]
1532    fn test_calculate_overfill_accounts_for_existing_fills() {
1533        let init = OrderInitializedSpec::builder()
1534            .quantity(Quantity::from(100_000))
1535            .build();
1536        let submitted = OrderSubmittedSpec::builder().build();
1537        let accepted = OrderAcceptedSpec::builder().build();
1538        let partial_fill = OrderFilledSpec::builder()
1539            .last_qty(Quantity::from(60_000))
1540            .build();
1541
1542        let mut order: MarketOrder = init.try_into().unwrap();
1543        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1544        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1545        order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1546
1547        // Order is 60k filled, 40k remaining
1548        // Fill of 50k would overfill by 10k
1549        let overfill = order.calculate_overfill(Quantity::from(50_000));
1550        assert_eq!(overfill, Quantity::from(10_000));
1551
1552        // Fill of 40k would not overfill
1553        let overfill = order.calculate_overfill(Quantity::from(40_000));
1554        assert_eq!(overfill, Quantity::from(0));
1555    }
1556
1557    #[rstest]
1558    fn test_calculate_overfill_with_fractional_quantities() {
1559        let order: MarketOrder = OrderInitializedSpec::builder()
1560            .quantity(Quantity::from("2450.5"))
1561            .build()
1562            .try_into()
1563            .unwrap();
1564
1565        // Simulates the exact scenario from user's log
1566        // Order for 2450.5, if fill of 2488.0 arrives
1567        let overfill = order.calculate_overfill(Quantity::from("2488.0"));
1568        assert_eq!(overfill, Quantity::from("37.5"));
1569    }
1570
1571    #[rstest]
1572    fn test_calculate_overfill_zero_after_fractional_partial_fill() {
1573        let init = OrderInitializedSpec::builder()
1574            .quantity(Quantity::from("1.000"))
1575            .build();
1576        let submitted = OrderSubmittedSpec::builder().build();
1577        let accepted = OrderAcceptedSpec::builder().build();
1578        let partial_fill = OrderFilledSpec::builder()
1579            .last_qty(Quantity::from("0.072"))
1580            .build();
1581
1582        let mut order: MarketOrder = init.try_into().unwrap();
1583        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1584        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1585        order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1586
1587        // After filling 0.072 of 1.000, another 0.072 fill should not overfill
1588        let overfill = order.calculate_overfill(Quantity::from("0.072"));
1589        assert_eq!(overfill, Quantity::from("0.000"));
1590    }
1591
1592    #[rstest]
1593    fn test_duplicate_fill_rejected() {
1594        let init = OrderInitializedSpec::builder()
1595            .quantity(Quantity::from(100_000))
1596            .build();
1597        let submitted = OrderSubmittedSpec::builder().build();
1598        let accepted = OrderAcceptedSpec::builder().build();
1599        let fill1 = OrderFilledSpec::builder()
1600            .last_qty(Quantity::from(50_000))
1601            .trade_id(TradeId::from("TRADE-001"))
1602            .build();
1603        let fill2_duplicate = OrderFilledSpec::builder()
1604            .last_qty(Quantity::from(50_000))
1605            .trade_id(TradeId::from("TRADE-001")) // Same trade_id as fill1
1606            .build();
1607
1608        let mut order: MarketOrder = init.try_into().unwrap();
1609        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1610        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1611        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1612
1613        // Verify first fill applied successfully
1614        assert_eq!(order.filled_qty(), Quantity::from(50_000));
1615        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1616
1617        // Applying duplicate fill should return DuplicateFill error
1618        let result = order.apply(OrderEventAny::Filled(fill2_duplicate));
1619        assert!(result.is_err());
1620        match result.unwrap_err() {
1621            OrderError::DuplicateFill(trade_id) => {
1622                assert_eq!(trade_id, TradeId::from("TRADE-001"));
1623            }
1624            e => panic!("Expected DuplicateFill error, was: {e:?}"),
1625        }
1626
1627        // Order state should be unchanged after rejected duplicate
1628        assert_eq!(order.filled_qty(), Quantity::from(50_000));
1629        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1630    }
1631
1632    #[rstest]
1633    fn test_check_display_qty_returns_typed_invariant_with_stable_display() {
1634        let error = check_display_qty(Some(Quantity::from(2)), Quantity::from(1)).unwrap_err();
1635
1636        match error {
1637            OrderError::Invariant(CorrectnessError::PredicateViolation { ref message }) => {
1638                assert_eq!(message, "`display_qty` may not exceed `quantity`");
1639            }
1640            other => panic!("Expected typed invariant error, was: {other:?}"),
1641        }
1642
1643        assert_eq!(error.to_string(), "`display_qty` may not exceed `quantity`");
1644    }
1645
1646    #[rstest]
1647    fn test_check_time_in_force_returns_typed_invariant_with_stable_display() {
1648        let error = check_time_in_force(TimeInForce::Gtd, None).unwrap_err();
1649
1650        match error {
1651            OrderError::Invariant(CorrectnessError::PredicateViolation { ref message }) => {
1652                assert_eq!(message, "`expire_time` is required for `GTD` order");
1653            }
1654            other => panic!("Expected typed invariant error, was: {other:?}"),
1655        }
1656
1657        assert_eq!(
1658            error.to_string(),
1659            "`expire_time` is required for `GTD` order"
1660        );
1661    }
1662
1663    #[rstest]
1664    fn test_different_trade_ids_allowed() {
1665        let init = OrderInitializedSpec::builder()
1666            .quantity(Quantity::from(100_000))
1667            .build();
1668        let submitted = OrderSubmittedSpec::builder().build();
1669        let accepted = OrderAcceptedSpec::builder().build();
1670        let fill1 = OrderFilledSpec::builder()
1671            .last_qty(Quantity::from(50_000))
1672            .trade_id(TradeId::from("TRADE-001"))
1673            .build();
1674        let fill2 = OrderFilledSpec::builder()
1675            .last_qty(Quantity::from(50_000))
1676            .trade_id(TradeId::from("TRADE-002")) // Different trade_id
1677            .build();
1678
1679        let mut order: MarketOrder = init.try_into().unwrap();
1680        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1681        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1682        order.apply(OrderEventAny::Filled(fill1)).unwrap();
1683        order.apply(OrderEventAny::Filled(fill2)).unwrap();
1684
1685        // Both fills should be applied
1686        assert_eq!(order.filled_qty(), Quantity::from(100_000));
1687        assert_eq!(order.status(), OrderStatus::Filled);
1688        assert_eq!(order.trade_ids.len(), 2);
1689    }
1690
1691    #[rstest]
1692    fn test_pending_update_order_restores_status_on_updated() {
1693        let init = OrderInitializedSpec::builder()
1694            .quantity(Quantity::from(100_000))
1695            .build();
1696        let submitted = OrderSubmittedSpec::builder().build();
1697        let accepted = OrderAcceptedSpec::builder().build();
1698        let pending_update = OrderPendingUpdateSpec::builder().build();
1699        let updated = OrderUpdatedSpec::builder()
1700            .quantity(Quantity::from(50_000))
1701            .build();
1702
1703        let mut order: MarketOrder = init.try_into().unwrap();
1704        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1705        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1706
1707        assert_eq!(order.status(), OrderStatus::Accepted);
1708
1709        order
1710            .apply(OrderEventAny::PendingUpdate(pending_update))
1711            .unwrap();
1712        assert_eq!(order.status(), OrderStatus::PendingUpdate);
1713
1714        order.apply(OrderEventAny::Updated(updated)).unwrap();
1715
1716        assert_eq!(order.status(), OrderStatus::Accepted);
1717        assert_eq!(order.quantity(), Quantity::from(50_000));
1718    }
1719
1720    #[rstest]
1721    fn test_partially_filled_order_can_be_updated() {
1722        // Test that a partially filled order can receive an Updated event
1723        // and remain in PartiallyFilled status
1724        let init = OrderInitializedSpec::builder()
1725            .quantity(Quantity::from(100_000))
1726            .build();
1727        let submitted = OrderSubmittedSpec::builder().build();
1728        let accepted = OrderAcceptedSpec::builder().build();
1729        let partial_fill = OrderFilledSpec::builder()
1730            .last_qty(Quantity::from(40_000))
1731            .build();
1732        let updated = OrderUpdatedSpec::builder()
1733            .quantity(Quantity::from(80_000)) // Reduce to 80k (still > 40k filled)
1734            .build();
1735
1736        let mut order: MarketOrder = init.try_into().unwrap();
1737        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1738        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1739        order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1740
1741        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1742        assert_eq!(order.filled_qty(), Quantity::from(40_000));
1743
1744        order.apply(OrderEventAny::Updated(updated)).unwrap();
1745
1746        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1747        assert_eq!(order.quantity(), Quantity::from(80_000));
1748        assert_eq!(order.leaves_qty(), Quantity::from(40_000)); // 80k - 40k filled
1749    }
1750
1751    #[rstest]
1752    fn test_triggered_order_can_be_updated() {
1753        // Test that a triggered order can receive an Updated event
1754        // and remain in Triggered status
1755        let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
1756        let submitted = OrderSubmittedSpec::builder().build();
1757        let accepted = OrderAcceptedSpec::builder().build();
1758        let triggered = OrderTriggeredSpec::builder().build();
1759        let updated = OrderUpdatedSpec::builder()
1760            .quantity(Quantity::from(80_000))
1761            .build();
1762
1763        let mut order = OrderTestBuilder::new(OrderType::StopLimit)
1764            .instrument_id(instrument_id)
1765            .quantity(Quantity::from(100_000))
1766            .price(Price::from("0.99500"))
1767            .trigger_price(Price::from("1.00000"))
1768            .trigger_type(TriggerType::LastPrice)
1769            .build();
1770        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1771        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1772        order.apply(OrderEventAny::Triggered(triggered)).unwrap();
1773
1774        assert_eq!(order.status(), OrderStatus::Triggered);
1775
1776        order.apply(OrderEventAny::Updated(updated)).unwrap();
1777
1778        assert_eq!(order.status(), OrderStatus::Triggered);
1779        assert_eq!(order.quantity(), Quantity::from(80_000));
1780    }
1781
1782    #[rstest]
1783    fn test_order_updated_with_is_quote_quantity_clears_flag() {
1784        let init = OrderInitializedSpec::builder()
1785            .quantity(Quantity::new(10.0, 6))
1786            .quote_quantity(true)
1787            .build();
1788        let submitted = OrderSubmittedSpec::builder().build();
1789        let accepted = OrderAcceptedSpec::builder().build();
1790        let updated = OrderUpdatedSpec::builder()
1791            .quantity(Quantity::new(47.393_365, 6))
1792            .is_quote_quantity(false)
1793            .build();
1794
1795        let mut order: MarketOrder = init.try_into().unwrap();
1796        assert!(order.is_quote_quantity());
1797
1798        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1799        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1800        order.apply(OrderEventAny::Updated(updated)).unwrap();
1801
1802        assert!(!order.is_quote_quantity());
1803        assert_eq!(order.quantity(), Quantity::new(47.393_365, 6));
1804        assert_eq!(order.leaves_qty(), Quantity::new(47.393_365, 6));
1805    }
1806
1807    #[rstest]
1808    fn test_order_updated_default_is_quote_quantity_clears_flag() {
1809        let init = OrderInitializedSpec::builder()
1810            .quantity(Quantity::new(10.0, 6))
1811            .quote_quantity(true)
1812            .build();
1813        let submitted = OrderSubmittedSpec::builder().build();
1814        let accepted = OrderAcceptedSpec::builder().build();
1815        // Builder defaults is_quote_quantity to false
1816        let updated = OrderUpdatedSpec::builder()
1817            .quantity(Quantity::new(8.0, 6))
1818            .build();
1819
1820        let mut order: MarketOrder = init.try_into().unwrap();
1821        assert!(order.is_quote_quantity());
1822
1823        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1824        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1825        order.apply(OrderEventAny::Updated(updated)).unwrap();
1826
1827        assert!(!order.is_quote_quantity());
1828        assert_eq!(order.quantity(), Quantity::new(8.0, 6));
1829    }
1830
1831    #[rstest]
1832    fn test_canceled_then_partial_fill_then_canceled() {
1833        let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1834        let submitted = OrderSubmittedSpec::builder().build();
1835        let accepted = OrderAcceptedSpec::builder().build();
1836        let canceled1 = OrderCanceledSpec::builder().build();
1837        let fill = OrderFilledSpec::builder()
1838            .last_qty(Quantity::from(50_000))
1839            .trade_id(TradeId::from("FILL-1"))
1840            .build();
1841        let canceled2 = OrderCanceledSpec::builder().build();
1842
1843        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1844        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1845        order.apply(OrderEventAny::Canceled(canceled1)).unwrap();
1846        assert_eq!(order.status(), OrderStatus::Canceled);
1847        assert!(order.is_closed());
1848
1849        // Fill arrives after cancel (real-world race condition)
1850        order.apply(OrderEventAny::Filled(fill)).unwrap();
1851        assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1852        assert_eq!(order.filled_qty(), Quantity::from(50_000));
1853        assert!(order.is_open());
1854
1855        // Re-emitted cancel restores terminal state
1856        order.apply(OrderEventAny::Canceled(canceled2)).unwrap();
1857        assert_eq!(order.status(), OrderStatus::Canceled);
1858        assert!(order.is_closed());
1859    }
1860
1861    #[rstest]
1862    fn test_apply_triggered_to_stop_market_order_returns_error() {
1863        let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
1864        let submitted = OrderSubmittedSpec::builder().build();
1865        let accepted = OrderAcceptedSpec::builder().build();
1866        let triggered = OrderTriggeredSpec::builder().build();
1867
1868        let mut order = OrderTestBuilder::new(OrderType::StopMarket)
1869            .instrument_id(instrument_id)
1870            .quantity(Quantity::from(1))
1871            .trigger_price(Price::from("1.00000"))
1872            .trigger_type(TriggerType::LastPrice)
1873            .build();
1874        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1875        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1876
1877        let result = order.apply(OrderEventAny::Triggered(triggered));
1878        assert!(result.is_err());
1879        assert_eq!(order.status(), OrderStatus::Accepted);
1880    }
1881
1882    #[rstest]
1883    fn test_apply_triggered_to_stop_limit_order_succeeds() {
1884        let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
1885        let submitted = OrderSubmittedSpec::builder().build();
1886        let accepted = OrderAcceptedSpec::builder().build();
1887        let triggered = OrderTriggeredSpec::builder().build();
1888
1889        let mut order = OrderTestBuilder::new(OrderType::StopLimit)
1890            .instrument_id(instrument_id)
1891            .quantity(Quantity::from(1))
1892            .price(Price::from("0.99500"))
1893            .trigger_price(Price::from("1.00000"))
1894            .trigger_type(TriggerType::LastPrice)
1895            .build();
1896        order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1897        order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1898        order.apply(OrderEventAny::Triggered(triggered)).unwrap();
1899
1900        assert_eq!(order.status(), OrderStatus::Triggered);
1901    }
1902}