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