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