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, OrderReplayError, PassiveOrderAny, StopOrderAny},
51 limit::LimitOrder,
52 limit_if_touched::LimitIfTouchedOrder,
53 list::{OrderList, OrderListValidationError},
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 reports::OrderStatusReport,
79 types::{Currency, Money, Price, Quantity},
80};
81
82pub const STOP_ORDER_TYPES: &[OrderType] = &[
84 OrderType::StopMarket,
85 OrderType::StopLimit,
86 OrderType::MarketIfTouched,
87 OrderType::LimitIfTouched,
88];
89
90pub const LIMIT_ORDER_TYPES: &[OrderType] = &[
92 OrderType::Limit,
93 OrderType::StopLimit,
94 OrderType::LimitIfTouched,
95 OrderType::TrailingStopLimit,
96];
97
98pub const TRIGGERABLE_ORDER_TYPES: &[OrderType] = &[
103 OrderType::StopLimit,
104 OrderType::TrailingStopLimit,
105 OrderType::LimitIfTouched,
106];
107
108pub const LOCAL_ACTIVE_ORDER_STATUSES: &[OrderStatus] = &[
110 OrderStatus::Initialized,
111 OrderStatus::Emulated,
112 OrderStatus::Released,
113];
114
115pub const CANCELLABLE_ORDER_STATUSES: &[OrderStatus] = &[
124 OrderStatus::Accepted,
125 OrderStatus::Triggered,
126 OrderStatus::PendingUpdate,
127 OrderStatus::PartiallyFilled,
128];
129
130#[must_use]
139pub fn cancellable_order_statuses_set() -> &'static AHashSet<OrderStatus> {
140 OrderStatus::cancellable_statuses_set()
141}
142
143#[derive(thiserror::Error, Debug)]
144pub enum OrderError {
145 #[error("Order not found: {0}")]
146 NotFound(ClientOrderId),
147 #[error("Order invariant failed: must have a side for this operation")]
148 NoOrderSide,
149 #[error("Invalid event for order type")]
150 InvalidOrderEvent,
151 #[error("Invalid order state transition")]
152 InvalidStateTransition,
153 #[error("Order was already initialized")]
154 AlreadyInitialized,
155 #[error("Order had no previous state")]
156 NoPreviousState,
157 #[error("Duplicate fill: trade_id {0} already applied to order")]
158 DuplicateFill(TradeId),
159 #[error("{0}")]
160 Invariant(#[from] CorrectnessError),
161}
162
163#[must_use]
165pub fn ustr_indexmap_to_str(h: IndexMap<Ustr, Ustr>) -> IndexMap<String, String> {
166 h.into_iter()
167 .map(|(k, v)| (k.to_string(), v.to_string()))
168 .collect()
169}
170
171#[must_use]
173pub fn str_indexmap_to_ustr(h: IndexMap<String, String>) -> IndexMap<Ustr, Ustr> {
174 h.into_iter()
175 .map(|(k, v)| (Ustr::from(&k), Ustr::from(&v)))
176 .collect()
177}
178
179#[inline]
180pub(crate) fn check_display_qty(
181 display_qty: Option<Quantity>,
182 quantity: Quantity,
183) -> Result<(), OrderError> {
184 if let Some(q) = display_qty {
185 check_predicate_false(q > quantity, "`display_qty` may not exceed `quantity`")?;
186 }
187 Ok(())
188}
189
190#[inline]
191pub(crate) fn check_time_in_force(
192 time_in_force: TimeInForce,
193 expire_time: Option<UnixNanos>,
194) -> Result<(), OrderError> {
195 check_predicate_false(
196 time_in_force == TimeInForce::Gtd && expire_time.unwrap_or_default() == 0,
197 "`expire_time` is required for `GTD` order",
198 )?;
199 Ok(())
200}
201
202impl OrderStatus {
203 #[rustfmt::skip]
209 pub fn transition(&mut self, event: &OrderEventAny) -> Result<Self, OrderError> {
210 let new_state = match (self, event) {
211 (Self::Initialized, OrderEventAny::Denied(_)) => Self::Denied,
212 (Self::Initialized, OrderEventAny::Emulated(_)) => Self::Emulated, (Self::Initialized, OrderEventAny::Released(_)) => Self::Released, (Self::Initialized, OrderEventAny::Submitted(_)) => Self::Submitted,
215 (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::Updated(_)) => Self::Emulated, (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,
230 (Self::Submitted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
231 (Self::Submitted, OrderEventAny::Rejected(_)) => Self::Rejected,
232 (Self::Submitted, OrderEventAny::Canceled(_)) => Self::Canceled, (Self::Submitted, OrderEventAny::Accepted(_)) => Self::Accepted,
234 (Self::Submitted, OrderEventAny::Updated(_)) => Self::Submitted,
235 (Self::Submitted, OrderEventAny::Filled(_)) => Self::Filled,
236 (Self::Accepted, OrderEventAny::Rejected(_)) => Self::Rejected, (Self::Accepted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
238 (Self::Accepted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
239 (Self::Accepted, OrderEventAny::Canceled(_)) => Self::Canceled,
240 (Self::Accepted, OrderEventAny::Triggered(_)) => Self::Triggered,
241 (Self::Accepted, OrderEventAny::Updated(_)) => Self::Accepted, (Self::Accepted, OrderEventAny::Expired(_)) => Self::Expired,
243 (Self::Accepted, OrderEventAny::Filled(_)) => Self::Filled,
244 (Self::Canceled, OrderEventAny::Filled(_)) => Self::Filled, (Self::PendingUpdate, OrderEventAny::Rejected(_)) => Self::Rejected,
246 (Self::PendingUpdate, OrderEventAny::Accepted(_)) => Self::Accepted,
247 (Self::PendingUpdate, OrderEventAny::Canceled(_)) => Self::Canceled,
248 (Self::PendingUpdate, OrderEventAny::Expired(_)) => Self::Expired,
249 (Self::PendingUpdate, OrderEventAny::Triggered(_)) => Self::Triggered,
250 (Self::PendingUpdate, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
252 (Self::PendingUpdate, OrderEventAny::ModifyRejected(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::Updated(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::Filled(_)) => Self::Filled,
255 (Self::PendingCancel, OrderEventAny::Rejected(_)) => Self::Rejected,
256 (Self::PendingCancel, OrderEventAny::PendingCancel(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::CancelRejected(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::Canceled(_)) => Self::Canceled,
259 (Self::PendingCancel, OrderEventAny::Expired(_)) => Self::Expired,
260 (Self::PendingCancel, OrderEventAny::Accepted(_)) => Self::Accepted, (Self::PendingCancel, OrderEventAny::Filled(_)) => Self::Filled,
262 (Self::Triggered, OrderEventAny::Rejected(_)) => Self::Rejected,
263 (Self::Triggered, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
264 (Self::Triggered, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
265 (Self::Triggered, OrderEventAny::Canceled(_)) => Self::Canceled,
266 (Self::Triggered, OrderEventAny::Expired(_)) => Self::Expired,
267 (Self::Triggered, OrderEventAny::Filled(_)) => Self::Filled,
268 (Self::Triggered, OrderEventAny::Updated(_)) => Self::Triggered,
269 (Self::PartiallyFilled, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
270 (Self::PartiallyFilled, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
271 (Self::PartiallyFilled, OrderEventAny::Canceled(_)) => Self::Canceled,
272 (Self::PartiallyFilled, OrderEventAny::Expired(_)) => Self::Expired,
273 (Self::PartiallyFilled, OrderEventAny::Filled(_)) => Self::Filled,
274 (Self::PartiallyFilled, OrderEventAny::Accepted(_)) => Self::Accepted,
275 (Self::PartiallyFilled, OrderEventAny::Updated(_)) => Self::PartiallyFilled,
276 _ => return Err(OrderError::InvalidStateTransition),
277 };
278 Ok(new_state)
279 }
280}
281
282#[enum_dispatch]
283pub trait Order: 'static + Send {
284 fn into_any(self) -> OrderAny;
285 fn status(&self) -> OrderStatus;
286 fn trader_id(&self) -> TraderId;
287 fn strategy_id(&self) -> StrategyId;
288 fn instrument_id(&self) -> InstrumentId;
289 fn symbol(&self) -> Symbol;
290 fn venue(&self) -> Venue;
291 fn client_order_id(&self) -> ClientOrderId;
292 fn venue_order_id(&self) -> Option<VenueOrderId>;
293 fn position_id(&self) -> Option<PositionId>;
294 fn account_id(&self) -> Option<AccountId>;
295 fn last_trade_id(&self) -> Option<TradeId>;
296 fn order_side(&self) -> OrderSide;
297 fn order_type(&self) -> OrderType;
298 fn quantity(&self) -> Quantity;
299 fn time_in_force(&self) -> TimeInForce;
300 fn expire_time(&self) -> Option<UnixNanos>;
301 fn price(&self) -> Option<Price>;
302 fn trigger_price(&self) -> Option<Price>;
303 fn activation_price(&self) -> Option<Price> {
304 None
305 }
306 fn trigger_type(&self) -> Option<TriggerType>;
307 fn liquidity_side(&self) -> Option<LiquiditySide>;
308 fn is_post_only(&self) -> bool;
309 fn is_reduce_only(&self) -> bool;
310 fn is_quote_quantity(&self) -> bool;
311 fn display_qty(&self) -> Option<Quantity>;
312 fn limit_offset(&self) -> Option<Decimal>;
313 fn trailing_offset(&self) -> Option<Decimal>;
314 fn trailing_offset_type(&self) -> Option<TrailingOffsetType>;
315 fn emulation_trigger(&self) -> Option<TriggerType>;
316 fn trigger_instrument_id(&self) -> Option<InstrumentId>;
317 fn contingency_type(&self) -> Option<ContingencyType>;
318 fn order_list_id(&self) -> Option<OrderListId>;
319 fn linked_order_ids(&self) -> Option<&[ClientOrderId]>;
320 fn parent_order_id(&self) -> Option<ClientOrderId>;
321 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId>;
322 fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>>;
323 fn exec_spawn_id(&self) -> Option<ClientOrderId>;
324 fn tags(&self) -> Option<&[Ustr]>;
325 fn filled_qty(&self) -> Quantity;
326 fn leaves_qty(&self) -> Quantity;
327 fn overfill_qty(&self) -> Quantity;
328
329 fn calculate_overfill(&self, fill_qty: Quantity) -> Quantity {
331 let potential_filled = self.filled_qty() + fill_qty;
332 let quantity = self.quantity();
333 if potential_filled > quantity {
334 potential_filled - quantity
335 } else {
336 Quantity::zero(fill_qty.precision)
337 }
338 }
339
340 fn avg_px(&self) -> Option<f64>;
341 fn slippage(&self) -> Option<f64>;
342 fn init_id(&self) -> UUID4;
343 fn ts_init(&self) -> UnixNanos;
344 fn ts_submitted(&self) -> Option<UnixNanos>;
345 fn ts_accepted(&self) -> Option<UnixNanos>;
346 fn ts_closed(&self) -> Option<UnixNanos>;
347 fn ts_last(&self) -> UnixNanos;
348
349 fn order_side_specified(&self) -> OrderSideSpecified {
350 self.order_side().as_specified()
351 }
352 fn commissions(&self) -> &IndexMap<Currency, Money>;
353
354 fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError>;
360 fn update(&mut self, event: &OrderUpdated);
361
362 fn events(&self) -> Vec<&OrderEventAny>;
363
364 fn last_event(&self) -> &OrderEventAny {
365 self.events()
366 .last()
367 .expect("Order invariant violated: no events")
368 }
369
370 fn event_count(&self) -> usize {
371 self.events().len()
372 }
373
374 fn venue_order_ids(&self) -> Vec<&VenueOrderId>;
375
376 fn trade_ids(&self) -> Vec<&TradeId>;
377
378 fn has_price(&self) -> bool;
379
380 fn is_duplicate_fill(&self, fill: &OrderFilled) -> bool {
382 self.events().iter().any(|event| {
383 if let OrderEventAny::Filled(existing) = event {
384 existing.trade_id == fill.trade_id
385 && existing.order_side == fill.order_side
386 && existing.last_qty == fill.last_qty
387 && existing.last_px == fill.last_px
388 } else {
389 false
390 }
391 })
392 }
393
394 fn is_buy(&self) -> bool {
395 self.order_side() == OrderSide::Buy
396 }
397
398 fn is_sell(&self) -> bool {
399 self.order_side() == OrderSide::Sell
400 }
401
402 fn is_passive(&self) -> bool {
403 self.order_type() != OrderType::Market
404 }
405
406 fn is_aggressive(&self) -> bool {
407 self.order_type() == OrderType::Market
408 }
409
410 fn is_emulated(&self) -> bool {
411 self.status() == OrderStatus::Emulated
412 }
413
414 fn is_active_local(&self) -> bool {
415 matches!(
416 self.status(),
417 OrderStatus::Initialized | OrderStatus::Emulated | OrderStatus::Released
418 )
419 }
420
421 fn is_primary(&self) -> bool {
422 self.exec_algorithm_id().is_some()
423 && self
424 .exec_spawn_id()
425 .is_some_and(|spawn_id| self.client_order_id() == spawn_id)
426 }
427
428 fn is_spawned(&self) -> bool {
429 self.exec_algorithm_id().is_some()
430 && self
431 .exec_spawn_id()
432 .is_some_and(|spawn_id| self.client_order_id() != spawn_id)
433 }
434
435 fn is_contingency(&self) -> bool {
436 self.contingency_type().is_some()
437 }
438
439 fn is_parent_order(&self) -> bool {
440 match self.contingency_type() {
441 Some(c) => c == ContingencyType::Oto,
442 None => false,
443 }
444 }
445
446 fn is_child_order(&self) -> bool {
447 self.parent_order_id().is_some()
448 }
449
450 fn is_open(&self) -> bool {
451 if let Some(emulation_trigger) = self.emulation_trigger()
452 && emulation_trigger != TriggerType::NoTrigger
453 {
454 return false;
455 }
456
457 matches!(
458 self.status(),
459 OrderStatus::Accepted
460 | OrderStatus::Triggered
461 | OrderStatus::PendingCancel
462 | OrderStatus::PendingUpdate
463 | OrderStatus::PartiallyFilled
464 )
465 }
466
467 fn is_canceled(&self) -> bool {
468 self.status() == OrderStatus::Canceled
469 }
470
471 fn is_closed(&self) -> bool {
472 matches!(
473 self.status(),
474 OrderStatus::Denied
475 | OrderStatus::Rejected
476 | OrderStatus::Canceled
477 | OrderStatus::Expired
478 | OrderStatus::Filled
479 )
480 }
481
482 fn is_inflight(&self) -> bool {
483 if let Some(emulation_trigger) = self.emulation_trigger()
484 && emulation_trigger != TriggerType::NoTrigger
485 {
486 return false;
487 }
488
489 matches!(
490 self.status(),
491 OrderStatus::Submitted | OrderStatus::PendingCancel | OrderStatus::PendingUpdate
492 )
493 }
494
495 fn is_pending_update(&self) -> bool {
496 self.status() == OrderStatus::PendingUpdate
497 }
498
499 fn is_pending_cancel(&self) -> bool {
500 self.status() == OrderStatus::PendingCancel
501 }
502
503 fn to_own_book_order(&self) -> OwnBookOrder {
504 OwnBookOrder::new(
505 self.trader_id(),
506 self.client_order_id(),
507 self.venue_order_id(),
508 self.order_side().as_specified(),
509 self.price().expect("`OwnBookOrder` must have a price"), self.quantity(),
511 self.order_type(),
512 self.time_in_force(),
513 self.status(),
514 self.ts_last(),
515 self.ts_accepted().unwrap_or_default(),
516 self.ts_submitted().unwrap_or_default(),
517 self.ts_init(),
518 )
519 }
520
521 fn to_order_status_report(&self, report_id: Option<UUID4>) -> Option<OrderStatusReport> {
527 let account_id = self.account_id()?;
528 let venue_order_id = self.venue_order_id()?;
529
530 let mut report = OrderStatusReport::new(
531 account_id,
532 self.instrument_id(),
533 Some(self.client_order_id()),
534 venue_order_id,
535 self.order_side(),
536 self.order_type(),
537 self.time_in_force(),
538 self.status(),
539 self.quantity(),
540 self.filled_qty(),
541 self.ts_accepted().unwrap_or_else(|| self.ts_last()),
542 self.ts_last(),
543 self.ts_init(),
544 report_id,
545 )
546 .with_post_only(self.is_post_only())
547 .with_reduce_only(self.is_reduce_only());
548
549 if let Some(price) = self.price() {
550 report = report.with_price(price);
551 }
552
553 if let Some(trigger_price) = self.trigger_price() {
554 report = report.with_trigger_price(trigger_price);
555 }
556
557 if let Some(trigger_type) = self.trigger_type() {
558 report = report.with_trigger_type(trigger_type);
559 }
560
561 if let Some(limit_offset) = self.limit_offset() {
562 report = report.with_limit_offset(limit_offset);
563 }
564
565 if let Some(trailing_offset) = self.trailing_offset() {
566 report = report.with_trailing_offset(trailing_offset);
567 }
568
569 if let Some(trailing_offset_type) = self.trailing_offset_type() {
570 report = report.with_trailing_offset_type(trailing_offset_type);
571 }
572
573 if let Some(display_qty) = self.display_qty() {
574 report = report.with_display_qty(display_qty);
575 }
576
577 if let Some(expire_time) = self.expire_time() {
578 report = report.with_expire_time(expire_time);
579 }
580
581 if let Some(contingency_type) = self.contingency_type() {
582 report = report.with_contingency_type(contingency_type);
583 }
584
585 if let Some(order_list_id) = self.order_list_id() {
586 report = report.with_order_list_id(order_list_id);
587 }
588
589 if let Some(linked_order_ids) = self.linked_order_ids() {
590 report = report.with_linked_order_ids(linked_order_ids.iter().copied());
591 }
592
593 if let Some(parent_order_id) = self.parent_order_id() {
594 report = report.with_parent_order_id(parent_order_id);
595 }
596
597 if let Some(position_id) = self.position_id() {
598 report = report.with_venue_position_id(position_id);
599 }
600
601 let mut ts_triggered = None;
604 let mut rejected_reason = None;
605
606 for event in self.events() {
607 match event {
608 OrderEventAny::Triggered(triggered) => ts_triggered = Some(triggered.ts_event),
609 OrderEventAny::Rejected(rejected) => rejected_reason = Some(rejected.reason),
610 _ => {}
611 }
612 }
613
614 if let Some(ts_triggered) = ts_triggered {
615 report = report.with_ts_triggered(ts_triggered);
616 }
617
618 if let Some(reason) = rejected_reason {
621 report = report.with_cancel_reason(reason.to_string());
622 }
623
624 if let Some(avg_px) = self.avg_px()
626 && let Ok(updated) = report.clone().with_avg_px(avg_px)
627 {
628 report = updated;
629 }
630
631 Some(report)
632 }
633
634 fn is_triggered(&self) -> Option<bool>; fn set_position_id(&mut self, position_id: Option<PositionId>);
636 fn set_quantity(&mut self, quantity: Quantity);
637 fn set_leaves_qty(&mut self, leaves_qty: Quantity);
638 fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>);
639 fn set_is_quote_quantity(&mut self, is_quote_quantity: bool);
640 fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide);
641 fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool;
642 fn previous_status(&self) -> Option<OrderStatus>;
643}
644
645impl<T> From<&T> for OrderInitialized
646where
647 T: Order,
648{
649 fn from(order: &T) -> Self {
650 Self {
651 trader_id: order.trader_id(),
652 strategy_id: order.strategy_id(),
653 instrument_id: order.instrument_id(),
654 client_order_id: order.client_order_id(),
655 order_side: order.order_side(),
656 order_type: order.order_type(),
657 quantity: order.quantity(),
658 price: order.price(),
659 trigger_price: order.trigger_price(),
660 trigger_type: order.trigger_type(),
661 time_in_force: order.time_in_force(),
662 expire_time: order.expire_time(),
663 post_only: order.is_post_only(),
664 reduce_only: order.is_reduce_only(),
665 quote_quantity: order.is_quote_quantity(),
666 display_qty: order.display_qty(),
667 limit_offset: order.limit_offset(),
668 trailing_offset: order.trailing_offset(),
669 trailing_offset_type: order.trailing_offset_type(),
670 emulation_trigger: order.emulation_trigger(),
671 trigger_instrument_id: order.trigger_instrument_id(),
672 contingency_type: order.contingency_type(),
673 order_list_id: order.order_list_id(),
674 linked_order_ids: order.linked_order_ids().map(|x| x.to_vec()),
675 parent_order_id: order.parent_order_id(),
676 exec_algorithm_id: order.exec_algorithm_id(),
677 exec_algorithm_params: order.exec_algorithm_params().map(|x| x.to_owned()),
678 exec_spawn_id: order.exec_spawn_id(),
679 tags: order.tags().map(|x| x.to_vec()),
680 event_id: order.init_id(),
681 ts_event: order.ts_init(),
682 ts_init: order.ts_init(),
683 reconciliation: false,
684 causation_id: None,
685 }
686 }
687}
688
689#[derive(Clone, Debug, Serialize, Deserialize)]
690pub struct OrderCore {
691 pub events: Vec<OrderEventAny>,
692 pub commissions: IndexMap<Currency, Money>,
693 pub venue_order_ids: Vec<VenueOrderId>,
694 pub trade_ids: Vec<TradeId>,
695 pub previous_status: Option<OrderStatus>,
696 pub status: OrderStatus,
697 pub trader_id: TraderId,
698 pub strategy_id: StrategyId,
699 pub instrument_id: InstrumentId,
700 pub client_order_id: ClientOrderId,
701 pub venue_order_id: Option<VenueOrderId>,
702 pub position_id: Option<PositionId>,
703 pub account_id: Option<AccountId>,
704 pub last_trade_id: Option<TradeId>,
705 pub side: OrderSide,
706 pub order_type: OrderType,
707 pub quantity: Quantity,
708 pub time_in_force: TimeInForce,
709 pub liquidity_side: Option<LiquiditySide>,
710 pub is_reduce_only: bool,
711 pub is_quote_quantity: bool,
712 pub emulation_trigger: Option<TriggerType>,
713 pub contingency_type: Option<ContingencyType>,
714 pub order_list_id: Option<OrderListId>,
715 pub linked_order_ids: Option<Vec<ClientOrderId>>,
716 pub parent_order_id: Option<ClientOrderId>,
717 pub exec_algorithm_id: Option<ExecAlgorithmId>,
718 pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
719 pub exec_spawn_id: Option<ClientOrderId>,
720 pub tags: Option<Vec<Ustr>>,
721 pub filled_qty: Quantity,
722 pub leaves_qty: Quantity,
723 pub overfill_qty: Quantity,
724 pub avg_px: Option<f64>,
725 pub slippage: Option<f64>,
726 pub init_id: UUID4,
727 pub ts_init: UnixNanos,
728 pub ts_submitted: Option<UnixNanos>,
729 pub ts_accepted: Option<UnixNanos>,
730 pub ts_closed: Option<UnixNanos>,
731 pub ts_last: UnixNanos,
732}
733
734impl OrderCore {
735 #[must_use]
737 pub fn new(init: OrderInitialized) -> Self {
738 let events: Vec<OrderEventAny> = vec![OrderEventAny::Initialized(init.clone())];
739 Self {
740 events,
741 commissions: IndexMap::new(),
742 venue_order_ids: Vec::new(),
743 trade_ids: Vec::new(),
744 previous_status: None,
745 status: OrderStatus::Initialized,
746 trader_id: init.trader_id,
747 strategy_id: init.strategy_id,
748 instrument_id: init.instrument_id,
749 client_order_id: init.client_order_id,
750 venue_order_id: None,
751 position_id: None,
752 account_id: None,
753 last_trade_id: None,
754 side: init.order_side,
755 order_type: init.order_type,
756 quantity: init.quantity,
757 time_in_force: init.time_in_force,
758 liquidity_side: Some(LiquiditySide::NoLiquiditySide),
759 is_reduce_only: init.reduce_only,
760 is_quote_quantity: init.quote_quantity,
761 emulation_trigger: init.emulation_trigger.or(Some(TriggerType::NoTrigger)),
762 contingency_type: init
763 .contingency_type
764 .or(Some(ContingencyType::NoContingency)),
765 order_list_id: init.order_list_id,
766 linked_order_ids: init.linked_order_ids,
767 parent_order_id: init.parent_order_id,
768 exec_algorithm_id: init.exec_algorithm_id,
769 exec_algorithm_params: init.exec_algorithm_params,
770 exec_spawn_id: init.exec_spawn_id,
771 tags: init.tags,
772 filled_qty: Quantity::zero(init.quantity.precision),
773 leaves_qty: init.quantity,
774 overfill_qty: Quantity::zero(init.quantity.precision),
775 avg_px: None,
776 slippage: None,
777 init_id: init.event_id,
778 ts_init: init.ts_event,
779 ts_submitted: None,
780 ts_accepted: None,
781 ts_closed: None,
782 ts_last: init.ts_event,
783 }
784 }
785
786 pub fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
793 if self.client_order_id != event.client_order_id() {
794 return Err(CorrectnessError::PredicateViolation {
795 message: format!(
796 "Event client_order_id {} does not match order client_order_id {}",
797 event.client_order_id(),
798 self.client_order_id
799 ),
800 }
801 .into());
802 }
803
804 if self.strategy_id != event.strategy_id() {
805 return Err(CorrectnessError::PredicateViolation {
806 message: format!(
807 "Event strategy_id {} does not match order strategy_id {}",
808 event.strategy_id(),
809 self.strategy_id
810 ),
811 }
812 .into());
813 }
814
815 if !matches!(
820 event,
821 OrderEventAny::Initialized(_)
822 | OrderEventAny::ModifyRejected(_)
823 | OrderEventAny::CancelRejected(_)
824 ) && !matches!(
825 self.status,
826 OrderStatus::PendingUpdate | OrderStatus::PendingCancel
827 ) {
828 self.previous_status = Some(self.status);
829 }
830
831 if let OrderEventAny::Filled(fill) = &event
833 && self.trade_ids.contains(&fill.trade_id)
834 {
835 return Err(OrderError::DuplicateFill(fill.trade_id));
836 }
837
838 if matches!(event, OrderEventAny::Triggered(_))
839 && !TRIGGERABLE_ORDER_TYPES.contains(&self.order_type)
840 {
841 return Err(OrderError::InvalidOrderEvent);
842 }
843
844 let new_status = self.status.transition(&event)?;
845 self.status = new_status;
846
847 match &event {
848 OrderEventAny::Initialized(_) => return Err(OrderError::AlreadyInitialized),
849 OrderEventAny::Denied(event) => self.denied(event),
850 OrderEventAny::Emulated(event) => self.emulated(event),
851 OrderEventAny::Released(event) => self.released(event),
852 OrderEventAny::Submitted(event) => self.submitted(event),
853 OrderEventAny::Rejected(event) => self.rejected(event),
854 OrderEventAny::Accepted(event) => self.accepted(event),
855 OrderEventAny::PendingUpdate(event) => self.pending_update(event),
856 OrderEventAny::PendingCancel(event) => self.pending_cancel(event),
857 OrderEventAny::ModifyRejected(event) => self.modify_rejected(event)?,
858 OrderEventAny::CancelRejected(event) => self.cancel_rejected(event)?,
859 OrderEventAny::Updated(event) => self.updated(event),
860 OrderEventAny::Triggered(event) => self.triggered(event),
861 OrderEventAny::Canceled(event) => self.canceled(event),
862 OrderEventAny::Expired(event) => self.expired(event),
863 OrderEventAny::Filled(event) => self.filled(event),
864 }
865
866 self.ts_last = event.ts_event();
867 self.events.push(event);
868 Ok(())
869 }
870
871 fn denied(&mut self, event: &OrderDenied) {
872 self.ts_closed = Some(event.ts_event);
873 }
874
875 fn emulated(&self, _event: &OrderEmulated) {
876 }
878
879 fn released(&mut self, _event: &OrderReleased) {
880 self.emulation_trigger = None;
881 }
882
883 fn submitted(&mut self, event: &OrderSubmitted) {
884 self.account_id = Some(event.account_id);
885 self.ts_submitted = Some(event.ts_event);
886 }
887
888 fn accepted(&mut self, event: &OrderAccepted) {
889 self.account_id = Some(event.account_id);
890 self.venue_order_id = Some(event.venue_order_id);
891 self.venue_order_ids.push(event.venue_order_id);
892 self.ts_accepted = Some(event.ts_event);
893 }
894
895 fn rejected(&mut self, event: &OrderRejected) {
896 self.ts_closed = Some(event.ts_event);
897 }
898
899 fn pending_update(&self, _event: &OrderPendingUpdate) {
900 }
902
903 fn pending_cancel(&self, _event: &OrderPendingCancel) {
904 }
906
907 fn modify_rejected(&mut self, _event: &OrderModifyRejected) -> Result<(), OrderError> {
908 self.status = self.previous_status.ok_or(OrderError::NoPreviousState)?;
909 Ok(())
910 }
911
912 fn cancel_rejected(&mut self, _event: &OrderCancelRejected) -> Result<(), OrderError> {
913 self.status = self.previous_status.ok_or(OrderError::NoPreviousState)?;
914 Ok(())
915 }
916
917 fn triggered(&self, _event: &OrderTriggered) {}
918
919 fn canceled(&mut self, event: &OrderCanceled) {
920 self.ts_closed = Some(event.ts_event);
921 }
922
923 fn expired(&mut self, event: &OrderExpired) {
924 self.ts_closed = Some(event.ts_event);
925 }
926
927 fn updated(&mut self, event: &OrderUpdated) {
928 if self.status == OrderStatus::PendingUpdate
929 && let Some(previous) = self.previous_status
930 {
931 self.status = previous;
932 }
933
934 if let Some(venue_order_id) = &event.venue_order_id
935 && (self.venue_order_id.is_none()
936 || venue_order_id != self.venue_order_id.as_ref().unwrap())
937 {
938 self.venue_order_id = Some(*venue_order_id);
939 self.venue_order_ids.push(*venue_order_id);
940 }
941
942 self.is_quote_quantity = event.is_quote_quantity;
943 }
944
945 fn filled(&mut self, event: &OrderFilled) {
946 let new_filled_qty = Quantity::from_raw(
948 self.filled_qty.raw.saturating_add(event.last_qty.raw),
949 self.filled_qty.precision,
950 );
951
952 if new_filled_qty > self.quantity {
954 let overfill_raw = new_filled_qty.raw - self.quantity.raw;
955 self.overfill_qty = Quantity::from_raw(
956 self.overfill_qty.raw.saturating_add(overfill_raw),
957 self.filled_qty.precision,
958 );
959 }
960
961 if new_filled_qty < self.quantity {
962 self.status = OrderStatus::PartiallyFilled;
963 } else {
964 self.status = OrderStatus::Filled;
965 self.ts_closed = Some(event.ts_event);
966 }
967
968 self.venue_order_id = Some(event.venue_order_id);
969 self.position_id = event.position_id;
970 self.trade_ids.push(event.trade_id);
971 self.last_trade_id = Some(event.trade_id);
972 self.liquidity_side = Some(event.liquidity_side);
973 self.filled_qty = new_filled_qty;
974 self.leaves_qty = self.leaves_qty.saturating_sub(event.last_qty);
975 self.ts_last = event.ts_event;
976
977 if let Some(commission) = event.commission {
978 let commission_currency = commission.currency;
979 if let Some(existing_commission) = self.commissions.get_mut(&commission_currency) {
980 *existing_commission = *existing_commission + commission;
981 } else {
982 self.commissions.insert(commission_currency, commission);
983 }
984 }
985
986 if self.ts_accepted.is_none() {
987 self.ts_accepted = Some(event.ts_event);
989 }
990
991 self.set_avg_px(event.last_qty, event.last_px);
992
993 debug_assert!(
994 matches!(
995 self.status,
996 OrderStatus::PartiallyFilled | OrderStatus::Filled
997 ),
998 "Invariant: status must be PartiallyFilled or Filled after fill handler (status={:?})",
999 self.status
1000 );
1001 debug_assert!(
1002 self.venue_order_id.is_some()
1003 && self.last_trade_id.is_some()
1004 && !self.trade_ids.is_empty(),
1005 "Invariant: venue_order_id, last_trade_id and trade_ids must be set after fill"
1006 );
1007 debug_assert!(
1008 self.filled_qty.raw.saturating_add(self.leaves_qty.raw) >= self.quantity.raw,
1009 "Invariant: filled_qty + leaves_qty >= quantity (filled={}, leaves={}, quantity={})",
1010 self.filled_qty,
1011 self.leaves_qty,
1012 self.quantity
1013 );
1014 }
1015
1016 fn set_avg_px(&mut self, last_qty: Quantity, last_px: Price) {
1017 if self.avg_px.is_none() {
1018 self.avg_px = Some(last_px.as_f64());
1019 return;
1020 }
1021
1022 let prev_filled_qty = (self.filled_qty - last_qty).as_f64();
1024 let last_qty_f64 = last_qty.as_f64();
1025 let total_qty = prev_filled_qty + last_qty_f64;
1026
1027 debug_assert!(
1028 total_qty > 0.0,
1029 "Invariant: avg_px calc requires positive total_qty (prev={prev_filled_qty}, last={last_qty_f64})"
1030 );
1031
1032 let avg_px = self
1033 .avg_px
1034 .unwrap()
1035 .mul_add(prev_filled_qty, last_px.as_f64() * last_qty_f64)
1036 / total_qty;
1037 self.avg_px = Some(avg_px);
1038 }
1039
1040 pub fn set_slippage(&mut self, price: Price) {
1041 self.slippage = self.avg_px.and_then(|avg_px| {
1042 let current_price = price.as_f64();
1043 match self.side {
1044 OrderSide::Buy if avg_px > current_price => Some(avg_px - current_price),
1045 OrderSide::Sell if avg_px < current_price => Some(current_price - avg_px),
1046 _ => None,
1047 }
1048 });
1049 }
1050
1051 #[must_use]
1053 pub fn opposite_side(side: OrderSide) -> OrderSide {
1054 match side {
1055 OrderSide::Buy => OrderSide::Sell,
1056 OrderSide::Sell => OrderSide::Buy,
1057 OrderSide::NoOrderSide => OrderSide::NoOrderSide,
1058 }
1059 }
1060
1061 #[must_use]
1063 pub fn closing_side(side: PositionSide) -> OrderSide {
1064 match side {
1065 PositionSide::Long => OrderSide::Sell,
1066 PositionSide::Short => OrderSide::Buy,
1067 PositionSide::Flat => OrderSide::NoOrderSide,
1068 PositionSide::NoPositionSide => OrderSide::NoOrderSide,
1069 }
1070 }
1071
1072 #[must_use]
1076 pub fn signed_decimal_qty(&self) -> Decimal {
1077 match self.side {
1078 OrderSide::Buy => self.quantity.as_decimal(),
1079 OrderSide::Sell => -self.quantity.as_decimal(),
1080 OrderSide::NoOrderSide => panic!("Invalid order side"),
1081 }
1082 }
1083
1084 #[must_use]
1085 pub fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
1086 if side == PositionSide::Flat {
1087 return false;
1088 }
1089
1090 match (self.side, side) {
1091 (OrderSide::Buy, PositionSide::Long) => false,
1092 (OrderSide::Buy, PositionSide::Short) => self.leaves_qty <= position_qty,
1093 (OrderSide::Sell, PositionSide::Short) => false,
1094 (OrderSide::Sell, PositionSide::Long) => self.leaves_qty <= position_qty,
1095 _ => true,
1096 }
1097 }
1098
1099 #[must_use]
1100 pub fn commission(&self, currency: &Currency) -> Option<Money> {
1101 self.commissions.get(currency).copied()
1102 }
1103
1104 #[must_use]
1105 pub fn commissions(&self) -> IndexMap<Currency, Money> {
1106 self.commissions.clone()
1107 }
1108
1109 #[must_use]
1110 pub fn commissions_vec(&self) -> Vec<Money> {
1111 self.commissions.values().copied().collect()
1112 }
1113
1114 #[must_use]
1115 pub fn init_event(&self) -> Option<OrderEventAny> {
1116 self.events.first().cloned()
1117 }
1118}
1119
1120#[cfg(test)]
1121mod tests {
1122 use rstest::rstest;
1123 use rust_decimal_macros::dec;
1124
1125 use super::*;
1126 use crate::{
1127 enums::{LiquiditySide, OrderSide, OrderStatus, PositionSide, TriggerType},
1128 events::order::spec::{
1129 OrderAcceptedSpec, OrderCanceledSpec, OrderDeniedSpec, OrderFilledSpec,
1130 OrderInitializedSpec, OrderPendingUpdateSpec, OrderRejectedSpec, OrderSubmittedSpec,
1131 OrderTriggeredSpec, OrderUpdatedSpec,
1132 },
1133 identifiers::InstrumentId,
1134 instruments::{CurrencyPair, Instrument, InstrumentAny, stubs::audusd_sim},
1135 orders::{MarketOrder, builder::OrderTestBuilder, stubs::TestOrderStubs},
1136 types::{Price, Quantity},
1137 };
1138
1139 #[rstest]
1150 #[case(OrderSide::Buy, OrderSide::Sell)]
1151 #[case(OrderSide::Sell, OrderSide::Buy)]
1152 #[case(OrderSide::NoOrderSide, OrderSide::NoOrderSide)]
1153 fn test_order_opposite_side(#[case] order_side: OrderSide, #[case] expected_side: OrderSide) {
1154 let result = OrderCore::opposite_side(order_side);
1155 assert_eq!(result, expected_side);
1156 }
1157
1158 #[rstest]
1159 #[case(PositionSide::Long, OrderSide::Sell)]
1160 #[case(PositionSide::Short, OrderSide::Buy)]
1161 #[case(PositionSide::NoPositionSide, OrderSide::NoOrderSide)]
1162 fn test_closing_side(#[case] position_side: PositionSide, #[case] expected_side: OrderSide) {
1163 let result = OrderCore::closing_side(position_side);
1164 assert_eq!(result, expected_side);
1165 }
1166
1167 #[rstest]
1168 #[case(OrderSide::Buy, dec!(10_000))]
1169 #[case(OrderSide::Sell, dec!(-10_000))]
1170 fn test_signed_decimal_qty(#[case] order_side: OrderSide, #[case] expected: Decimal) {
1171 let order: MarketOrder = OrderInitializedSpec::builder()
1172 .order_side(order_side)
1173 .quantity(Quantity::from(10_000))
1174 .build()
1175 .try_into()
1176 .unwrap();
1177
1178 let result = order.signed_decimal_qty();
1179 assert_eq!(result, expected);
1180 }
1181
1182 #[rustfmt::skip]
1183 #[rstest]
1184 #[case(OrderSide::Buy, Quantity::from(100), PositionSide::Long, Quantity::from(50), false)]
1185 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(50), true)]
1186 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(100), true)]
1187 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1188 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1189 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(50), true)]
1190 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(100), true)]
1191 #[case(OrderSide::Sell, Quantity::from(100), PositionSide::Short, Quantity::from(50), false)]
1192 fn test_would_reduce_only(
1193 #[case] order_side: OrderSide,
1194 #[case] order_qty: Quantity,
1195 #[case] position_side: PositionSide,
1196 #[case] position_qty: Quantity,
1197 #[case] expected: bool,
1198 ) {
1199 let order: MarketOrder = OrderInitializedSpec::builder()
1200 .order_side(order_side)
1201 .quantity(order_qty)
1202 .build()
1203 .try_into()
1204 .unwrap();
1205
1206 assert_eq!(
1207 order.would_reduce_only(position_side, position_qty),
1208 expected
1209 );
1210 }
1211
1212 #[rstest]
1213 fn test_order_state_transition_denied() {
1214 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1215 let denied = OrderDeniedSpec::builder().build();
1216 let event = OrderEventAny::Denied(denied);
1217
1218 order.apply(event.clone()).unwrap();
1219
1220 assert_eq!(order.status, OrderStatus::Denied);
1221 assert!(order.is_closed());
1222 assert!(!order.is_open());
1223 assert_eq!(order.event_count(), 2);
1224 assert_eq!(order.last_event(), &event);
1225 }
1226
1227 #[rstest]
1228 fn test_order_life_cycle_to_filled() {
1229 let init = OrderInitializedSpec::builder().build();
1230 let submitted = OrderSubmittedSpec::builder().build();
1231 let accepted = OrderAcceptedSpec::builder().build();
1232 let filled = OrderFilledSpec::builder().build();
1233
1234 let mut order: MarketOrder = init.clone().try_into().unwrap();
1235 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1236 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1237 order.apply(OrderEventAny::Filled(filled)).unwrap();
1238
1239 assert_eq!(order.client_order_id, init.client_order_id);
1240 assert_eq!(order.status(), OrderStatus::Filled);
1241 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1242 assert_eq!(order.leaves_qty(), Quantity::from(0));
1243 assert_eq!(order.avg_px(), Some(1.0));
1244 assert!(!order.is_open());
1245 assert!(order.is_closed());
1246 assert_eq!(order.commission(&Currency::USD()), None);
1247 assert_eq!(order.commissions(), &IndexMap::new());
1248 }
1249
1250 #[rstest]
1251 fn test_order_life_cycle_fills_with_negative_prices() {
1252 let init = OrderInitializedSpec::builder()
1256 .quantity(Quantity::from(100_000))
1257 .build();
1258 let submitted = OrderSubmittedSpec::builder().build();
1259 let accepted = OrderAcceptedSpec::builder().build();
1260 let fill1 = OrderFilledSpec::builder()
1261 .last_qty(Quantity::from(50_000))
1262 .last_px(Price::from("-5.00000"))
1263 .trade_id(TradeId::from("TRADE-1"))
1264 .build();
1265 let fill2 = OrderFilledSpec::builder()
1266 .last_qty(Quantity::from(50_000))
1267 .last_px(Price::from("-7.00000"))
1268 .trade_id(TradeId::from("TRADE-2"))
1269 .build();
1270
1271 let mut order: MarketOrder = init.try_into().unwrap();
1272 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1273 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1274 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1275 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1276
1277 assert_eq!(order.status(), OrderStatus::Filled);
1278 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1279 assert_eq!(order.leaves_qty(), Quantity::from(0));
1280 assert_eq!(order.avg_px(), Some(-6.0));
1282 }
1283
1284 #[rstest]
1285 fn test_order_life_cycle_accumulates_fill_commissions() {
1286 let init = OrderInitializedSpec::builder()
1287 .quantity(Quantity::from(100_000))
1288 .build();
1289 let submitted = OrderSubmittedSpec::builder().build();
1290 let accepted = OrderAcceptedSpec::builder().build();
1291 let fill1 = OrderFilledSpec::builder()
1292 .last_qty(Quantity::from(50_000))
1293 .trade_id(TradeId::from("TRADE-1"))
1294 .commission(Money::from("1.25 USD"))
1295 .build();
1296 let fill2 = OrderFilledSpec::builder()
1297 .last_qty(Quantity::from(50_000))
1298 .trade_id(TradeId::from("TRADE-2"))
1299 .commission(Money::from("1.35 USD"))
1300 .build();
1301
1302 let mut order: MarketOrder = init.try_into().unwrap();
1303 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1304 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1305 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1306 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1307
1308 assert_eq!(order.status(), OrderStatus::Filled);
1309 assert_eq!(
1310 order.commission(&Currency::USD()),
1311 Some(Money::from("2.60 USD"))
1312 );
1313 assert_eq!(order.commissions_vec(), vec![Money::from("2.60 USD")]);
1314 }
1315
1316 #[rstest]
1317 fn test_order_state_transition_to_canceled() {
1318 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1319 let submitted = OrderSubmittedSpec::builder().build();
1320 let canceled = OrderCanceledSpec::builder().build();
1321
1322 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1323 order.apply(OrderEventAny::Canceled(canceled)).unwrap();
1324
1325 assert_eq!(order.status(), OrderStatus::Canceled);
1326 assert!(order.is_closed());
1327 assert!(!order.is_open());
1328 }
1329
1330 #[rstest]
1331 fn test_order_life_cycle_to_partially_filled() {
1332 let init = OrderInitializedSpec::builder().build();
1333 let submitted = OrderSubmittedSpec::builder().build();
1334 let accepted = OrderAcceptedSpec::builder().build();
1335 let filled = OrderFilledSpec::builder()
1336 .last_qty(Quantity::from(50_000))
1337 .build();
1338
1339 let mut order: MarketOrder = init.clone().try_into().unwrap();
1340 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1341 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1342 order.apply(OrderEventAny::Filled(filled)).unwrap();
1343
1344 assert_eq!(order.client_order_id, init.client_order_id);
1345 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1346 assert_eq!(order.filled_qty(), Quantity::from(50_000));
1347 assert_eq!(order.leaves_qty(), Quantity::from(50_000));
1348 assert!(order.is_open());
1349 assert!(!order.is_closed());
1350 }
1351
1352 #[rstest]
1353 fn test_order_commission_calculation() {
1354 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1355 order
1356 .commissions
1357 .insert(Currency::USD(), Money::new(10.0, Currency::USD()));
1358
1359 assert_eq!(
1360 order.commission(&Currency::USD()),
1361 Some(Money::new(10.0, Currency::USD()))
1362 );
1363 assert_eq!(
1364 order.commissions_vec(),
1365 vec![Money::new(10.0, Currency::USD())]
1366 );
1367 }
1368
1369 #[rstest]
1370 fn test_order_is_primary() {
1371 let order: MarketOrder = OrderInitializedSpec::builder()
1372 .exec_algorithm_id(ExecAlgorithmId::from("ALGO-001"))
1373 .exec_spawn_id(ClientOrderId::from("O-001"))
1374 .client_order_id(ClientOrderId::from("O-001"))
1375 .build()
1376 .try_into()
1377 .unwrap();
1378
1379 assert!(order.is_primary());
1380 assert!(!order.is_spawned());
1381 }
1382
1383 #[rstest]
1384 fn test_order_is_spawned() {
1385 let order: MarketOrder = OrderInitializedSpec::builder()
1386 .exec_algorithm_id(ExecAlgorithmId::from("ALGO-001"))
1387 .exec_spawn_id(ClientOrderId::from("O-002"))
1388 .client_order_id(ClientOrderId::from("O-001"))
1389 .build()
1390 .try_into()
1391 .unwrap();
1392
1393 assert!(!order.is_primary());
1394 assert!(order.is_spawned());
1395 }
1396
1397 #[rstest]
1398 fn test_order_is_contingency() {
1399 let order: MarketOrder = OrderInitializedSpec::builder()
1400 .contingency_type(ContingencyType::Oto)
1401 .build()
1402 .try_into()
1403 .unwrap();
1404
1405 assert!(order.is_contingency());
1406 assert!(order.is_parent_order());
1407 assert!(!order.is_child_order());
1408 }
1409
1410 #[rstest]
1411 fn test_order_is_child_order() {
1412 let order: MarketOrder = OrderInitializedSpec::builder()
1413 .parent_order_id(ClientOrderId::from("PARENT-001"))
1414 .build()
1415 .try_into()
1416 .unwrap();
1417
1418 assert!(order.is_child_order());
1419 assert!(!order.is_parent_order());
1420 }
1421
1422 #[rstest]
1423 fn test_to_own_book_order_timestamp_ordering() {
1424 use crate::orders::limit::LimitOrder;
1425
1426 let init = OrderInitializedSpec::builder()
1428 .price(Price::from("100.00"))
1429 .build();
1430 let submitted = OrderSubmittedSpec::builder()
1431 .ts_event(UnixNanos::from(1_000_000))
1432 .build();
1433 let accepted = OrderAcceptedSpec::builder()
1434 .ts_event(UnixNanos::from(2_000_000))
1435 .build();
1436
1437 let mut order: LimitOrder = init.try_into().unwrap();
1438 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1439 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1440
1441 let own_book_order = order.to_own_book_order();
1442
1443 assert_eq!(own_book_order.ts_submitted, UnixNanos::from(1_000_000));
1445 assert_eq!(own_book_order.ts_accepted, UnixNanos::from(2_000_000));
1446 assert_eq!(own_book_order.ts_last, UnixNanos::from(2_000_000));
1447 }
1448
1449 #[rstest]
1450 fn test_order_accepted_without_submitted_sets_account_id() {
1451 let init = OrderInitializedSpec::builder().build();
1453 let accepted = OrderAcceptedSpec::builder()
1454 .account_id(AccountId::from("EXTERNAL-001"))
1455 .build();
1456
1457 let mut order: MarketOrder = init.try_into().unwrap();
1458
1459 assert_eq!(order.account_id(), None);
1461
1462 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1464
1465 assert_eq!(order.account_id(), Some(AccountId::from("EXTERNAL-001")));
1467 assert_eq!(order.status(), OrderStatus::Accepted);
1468 }
1469
1470 #[rstest]
1471 fn test_order_accepted_after_submitted_preserves_account_id() {
1472 let init = OrderInitializedSpec::builder().build();
1474 let submitted = OrderSubmittedSpec::builder()
1475 .account_id(AccountId::from("SUBMITTED-001"))
1476 .build();
1477 let accepted = OrderAcceptedSpec::builder()
1478 .account_id(AccountId::from("ACCEPTED-001"))
1479 .build();
1480
1481 let mut order: MarketOrder = init.try_into().unwrap();
1482 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1483
1484 assert_eq!(order.account_id(), Some(AccountId::from("SUBMITTED-001")));
1486
1487 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1489
1490 assert_eq!(order.account_id(), Some(AccountId::from("ACCEPTED-001")));
1492 assert_eq!(order.status(), OrderStatus::Accepted);
1493 }
1494
1495 #[rstest]
1496 fn test_overfill_tracks_overfill_qty() {
1497 let init = OrderInitializedSpec::builder()
1499 .quantity(Quantity::from(100_000))
1500 .build();
1501 let submitted = OrderSubmittedSpec::builder().build();
1502 let accepted = OrderAcceptedSpec::builder().build();
1503 let overfill = OrderFilledSpec::builder()
1504 .last_qty(Quantity::from(110_000)) .build();
1506
1507 let mut order: MarketOrder = init.try_into().unwrap();
1508 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1509 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1510 order.apply(OrderEventAny::Filled(overfill)).unwrap();
1511
1512 assert_eq!(order.overfill_qty(), Quantity::from(10_000));
1514 assert_eq!(order.filled_qty(), Quantity::from(110_000));
1515 assert_eq!(order.leaves_qty(), Quantity::from(0));
1516 assert_eq!(order.status(), OrderStatus::Filled);
1517 }
1518
1519 #[rstest]
1520 fn test_partial_fill_then_overfill() {
1521 let init = OrderInitializedSpec::builder()
1523 .quantity(Quantity::from(100_000))
1524 .build();
1525 let submitted = OrderSubmittedSpec::builder().build();
1526 let accepted = OrderAcceptedSpec::builder().build();
1527 let fill1 = OrderFilledSpec::builder()
1528 .last_qty(Quantity::from(80_000))
1529 .trade_id(TradeId::from("TRADE-1"))
1530 .build();
1531 let fill2 = OrderFilledSpec::builder()
1532 .last_qty(Quantity::from(30_000)) .trade_id(TradeId::from("TRADE-2"))
1534 .build();
1535
1536 let mut order: MarketOrder = init.try_into().unwrap();
1537 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1538 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1539 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1540
1541 assert_eq!(order.overfill_qty(), Quantity::from(0));
1543 assert_eq!(order.filled_qty(), Quantity::from(80_000));
1544 assert_eq!(order.leaves_qty(), Quantity::from(20_000));
1545
1546 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1547
1548 assert_eq!(order.overfill_qty(), Quantity::from(10_000));
1550 assert_eq!(order.filled_qty(), Quantity::from(110_000));
1551 assert_eq!(order.leaves_qty(), Quantity::from(0));
1552 assert_eq!(order.status(), OrderStatus::Filled);
1553 }
1554
1555 #[rstest]
1556 fn test_exact_fill_no_overfill() {
1557 let init = OrderInitializedSpec::builder()
1559 .quantity(Quantity::from(100_000))
1560 .build();
1561 let submitted = OrderSubmittedSpec::builder().build();
1562 let accepted = OrderAcceptedSpec::builder().build();
1563 let filled = OrderFilledSpec::builder()
1564 .last_qty(Quantity::from(100_000)) .build();
1566
1567 let mut order: MarketOrder = init.try_into().unwrap();
1568 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1569 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1570 order.apply(OrderEventAny::Filled(filled)).unwrap();
1571
1572 assert_eq!(order.overfill_qty(), Quantity::from(0));
1574 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1575 assert_eq!(order.leaves_qty(), Quantity::from(0));
1576 }
1577
1578 #[rstest]
1579 fn test_partial_fill_then_overfill_with_fractional_quantities() {
1580 let init = OrderInitializedSpec::builder()
1584 .quantity(Quantity::from("2450.5"))
1585 .build();
1586 let submitted = OrderSubmittedSpec::builder().build();
1587 let accepted = OrderAcceptedSpec::builder().build();
1588 let fill1 = OrderFilledSpec::builder()
1589 .last_qty(Quantity::from("1202.5"))
1590 .trade_id(TradeId::from("TRADE-1"))
1591 .build();
1592 let fill2 = OrderFilledSpec::builder()
1593 .last_qty(Quantity::from("1285.5")) .trade_id(TradeId::from("TRADE-2"))
1595 .build();
1596
1597 let mut order: MarketOrder = init.try_into().unwrap();
1598 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1599 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1600 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1601
1602 assert_eq!(order.overfill_qty(), Quantity::from(0));
1604 assert_eq!(order.filled_qty(), Quantity::from("1202.5"));
1605 assert_eq!(order.leaves_qty(), Quantity::from("1248.0"));
1606 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1607
1608 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1609
1610 assert_eq!(order.overfill_qty(), Quantity::from("37.5"));
1612 assert_eq!(order.filled_qty(), Quantity::from("2488.0"));
1613 assert_eq!(order.leaves_qty(), Quantity::from(0));
1614 assert_eq!(order.status(), OrderStatus::Filled);
1615 }
1616
1617 #[rstest]
1618 fn test_calculate_overfill_returns_zero_when_no_overfill() {
1619 let order: MarketOrder = OrderInitializedSpec::builder()
1620 .quantity(Quantity::from(100_000))
1621 .build()
1622 .try_into()
1623 .unwrap();
1624
1625 let overfill = order.calculate_overfill(Quantity::from(50_000));
1627 assert_eq!(overfill, Quantity::from(0));
1628
1629 let overfill = order.calculate_overfill(Quantity::from(100_000));
1631 assert_eq!(overfill, Quantity::from(0));
1632 }
1633
1634 #[rstest]
1635 fn test_calculate_overfill_returns_overfill_amount() {
1636 let order: MarketOrder = OrderInitializedSpec::builder()
1637 .quantity(Quantity::from(100_000))
1638 .build()
1639 .try_into()
1640 .unwrap();
1641
1642 let overfill = order.calculate_overfill(Quantity::from(110_000));
1644 assert_eq!(overfill, Quantity::from(10_000));
1645 }
1646
1647 #[rstest]
1648 fn test_calculate_overfill_accounts_for_existing_fills() {
1649 let init = OrderInitializedSpec::builder()
1650 .quantity(Quantity::from(100_000))
1651 .build();
1652 let submitted = OrderSubmittedSpec::builder().build();
1653 let accepted = OrderAcceptedSpec::builder().build();
1654 let partial_fill = OrderFilledSpec::builder()
1655 .last_qty(Quantity::from(60_000))
1656 .build();
1657
1658 let mut order: MarketOrder = init.try_into().unwrap();
1659 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1660 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1661 order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1662
1663 let overfill = order.calculate_overfill(Quantity::from(50_000));
1666 assert_eq!(overfill, Quantity::from(10_000));
1667
1668 let overfill = order.calculate_overfill(Quantity::from(40_000));
1670 assert_eq!(overfill, Quantity::from(0));
1671 }
1672
1673 #[rstest]
1674 fn test_calculate_overfill_with_fractional_quantities() {
1675 let order: MarketOrder = OrderInitializedSpec::builder()
1676 .quantity(Quantity::from("2450.5"))
1677 .build()
1678 .try_into()
1679 .unwrap();
1680
1681 let overfill = order.calculate_overfill(Quantity::from("2488.0"));
1684 assert_eq!(overfill, Quantity::from("37.5"));
1685 }
1686
1687 #[rstest]
1688 fn test_calculate_overfill_zero_after_fractional_partial_fill() {
1689 let init = OrderInitializedSpec::builder()
1690 .quantity(Quantity::from("1.000"))
1691 .build();
1692 let submitted = OrderSubmittedSpec::builder().build();
1693 let accepted = OrderAcceptedSpec::builder().build();
1694 let partial_fill = OrderFilledSpec::builder()
1695 .last_qty(Quantity::from("0.072"))
1696 .build();
1697
1698 let mut order: MarketOrder = init.try_into().unwrap();
1699 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1700 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1701 order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1702
1703 let overfill = order.calculate_overfill(Quantity::from("0.072"));
1705 assert_eq!(overfill, Quantity::from("0.000"));
1706 }
1707
1708 #[rstest]
1709 fn test_duplicate_fill_rejected() {
1710 let init = OrderInitializedSpec::builder()
1711 .quantity(Quantity::from(100_000))
1712 .build();
1713 let submitted = OrderSubmittedSpec::builder().build();
1714 let accepted = OrderAcceptedSpec::builder().build();
1715 let fill1 = OrderFilledSpec::builder()
1716 .last_qty(Quantity::from(50_000))
1717 .trade_id(TradeId::from("TRADE-001"))
1718 .build();
1719 let fill2_duplicate = OrderFilledSpec::builder()
1720 .last_qty(Quantity::from(50_000))
1721 .trade_id(TradeId::from("TRADE-001")) .build();
1723
1724 let mut order: MarketOrder = init.try_into().unwrap();
1725 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1726 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1727 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1728
1729 assert_eq!(order.filled_qty(), Quantity::from(50_000));
1731 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1732
1733 let result = order.apply(OrderEventAny::Filled(fill2_duplicate));
1735 assert!(result.is_err());
1736 match result.unwrap_err() {
1737 OrderError::DuplicateFill(trade_id) => {
1738 assert_eq!(trade_id, TradeId::from("TRADE-001"));
1739 }
1740 e => panic!("Expected DuplicateFill error, was: {e:?}"),
1741 }
1742
1743 assert_eq!(order.filled_qty(), Quantity::from(50_000));
1745 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1746 }
1747
1748 #[rstest]
1749 fn test_check_display_qty_returns_typed_invariant_with_stable_display() {
1750 let error = check_display_qty(Some(Quantity::from(2)), Quantity::from(1)).unwrap_err();
1751
1752 match error {
1753 OrderError::Invariant(CorrectnessError::PredicateViolation { ref message }) => {
1754 assert_eq!(message, "`display_qty` may not exceed `quantity`");
1755 }
1756 other => panic!("Expected typed invariant error, was: {other:?}"),
1757 }
1758
1759 assert_eq!(error.to_string(), "`display_qty` may not exceed `quantity`");
1760 }
1761
1762 #[rstest]
1763 fn test_check_time_in_force_returns_typed_invariant_with_stable_display() {
1764 let error = check_time_in_force(TimeInForce::Gtd, None).unwrap_err();
1765
1766 match error {
1767 OrderError::Invariant(CorrectnessError::PredicateViolation { ref message }) => {
1768 assert_eq!(message, "`expire_time` is required for `GTD` order");
1769 }
1770 other => panic!("Expected typed invariant error, was: {other:?}"),
1771 }
1772
1773 assert_eq!(
1774 error.to_string(),
1775 "`expire_time` is required for `GTD` order"
1776 );
1777 }
1778
1779 #[rstest]
1780 fn test_different_trade_ids_allowed() {
1781 let init = OrderInitializedSpec::builder()
1782 .quantity(Quantity::from(100_000))
1783 .build();
1784 let submitted = OrderSubmittedSpec::builder().build();
1785 let accepted = OrderAcceptedSpec::builder().build();
1786 let fill1 = OrderFilledSpec::builder()
1787 .last_qty(Quantity::from(50_000))
1788 .trade_id(TradeId::from("TRADE-001"))
1789 .build();
1790 let fill2 = OrderFilledSpec::builder()
1791 .last_qty(Quantity::from(50_000))
1792 .trade_id(TradeId::from("TRADE-002")) .build();
1794
1795 let mut order: MarketOrder = init.try_into().unwrap();
1796 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1797 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1798 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1799 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1800
1801 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1803 assert_eq!(order.status(), OrderStatus::Filled);
1804 assert_eq!(order.trade_ids.len(), 2);
1805 }
1806
1807 #[rstest]
1808 fn test_pending_update_order_restores_status_on_updated() {
1809 let init = OrderInitializedSpec::builder()
1810 .quantity(Quantity::from(100_000))
1811 .build();
1812 let submitted = OrderSubmittedSpec::builder().build();
1813 let accepted = OrderAcceptedSpec::builder().build();
1814 let pending_update = OrderPendingUpdateSpec::builder().build();
1815 let updated = OrderUpdatedSpec::builder()
1816 .quantity(Quantity::from(50_000))
1817 .build();
1818
1819 let mut order: MarketOrder = init.try_into().unwrap();
1820 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1821 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1822
1823 assert_eq!(order.status(), OrderStatus::Accepted);
1824
1825 order
1826 .apply(OrderEventAny::PendingUpdate(pending_update))
1827 .unwrap();
1828 assert_eq!(order.status(), OrderStatus::PendingUpdate);
1829
1830 order.apply(OrderEventAny::Updated(updated)).unwrap();
1831
1832 assert_eq!(order.status(), OrderStatus::Accepted);
1833 assert_eq!(order.quantity(), Quantity::from(50_000));
1834 }
1835
1836 #[rstest]
1837 fn test_partially_filled_order_can_be_updated() {
1838 let init = OrderInitializedSpec::builder()
1841 .quantity(Quantity::from(100_000))
1842 .build();
1843 let submitted = OrderSubmittedSpec::builder().build();
1844 let accepted = OrderAcceptedSpec::builder().build();
1845 let partial_fill = OrderFilledSpec::builder()
1846 .last_qty(Quantity::from(40_000))
1847 .build();
1848 let updated = OrderUpdatedSpec::builder()
1849 .quantity(Quantity::from(80_000)) .build();
1851
1852 let mut order: MarketOrder = init.try_into().unwrap();
1853 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1854 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1855 order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1856
1857 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1858 assert_eq!(order.filled_qty(), Quantity::from(40_000));
1859
1860 order.apply(OrderEventAny::Updated(updated)).unwrap();
1861
1862 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1863 assert_eq!(order.quantity(), Quantity::from(80_000));
1864 assert_eq!(order.leaves_qty(), Quantity::from(40_000)); }
1866
1867 #[rstest]
1868 fn test_triggered_order_can_be_updated() {
1869 let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
1872 let submitted = OrderSubmittedSpec::builder().build();
1873 let accepted = OrderAcceptedSpec::builder().build();
1874 let triggered = OrderTriggeredSpec::builder().build();
1875 let updated = OrderUpdatedSpec::builder()
1876 .quantity(Quantity::from(80_000))
1877 .build();
1878
1879 let mut order = OrderTestBuilder::new(OrderType::StopLimit)
1880 .instrument_id(instrument_id)
1881 .quantity(Quantity::from(100_000))
1882 .price(Price::from("0.99500"))
1883 .trigger_price(Price::from("1.00000"))
1884 .trigger_type(TriggerType::LastPrice)
1885 .build();
1886 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1887 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1888 order.apply(OrderEventAny::Triggered(triggered)).unwrap();
1889
1890 assert_eq!(order.status(), OrderStatus::Triggered);
1891
1892 order.apply(OrderEventAny::Updated(updated)).unwrap();
1893
1894 assert_eq!(order.status(), OrderStatus::Triggered);
1895 assert_eq!(order.quantity(), Quantity::from(80_000));
1896 }
1897
1898 #[rstest]
1899 fn test_order_updated_with_is_quote_quantity_clears_flag() {
1900 let init = OrderInitializedSpec::builder()
1901 .quantity(Quantity::new(10.0, 6))
1902 .quote_quantity(true)
1903 .build();
1904 let submitted = OrderSubmittedSpec::builder().build();
1905 let accepted = OrderAcceptedSpec::builder().build();
1906 let updated = OrderUpdatedSpec::builder()
1907 .quantity(Quantity::new(47.393_365, 6))
1908 .is_quote_quantity(false)
1909 .build();
1910
1911 let mut order: MarketOrder = init.try_into().unwrap();
1912 assert!(order.is_quote_quantity());
1913
1914 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1915 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1916 order.apply(OrderEventAny::Updated(updated)).unwrap();
1917
1918 assert!(!order.is_quote_quantity());
1919 assert_eq!(order.quantity(), Quantity::new(47.393_365, 6));
1920 assert_eq!(order.leaves_qty(), Quantity::new(47.393_365, 6));
1921 }
1922
1923 #[rstest]
1924 fn test_order_updated_default_is_quote_quantity_clears_flag() {
1925 let init = OrderInitializedSpec::builder()
1926 .quantity(Quantity::new(10.0, 6))
1927 .quote_quantity(true)
1928 .build();
1929 let submitted = OrderSubmittedSpec::builder().build();
1930 let accepted = OrderAcceptedSpec::builder().build();
1931 let updated = OrderUpdatedSpec::builder()
1933 .quantity(Quantity::new(8.0, 6))
1934 .build();
1935
1936 let mut order: MarketOrder = init.try_into().unwrap();
1937 assert!(order.is_quote_quantity());
1938
1939 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1940 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1941 order.apply(OrderEventAny::Updated(updated)).unwrap();
1942
1943 assert!(!order.is_quote_quantity());
1944 assert_eq!(order.quantity(), Quantity::new(8.0, 6));
1945 }
1946
1947 #[rstest]
1948 fn test_canceled_then_partial_fill_then_canceled() {
1949 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1950 let submitted = OrderSubmittedSpec::builder().build();
1951 let accepted = OrderAcceptedSpec::builder().build();
1952 let canceled1 = OrderCanceledSpec::builder().build();
1953 let fill = OrderFilledSpec::builder()
1954 .last_qty(Quantity::from(50_000))
1955 .trade_id(TradeId::from("FILL-1"))
1956 .build();
1957 let canceled2 = OrderCanceledSpec::builder().build();
1958
1959 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1960 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1961 order.apply(OrderEventAny::Canceled(canceled1)).unwrap();
1962 assert_eq!(order.status(), OrderStatus::Canceled);
1963 assert!(order.is_closed());
1964
1965 order.apply(OrderEventAny::Filled(fill)).unwrap();
1967 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1968 assert_eq!(order.filled_qty(), Quantity::from(50_000));
1969 assert!(order.is_open());
1970
1971 order.apply(OrderEventAny::Canceled(canceled2)).unwrap();
1973 assert_eq!(order.status(), OrderStatus::Canceled);
1974 assert!(order.is_closed());
1975 }
1976
1977 #[rstest]
1978 fn test_apply_triggered_to_stop_market_order_returns_error() {
1979 let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
1980 let submitted = OrderSubmittedSpec::builder().build();
1981 let accepted = OrderAcceptedSpec::builder().build();
1982 let triggered = OrderTriggeredSpec::builder().build();
1983
1984 let mut order = OrderTestBuilder::new(OrderType::StopMarket)
1985 .instrument_id(instrument_id)
1986 .quantity(Quantity::from(1))
1987 .trigger_price(Price::from("1.00000"))
1988 .trigger_type(TriggerType::LastPrice)
1989 .build();
1990 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1991 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1992
1993 let result = order.apply(OrderEventAny::Triggered(triggered));
1994 assert!(result.is_err());
1995 assert_eq!(order.status(), OrderStatus::Accepted);
1996 }
1997
1998 #[rstest]
1999 fn test_apply_triggered_to_stop_limit_order_succeeds() {
2000 let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
2001 let submitted = OrderSubmittedSpec::builder().build();
2002 let accepted = OrderAcceptedSpec::builder().build();
2003 let triggered = OrderTriggeredSpec::builder().build();
2004
2005 let mut order = OrderTestBuilder::new(OrderType::StopLimit)
2006 .instrument_id(instrument_id)
2007 .quantity(Quantity::from(1))
2008 .price(Price::from("0.99500"))
2009 .trigger_price(Price::from("1.00000"))
2010 .trigger_type(TriggerType::LastPrice)
2011 .build();
2012 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2013 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2014 order.apply(OrderEventAny::Triggered(triggered)).unwrap();
2015
2016 assert_eq!(order.status(), OrderStatus::Triggered);
2017 }
2018
2019 #[rstest]
2020 fn test_to_order_status_report_for_accepted_limit_order() {
2021 let order = OrderTestBuilder::new(OrderType::Limit)
2022 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2023 .side(OrderSide::Buy)
2024 .quantity(Quantity::from(100_000))
2025 .price(Price::from("1.00000"))
2026 .build();
2027 let accepted = TestOrderStubs::make_accepted_order(&order);
2028
2029 let report = accepted.to_order_status_report(None).unwrap();
2030
2031 assert_eq!(report.account_id, AccountId::from("SIM-001"));
2032 assert_eq!(report.instrument_id, InstrumentId::from("AUDUSD.SIM"));
2033 assert_eq!(report.client_order_id, Some(accepted.client_order_id()));
2034 assert_eq!(report.venue_order_id, VenueOrderId::from("V-001"));
2035 assert_eq!(report.order_side, OrderSide::Buy);
2036 assert_eq!(report.order_type, OrderType::Limit);
2037 assert_eq!(report.time_in_force, accepted.time_in_force());
2038 assert_eq!(report.order_status, OrderStatus::Accepted);
2039 assert_eq!(report.quantity, Quantity::from(100_000));
2040 assert_eq!(report.filled_qty, Quantity::from(0));
2041 assert_eq!(report.price, Some(Price::from("1.00000")));
2042 assert_eq!(report.avg_px, None);
2043 assert_eq!(report.ts_accepted, accepted.ts_accepted().unwrap());
2044 assert_eq!(report.ts_last, accepted.ts_last());
2045 assert_eq!(report.ts_init, accepted.ts_init());
2046 }
2047
2048 #[rstest]
2049 fn test_to_order_status_report_for_filled_market_order(audusd_sim: CurrencyPair) {
2050 let audusd_sim = InstrumentAny::CurrencyPair(audusd_sim);
2051 let order = OrderTestBuilder::new(OrderType::Market)
2052 .instrument_id(audusd_sim.id())
2053 .side(OrderSide::Buy)
2054 .quantity(Quantity::from(100_000))
2055 .build();
2056 let filled = TestOrderStubs::make_filled_order(&order, &audusd_sim, LiquiditySide::Maker);
2057 let report_id = UUID4::new();
2058
2059 let report = filled.to_order_status_report(Some(report_id)).unwrap();
2060
2061 assert_eq!(report.report_id, report_id);
2062 assert_eq!(report.order_status, OrderStatus::Filled);
2063 assert_eq!(report.quantity, Quantity::from(100_000));
2064 assert_eq!(report.filled_qty, Quantity::from(100_000));
2065 assert_eq!(report.price, None);
2066 assert_eq!(report.avg_px, Some(dec!(1)));
2067 assert_eq!(report.venue_position_id, Some(PositionId::from("1")));
2068 assert_eq!(report.ts_accepted, filled.ts_accepted().unwrap());
2069 assert_eq!(report.ts_last, filled.ts_last());
2070 }
2071
2072 #[rstest]
2073 fn test_to_order_status_report_maps_optional_fields() {
2074 let order = OrderTestBuilder::new(OrderType::StopLimit)
2075 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2076 .side(OrderSide::Sell)
2077 .quantity(Quantity::from(100_000))
2078 .price(Price::from("0.99500"))
2079 .trigger_price(Price::from("1.00000"))
2080 .trigger_type(TriggerType::LastPrice)
2081 .time_in_force(TimeInForce::Gtd)
2082 .expire_time(UnixNanos::from(5_000_000_000))
2083 .display_qty(Quantity::from(10_000))
2084 .post_only(true)
2085 .reduce_only(true)
2086 .contingency_type(ContingencyType::Oto)
2087 .order_list_id(OrderListId::from("OL-001"))
2088 .linked_order_ids(vec![ClientOrderId::from("O-CHILD")])
2089 .parent_order_id(ClientOrderId::from("O-PARENT"))
2090 .build();
2091 let accepted = TestOrderStubs::make_accepted_order(&order);
2092
2093 let report = accepted.to_order_status_report(None).unwrap();
2094
2095 assert_eq!(report.price, Some(Price::from("0.99500")));
2096 assert_eq!(report.trigger_price, Some(Price::from("1.00000")));
2097 assert_eq!(report.trigger_type, Some(TriggerType::LastPrice));
2098 assert_eq!(report.expire_time, Some(UnixNanos::from(5_000_000_000)));
2099 assert_eq!(report.display_qty, Some(Quantity::from(10_000)));
2100 assert!(report.post_only);
2101 assert!(report.reduce_only);
2102 assert_eq!(report.contingency_type, ContingencyType::Oto);
2103 assert_eq!(report.order_list_id, Some(OrderListId::from("OL-001")));
2104 assert_eq!(
2105 report.linked_order_ids,
2106 Some(vec![ClientOrderId::from("O-CHILD")])
2107 );
2108 assert_eq!(
2109 report.parent_order_id,
2110 Some(ClientOrderId::from("O-PARENT"))
2111 );
2112 }
2113
2114 #[rstest]
2115 fn test_to_order_status_report_maps_ts_triggered() {
2116 let order = OrderTestBuilder::new(OrderType::StopLimit)
2117 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2118 .side(OrderSide::Buy)
2119 .quantity(Quantity::from(100_000))
2120 .price(Price::from("0.99500"))
2121 .trigger_price(Price::from("1.00000"))
2122 .trigger_type(TriggerType::LastPrice)
2123 .build();
2124 let mut order = TestOrderStubs::make_accepted_order(&order);
2125 let triggered = OrderTriggeredSpec::builder()
2126 .ts_event(UnixNanos::from(1_500_000_000))
2127 .build();
2128 order.apply(OrderEventAny::Triggered(triggered)).unwrap();
2129
2130 let report = order.to_order_status_report(None).unwrap();
2131
2132 assert_eq!(report.order_status, OrderStatus::Triggered);
2133 assert_eq!(report.ts_triggered, Some(UnixNanos::from(1_500_000_000)));
2134 }
2135
2136 #[rstest]
2137 fn test_to_order_status_report_maps_rejection_reason() {
2138 let order = OrderTestBuilder::new(OrderType::Limit)
2139 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2140 .side(OrderSide::Buy)
2141 .quantity(Quantity::from(100_000))
2142 .price(Price::from("1.00000"))
2143 .build();
2144 let mut order = TestOrderStubs::make_accepted_order(&order);
2145 let rejected = OrderRejectedSpec::builder()
2146 .reason(Ustr::from("INSUFFICIENT_MARGIN"))
2147 .build();
2148 order.apply(OrderEventAny::Rejected(rejected)).unwrap();
2149
2150 let report = order.to_order_status_report(None).unwrap();
2151
2152 assert_eq!(report.order_status, OrderStatus::Rejected);
2153 assert_eq!(
2154 report.cancel_reason,
2155 Some("INSUFFICIENT_MARGIN".to_string())
2156 );
2157 }
2158
2159 #[rstest]
2160 fn test_to_order_status_report_maps_distinct_timestamps() {
2161 let mut order = OrderTestBuilder::new(OrderType::Limit)
2162 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2163 .side(OrderSide::Buy)
2164 .quantity(Quantity::from(100_000))
2165 .price(Price::from("1.00000"))
2166 .ts_init(UnixNanos::from(1_000))
2167 .build();
2168 let submitted = OrderSubmittedSpec::builder()
2169 .ts_event(UnixNanos::from(2_000))
2170 .build();
2171 let accepted = OrderAcceptedSpec::builder()
2172 .ts_event(UnixNanos::from(3_000))
2173 .build();
2174 let filled = OrderFilledSpec::builder()
2175 .last_qty(Quantity::from(50_000))
2176 .ts_event(UnixNanos::from(4_000))
2177 .build();
2178 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2179 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2180 order.apply(OrderEventAny::Filled(filled)).unwrap();
2181
2182 let report = order.to_order_status_report(None).unwrap();
2183
2184 assert_eq!(report.order_status, OrderStatus::PartiallyFilled);
2185 assert_eq!(report.filled_qty, Quantity::from(50_000));
2186 assert_eq!(report.ts_accepted, UnixNanos::from(3_000));
2187 assert_eq!(report.ts_last, UnixNanos::from(4_000));
2188 assert_eq!(report.ts_init, UnixNanos::from(1_000));
2189 }
2190
2191 #[rstest]
2192 fn test_to_order_status_report_ts_accepted_falls_back_to_ts_last() {
2193 let mut order = OrderTestBuilder::new(OrderType::Limit)
2195 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2196 .side(OrderSide::Buy)
2197 .quantity(Quantity::from(100_000))
2198 .price(Price::from("1.00000"))
2199 .build();
2200 let submitted = OrderSubmittedSpec::builder()
2201 .ts_event(UnixNanos::from(2_000))
2202 .build();
2203 let updated = OrderUpdatedSpec::builder()
2204 .venue_order_id(VenueOrderId::from("V-001"))
2205 .ts_event(UnixNanos::from(3_000))
2206 .build();
2207 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2208 order.apply(OrderEventAny::Updated(updated)).unwrap();
2209
2210 let report = order.to_order_status_report(None).unwrap();
2211
2212 assert_eq!(report.order_status, OrderStatus::Submitted);
2213 assert_eq!(report.venue_order_id, VenueOrderId::from("V-001"));
2214 assert_eq!(report.ts_accepted, UnixNanos::from(3_000));
2215 assert_eq!(report.ts_last, UnixNanos::from(3_000));
2216 }
2217
2218 #[rstest]
2219 fn test_to_order_status_report_maps_trailing_offsets() {
2220 let order = OrderTestBuilder::new(OrderType::TrailingStopLimit)
2221 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2222 .side(OrderSide::Sell)
2223 .quantity(Quantity::from(100_000))
2224 .price(Price::from("0.99500"))
2225 .trigger_price(Price::from("1.00000"))
2226 .trigger_type(TriggerType::LastPrice)
2227 .limit_offset(dec!(0.0001))
2228 .trailing_offset(dec!(0.0002))
2229 .trailing_offset_type(TrailingOffsetType::Price)
2230 .build();
2231 let accepted = TestOrderStubs::make_accepted_order(&order);
2232
2233 let report = accepted.to_order_status_report(None).unwrap();
2234
2235 assert_eq!(report.limit_offset, Some(dec!(0.0001)));
2236 assert_eq!(report.trailing_offset, Some(dec!(0.0002)));
2237 assert_eq!(report.trailing_offset_type, TrailingOffsetType::Price);
2238 }
2239
2240 #[rstest]
2241 fn test_to_order_status_report_returns_none_before_venue_ack() {
2242 let order = OrderTestBuilder::new(OrderType::Limit)
2243 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2244 .side(OrderSide::Buy)
2245 .quantity(Quantity::from(100_000))
2246 .price(Price::from("1.00000"))
2247 .build();
2248
2249 assert!(order.to_order_status_report(None).is_none());
2250 }
2251
2252 #[rstest]
2253 fn test_to_order_status_report_returns_none_for_submitted_order() {
2254 let mut order = OrderTestBuilder::new(OrderType::Limit)
2256 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
2257 .side(OrderSide::Buy)
2258 .quantity(Quantity::from(100_000))
2259 .price(Price::from("1.00000"))
2260 .build();
2261 let submitted = OrderSubmittedSpec::builder().build();
2262 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2263
2264 assert!(order.account_id().is_some());
2265 assert!(order.to_order_status_report(None).is_none());
2266 }
2267}