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