nautilus_model/orders/
mod.rs

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