1pub mod any;
31pub mod limit;
32pub mod limit_if_touched;
33pub mod list;
34pub mod market;
35pub mod market_if_touched;
36pub mod market_to_limit;
37pub mod stop_limit;
38pub mod stop_market;
39pub mod trailing_stop_limit;
40pub mod trailing_stop_market;
41
42#[cfg(any(test, feature = "stubs"))]
43pub mod builder;
44#[cfg(any(test, feature = "stubs"))]
45pub mod stubs;
46
47use ahash::AHashSet;
49use enum_dispatch::enum_dispatch;
50use indexmap::IndexMap;
51use nautilus_core::{
52 UUID4, UnixNanos,
53 correctness::{CorrectnessError, check_predicate_false},
54};
55use rust_decimal::Decimal;
56use serde::{Deserialize, Serialize};
57use ustr::Ustr;
58
59#[cfg(any(test, feature = "stubs"))]
60pub use crate::orders::builder::OrderTestBuilder;
61pub use crate::orders::{
62 any::{LimitOrderAny, OrderAny, OrderReplayError, PassiveOrderAny, StopOrderAny},
63 limit::LimitOrder,
64 limit_if_touched::LimitIfTouchedOrder,
65 list::{OrderList, OrderListValidationError},
66 market::MarketOrder,
67 market_if_touched::MarketIfTouchedOrder,
68 market_to_limit::MarketToLimitOrder,
69 stop_limit::StopLimitOrder,
70 stop_market::StopMarketOrder,
71 trailing_stop_limit::TrailingStopLimitOrder,
72 trailing_stop_market::TrailingStopMarketOrder,
73};
74use crate::{
75 enums::{
76 ContingencyType, LiquiditySide, OrderSide, OrderSideSpecified, OrderStatus, OrderType,
77 PositionSide, TimeInForce, TrailingOffsetType, TriggerType,
78 },
79 events::{
80 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated,
81 OrderEventAny, OrderExpired, OrderFillVoided, OrderFilled, OrderInitialized,
82 OrderModifyRejected, OrderPendingCancel, OrderPendingUpdate, OrderRejected, OrderReleased,
83 OrderSubmitted, OrderTriggered, OrderUpdated,
84 },
85 identifiers::{
86 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
87 StrategyId, Symbol, TradeId, TraderId, Venue, VenueOrderId,
88 },
89 orderbook::OwnBookOrder,
90 reports::OrderStatusReport,
91 types::{Currency, Money, Price, Quantity},
92};
93
94pub const STOP_ORDER_TYPES: &[OrderType] = &[
96 OrderType::StopMarket,
97 OrderType::StopLimit,
98 OrderType::MarketIfTouched,
99 OrderType::LimitIfTouched,
100];
101
102pub const LIMIT_ORDER_TYPES: &[OrderType] = &[
104 OrderType::Limit,
105 OrderType::StopLimit,
106 OrderType::LimitIfTouched,
107 OrderType::TrailingStopLimit,
108];
109
110pub const TRIGGERABLE_ORDER_TYPES: &[OrderType] = &[
115 OrderType::StopLimit,
116 OrderType::TrailingStopLimit,
117 OrderType::LimitIfTouched,
118];
119
120pub const LOCAL_ACTIVE_ORDER_STATUSES: &[OrderStatus] = &[
122 OrderStatus::Initialized,
123 OrderStatus::Emulated,
124 OrderStatus::Released,
125];
126
127#[derive(thiserror::Error, Debug)]
128pub enum OrderError {
129 #[error("Order not found: {0}")]
130 NotFound(ClientOrderId),
131 #[error("Order invariant failed: must have a side for this operation")]
132 NoOrderSide,
133 #[error("Invalid event for order type")]
134 InvalidOrderEvent,
135 #[error("Invalid order state transition")]
136 InvalidStateTransition,
137 #[error("Order was already initialized")]
138 AlreadyInitialized,
139 #[error("Order had no previous state")]
140 NoPreviousState,
141 #[error("Duplicate fill: trade_id {0} already applied to order")]
142 DuplicateFill(TradeId),
143 #[error("Duplicate fill void: trade_id {0} already has this cumulative correction")]
144 DuplicateFillVoid(TradeId),
145 #[error("Fill void for trade_id {0} is older than the applied correction")]
146 StaleFillVoid(TradeId),
147 #[error("Fill void quantity exceeds the original fill for trade_id {0}")]
148 OverVoid(TradeId),
149 #[error("Fill void commission is missing or conflicts with trade_id {0}")]
150 InvalidFillVoidCommission(TradeId),
151 #[error("{0}")]
152 Invariant(#[from] CorrectnessError),
153}
154
155#[must_use]
157pub fn ustr_indexmap_to_str(h: IndexMap<Ustr, Ustr>) -> IndexMap<String, String> {
158 h.into_iter()
159 .map(|(k, v)| (k.to_string(), v.to_string()))
160 .collect()
161}
162
163#[must_use]
165pub fn str_indexmap_to_ustr(h: IndexMap<String, String>) -> IndexMap<Ustr, Ustr> {
166 h.into_iter()
167 .map(|(k, v)| (Ustr::from(&k), Ustr::from(&v)))
168 .collect()
169}
170
171#[inline]
172pub(crate) fn check_display_qty(
173 display_qty: Option<Quantity>,
174 quantity: Quantity,
175) -> Result<(), OrderError> {
176 if let Some(q) = display_qty {
177 check_predicate_false(q > quantity, "`display_qty` may not exceed `quantity`")?;
178 }
179 Ok(())
180}
181
182#[inline]
183pub(crate) fn check_time_in_force(
184 time_in_force: TimeInForce,
185 expire_time: Option<UnixNanos>,
186) -> Result<(), OrderError> {
187 check_predicate_false(
188 time_in_force == TimeInForce::Gtd && expire_time.unwrap_or_default() == 0,
189 "`expire_time` is required for `GTD` order",
190 )?;
191 Ok(())
192}
193
194impl OrderStatus {
195 #[rustfmt::skip]
201 pub fn transition(&mut self, event: &OrderEventAny) -> Result<Self, OrderError> {
202 let new_state = match (self, event) {
203 (Self::Initialized, OrderEventAny::Denied(_)) => Self::Denied,
204 (Self::Initialized, OrderEventAny::Emulated(_)) => Self::Emulated, (Self::Initialized, OrderEventAny::Released(_)) => Self::Released, (Self::Initialized, OrderEventAny::Submitted(_)) => Self::Submitted,
207 (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,
222 (Self::Submitted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
223 (Self::Submitted, OrderEventAny::Rejected(_)) => Self::Rejected,
224 (Self::Submitted, OrderEventAny::Canceled(_)) => Self::Canceled, (Self::Submitted, OrderEventAny::Accepted(_)) => Self::Accepted,
226 (Self::Submitted, OrderEventAny::Updated(_)) => Self::Submitted,
227 (Self::Submitted, OrderEventAny::Filled(_)) => Self::Filled,
228 (Self::Accepted, OrderEventAny::Rejected(_)) => Self::Rejected, (Self::Accepted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
230 (Self::Accepted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
231 (Self::Accepted, OrderEventAny::Canceled(_)) => Self::Canceled,
232 (Self::Accepted, OrderEventAny::Triggered(_)) => Self::Triggered,
233 (Self::Accepted, OrderEventAny::Updated(_)) => Self::Accepted, (Self::Accepted, OrderEventAny::Expired(_)) => Self::Expired,
235 (Self::Accepted, OrderEventAny::Filled(_)) => Self::Filled,
236 (Self::Accepted, OrderEventAny::FillVoided(_)) => Self::Accepted,
237 (Self::Canceled, OrderEventAny::Filled(_)) => Self::Filled, (Self::Canceled, OrderEventAny::FillVoided(_)) => Self::Canceled,
239 (Self::Canceled, OrderEventAny::Updated(_)) => Self::Canceled,
240 (Self::PendingUpdate, OrderEventAny::Rejected(_)) => Self::Rejected,
241 (Self::PendingUpdate, OrderEventAny::Accepted(_)) => Self::Accepted,
242 (Self::PendingUpdate, OrderEventAny::Canceled(_)) => Self::Canceled,
243 (Self::PendingUpdate, OrderEventAny::Expired(_)) => Self::Expired,
244 (Self::PendingUpdate, OrderEventAny::Triggered(_)) => Self::Triggered,
245 (Self::PendingUpdate, OrderEventAny::Submitted(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
248 (Self::PendingUpdate, OrderEventAny::ModifyRejected(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::Updated(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::Filled(_)) => Self::Filled,
251 (Self::PendingUpdate, OrderEventAny::FillVoided(_)) => Self::PendingUpdate,
252 (Self::PendingCancel, OrderEventAny::Rejected(_)) => Self::Rejected,
253 (Self::PendingCancel, OrderEventAny::PendingCancel(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::CancelRejected(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::Canceled(_)) => Self::Canceled,
256 (Self::PendingCancel, OrderEventAny::Expired(_)) => Self::Expired,
257 (Self::PendingCancel, OrderEventAny::Accepted(_)) => Self::Accepted, (Self::PendingCancel, OrderEventAny::Updated(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::Filled(_)) => Self::Filled,
260 (Self::PendingCancel, OrderEventAny::FillVoided(_)) => Self::PendingCancel,
261 (Self::Triggered, OrderEventAny::Rejected(_)) => Self::Rejected,
262 (Self::Triggered, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
263 (Self::Triggered, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
264 (Self::Triggered, OrderEventAny::Canceled(_)) => Self::Canceled,
265 (Self::Triggered, OrderEventAny::Expired(_)) => Self::Expired,
266 (Self::Triggered, OrderEventAny::Filled(_)) => Self::Filled,
267 (Self::Triggered, OrderEventAny::Updated(_)) => Self::Triggered,
268 (Self::Triggered, OrderEventAny::FillVoided(_)) => 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 (Self::PartiallyFilled, OrderEventAny::FillVoided(_)) => Self::PartiallyFilled,
277 (Self::Filled, OrderEventAny::FillVoided(_)) => Self::Voided,
278 (Self::Filled, OrderEventAny::Updated(_)) => Self::Filled,
279 (Self::Expired, OrderEventAny::FillVoided(_)) => Self::Expired,
280 (Self::Expired, OrderEventAny::Updated(_)) => Self::Expired,
281 (Self::Voided, OrderEventAny::FillVoided(_)) => Self::Voided,
282 _ => return Err(OrderError::InvalidStateTransition),
283 };
284 Ok(new_state)
285 }
286}
287
288#[enum_dispatch]
289pub trait Order: 'static + Send {
290 fn into_any(self) -> OrderAny;
291 fn status(&self) -> OrderStatus;
292 fn trader_id(&self) -> TraderId;
293 fn strategy_id(&self) -> StrategyId;
294 fn instrument_id(&self) -> InstrumentId;
295 fn symbol(&self) -> Symbol;
296 fn venue(&self) -> Venue;
297 fn client_order_id(&self) -> ClientOrderId;
298 fn venue_order_id(&self) -> Option<VenueOrderId>;
299 fn position_id(&self) -> Option<PositionId>;
300 fn account_id(&self) -> Option<AccountId>;
301 fn last_trade_id(&self) -> Option<TradeId>;
302 fn order_side(&self) -> OrderSide;
303 fn order_type(&self) -> OrderType;
304 fn quantity(&self) -> Quantity;
305 fn time_in_force(&self) -> TimeInForce;
306 fn expire_time(&self) -> Option<UnixNanos>;
307 fn price(&self) -> Option<Price>;
308 fn activation_price(&self) -> Option<Price> {
309 None
310 }
311 fn trigger_price(&self) -> Option<Price>;
312 fn trigger_type(&self) -> Option<TriggerType>;
313 fn liquidity_side(&self) -> Option<LiquiditySide>;
314 fn is_post_only(&self) -> bool;
315 fn is_reduce_only(&self) -> bool;
316 fn is_quote_quantity(&self) -> bool;
317 fn display_qty(&self) -> Option<Quantity>;
318 fn limit_offset(&self) -> Option<Decimal>;
319 fn trailing_offset(&self) -> Option<Decimal>;
320 fn trailing_offset_type(&self) -> Option<TrailingOffsetType>;
321 fn emulation_trigger(&self) -> Option<TriggerType>;
322 fn trigger_instrument_id(&self) -> Option<InstrumentId>;
323 fn contingency_type(&self) -> Option<ContingencyType>;
324 fn order_list_id(&self) -> Option<OrderListId>;
325 fn linked_order_ids(&self) -> Option<&[ClientOrderId]>;
326 fn parent_order_id(&self) -> Option<ClientOrderId>;
327 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId>;
328 fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>>;
329 fn exec_spawn_id(&self) -> Option<ClientOrderId>;
330 fn tags(&self) -> Option<&[Ustr]>;
331 fn filled_qty(&self) -> Quantity;
332 fn voided_qty(&self) -> Quantity;
333 fn leaves_qty(&self) -> Quantity;
334 fn overfill_qty(&self) -> Quantity;
335
336 fn calculate_overfill(&self, fill_qty: Quantity) -> Quantity {
338 let potential_filled = self.filled_qty() + fill_qty;
339 let quantity = self.quantity();
340 if potential_filled > quantity {
341 potential_filled - quantity
342 } else {
343 Quantity::zero(fill_qty.precision)
344 }
345 }
346
347 fn avg_px(&self) -> Option<Decimal>;
348 fn slippage(&self) -> Option<Decimal>;
349 fn init_id(&self) -> UUID4;
350 fn ts_init(&self) -> UnixNanos;
351 fn ts_submitted(&self) -> Option<UnixNanos>;
352 fn ts_accepted(&self) -> Option<UnixNanos>;
353 fn ts_closed(&self) -> Option<UnixNanos>;
354 fn ts_last(&self) -> UnixNanos;
355
356 fn order_side_specified(&self) -> OrderSideSpecified {
357 self.order_side().as_specified()
358 }
359 fn commissions(&self) -> &IndexMap<Currency, Money>;
360
361 fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError>;
367
368 fn update(&mut self, event: &OrderUpdated);
369
370 fn events(&self) -> Vec<&OrderEventAny>;
371
372 fn last_event(&self) -> &OrderEventAny {
373 self.events()
374 .last()
375 .expect("Order invariant violated: no events")
376 }
377
378 fn event_count(&self) -> usize {
379 self.events().len()
380 }
381
382 fn venue_order_ids(&self) -> Vec<&VenueOrderId>;
383
384 fn trade_ids(&self) -> Vec<&TradeId>;
385
386 fn has_price(&self) -> bool;
387
388 fn is_duplicate_fill(&self, fill: &OrderFilled) -> bool {
390 self.events().iter().any(|event| {
391 if let OrderEventAny::Filled(existing) = event {
392 existing.trade_id == fill.trade_id
393 && existing.order_side == fill.order_side
394 && existing.last_qty == fill.last_qty
395 && existing.last_px == fill.last_px
396 } else {
397 false
398 }
399 })
400 }
401
402 fn is_buy(&self) -> bool {
403 self.order_side() == OrderSide::Buy
404 }
405
406 fn is_sell(&self) -> bool {
407 self.order_side() == OrderSide::Sell
408 }
409
410 fn is_passive(&self) -> bool {
411 self.order_type() != OrderType::Market
412 }
413
414 fn is_aggressive(&self) -> bool {
415 self.order_type() == OrderType::Market
416 }
417
418 fn is_emulated(&self) -> bool {
419 self.status() == OrderStatus::Emulated
420 }
421
422 fn is_active_local(&self) -> bool {
423 matches!(
424 self.status(),
425 OrderStatus::Initialized | OrderStatus::Emulated | OrderStatus::Released
426 )
427 }
428
429 fn is_primary(&self) -> bool {
430 self.exec_algorithm_id().is_some()
431 && self
432 .exec_spawn_id()
433 .is_some_and(|spawn_id| self.client_order_id() == spawn_id)
434 }
435
436 fn is_spawned(&self) -> bool {
437 self.exec_algorithm_id().is_some()
438 && self
439 .exec_spawn_id()
440 .is_some_and(|spawn_id| self.client_order_id() != spawn_id)
441 }
442
443 fn is_contingency(&self) -> bool {
444 self.contingency_type().is_some()
445 }
446
447 fn is_parent_order(&self) -> bool {
448 match self.contingency_type() {
449 Some(c) => c == ContingencyType::Oto,
450 None => false,
451 }
452 }
453
454 fn is_child_order(&self) -> bool {
455 self.parent_order_id().is_some()
456 }
457
458 fn is_open(&self) -> bool {
459 if let Some(emulation_trigger) = self.emulation_trigger()
460 && emulation_trigger != TriggerType::NoTrigger
461 {
462 return false;
463 }
464
465 matches!(
466 self.status(),
467 OrderStatus::Accepted
468 | OrderStatus::Triggered
469 | OrderStatus::PendingCancel
470 | OrderStatus::PendingUpdate
471 | OrderStatus::PartiallyFilled
472 )
473 }
474
475 fn is_canceled(&self) -> bool {
476 self.status() == OrderStatus::Canceled
477 }
478
479 fn is_closed(&self) -> bool {
480 matches!(
481 self.status(),
482 OrderStatus::Denied
483 | OrderStatus::Rejected
484 | OrderStatus::Canceled
485 | OrderStatus::Expired
486 | OrderStatus::Filled
487 | OrderStatus::Voided
488 )
489 }
490
491 fn is_inflight(&self) -> bool {
492 if let Some(emulation_trigger) = self.emulation_trigger()
493 && emulation_trigger != TriggerType::NoTrigger
494 {
495 return false;
496 }
497
498 matches!(
499 self.status(),
500 OrderStatus::Submitted | OrderStatus::PendingCancel | OrderStatus::PendingUpdate
501 )
502 }
503
504 fn is_pending_update(&self) -> bool {
505 self.status() == OrderStatus::PendingUpdate
506 }
507
508 fn is_pending_cancel(&self) -> bool {
509 self.status() == OrderStatus::PendingCancel
510 }
511
512 fn to_own_book_order(&self) -> OwnBookOrder {
513 OwnBookOrder::new(
514 self.trader_id(),
515 self.client_order_id(),
516 self.venue_order_id(),
517 self.order_side().as_specified(),
518 self.price().expect("`OwnBookOrder` must have a price"),
519 self.leaves_qty(),
520 self.order_type(),
521 self.time_in_force(),
522 self.status(),
523 self.ts_last(),
524 self.ts_accepted().unwrap_or_default(),
525 self.ts_submitted().unwrap_or_default(),
526 self.ts_init(),
527 )
528 }
529
530 fn to_order_status_report(&self, report_id: Option<UUID4>) -> Option<OrderStatusReport> {
536 let account_id = self.account_id()?;
537 let venue_order_id = self.venue_order_id()?;
538
539 let mut report = OrderStatusReport::new(
540 account_id,
541 self.instrument_id(),
542 Some(self.client_order_id()),
543 venue_order_id,
544 self.order_side(),
545 self.order_type(),
546 self.time_in_force(),
547 self.status(),
548 self.quantity(),
549 self.filled_qty(),
550 self.ts_accepted().unwrap_or_else(|| self.ts_last()),
551 self.ts_last(),
552 self.ts_init(),
553 report_id,
554 )
555 .with_post_only(self.is_post_only())
556 .with_reduce_only(self.is_reduce_only());
557
558 if let Some(price) = self.price() {
559 report = report.with_price(price);
560 }
561
562 if let Some(activation_price) = self.activation_price() {
563 report = report.with_activation_price(activation_price);
564 }
565
566 if let Some(trigger_price) = self.trigger_price() {
567 report = report.with_trigger_price(trigger_price);
568 }
569
570 if let Some(trigger_type) = self.trigger_type() {
571 report = report.with_trigger_type(trigger_type);
572 }
573
574 if let Some(limit_offset) = self.limit_offset() {
575 report = report.with_limit_offset(limit_offset);
576 }
577
578 if let Some(trailing_offset) = self.trailing_offset() {
579 report = report.with_trailing_offset(trailing_offset);
580 }
581
582 if let Some(trailing_offset_type) = self.trailing_offset_type() {
583 report = report.with_trailing_offset_type(trailing_offset_type);
584 }
585
586 if let Some(display_qty) = self.display_qty() {
587 report = report.with_display_qty(display_qty);
588 }
589
590 if let Some(expire_time) = self.expire_time() {
591 report = report.with_expire_time(expire_time);
592 }
593
594 if let Some(contingency_type) = self.contingency_type() {
595 report = report.with_contingency_type(contingency_type);
596 }
597
598 if let Some(order_list_id) = self.order_list_id() {
599 report = report.with_order_list_id(order_list_id);
600 }
601
602 if let Some(linked_order_ids) = self.linked_order_ids() {
603 report = report.with_linked_order_ids(linked_order_ids.iter().copied());
604 }
605
606 if let Some(parent_order_id) = self.parent_order_id() {
607 report = report.with_parent_order_id(parent_order_id);
608 }
609
610 if let Some(position_id) = self.position_id() {
611 report = report.with_venue_position_id(position_id);
612 }
613
614 let mut ts_triggered = None;
617 let mut rejected_reason = None;
618
619 for event in self.events() {
620 match event {
621 OrderEventAny::Triggered(triggered) => ts_triggered = Some(triggered.ts_event),
622 OrderEventAny::Rejected(rejected) => rejected_reason = Some(rejected.reason),
623 _ => {}
624 }
625 }
626
627 if let Some(ts_triggered) = ts_triggered {
628 report = report.with_ts_triggered(ts_triggered);
629 }
630
631 if let Some(reason) = rejected_reason {
634 report = report.with_cancel_reason(reason.to_string());
635 }
636
637 if let Some(avg_px) = self.avg_px() {
638 report = report.with_avg_px(avg_px);
639 }
640
641 Some(report)
642 }
643
644 fn is_triggered(&self) -> Option<bool>; fn set_position_id(&mut self, position_id: Option<PositionId>);
646 fn set_quantity(&mut self, quantity: Quantity);
647 fn set_leaves_qty(&mut self, leaves_qty: Quantity);
648 fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>);
649 fn set_is_quote_quantity(&mut self, is_quote_quantity: bool);
650 fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide);
651 fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool;
652 fn previous_status(&self) -> Option<OrderStatus>;
653}
654
655impl<T> From<&T> for OrderInitialized
656where
657 T: Order,
658{
659 fn from(order: &T) -> Self {
660 Self {
661 trader_id: order.trader_id(),
662 strategy_id: order.strategy_id(),
663 instrument_id: order.instrument_id(),
664 client_order_id: order.client_order_id(),
665 order_side: order.order_side(),
666 order_type: order.order_type(),
667 quantity: order.quantity(),
668 price: order.price(),
669 activation_price: order.activation_price(),
670 trigger_price: order.trigger_price(),
671 trigger_type: order.trigger_type(),
672 time_in_force: order.time_in_force(),
673 expire_time: order.expire_time(),
674 post_only: order.is_post_only(),
675 reduce_only: order.is_reduce_only(),
676 quote_quantity: order.is_quote_quantity(),
677 display_qty: order.display_qty(),
678 limit_offset: order.limit_offset(),
679 trailing_offset: order.trailing_offset(),
680 trailing_offset_type: order.trailing_offset_type(),
681 emulation_trigger: order.emulation_trigger(),
682 trigger_instrument_id: order.trigger_instrument_id(),
683 contingency_type: order.contingency_type(),
684 order_list_id: order.order_list_id(),
685 linked_order_ids: order.linked_order_ids().map(|x| x.to_vec()),
686 parent_order_id: order.parent_order_id(),
687 exec_algorithm_id: order.exec_algorithm_id(),
688 exec_algorithm_params: order.exec_algorithm_params().map(|x| x.to_owned()),
689 exec_spawn_id: order.exec_spawn_id(),
690 tags: order.tags().map(|x| x.to_vec()),
691 event_id: order.init_id(),
692 ts_event: order.ts_init(),
693 ts_init: order.ts_init(),
694 reconciliation: false,
695 causation_id: None,
696 }
697 }
698}
699
700#[derive(Clone, Debug, Serialize, Deserialize)]
701pub struct OrderCore {
702 pub events: Vec<OrderEventAny>,
703 pub commissions: IndexMap<Currency, Money>,
704 pub venue_order_ids: Vec<VenueOrderId>,
705 pub trade_ids: Vec<TradeId>,
706 pub previous_status: Option<OrderStatus>,
707 pub status: OrderStatus,
708 pub trader_id: TraderId,
709 pub strategy_id: StrategyId,
710 pub instrument_id: InstrumentId,
711 pub client_order_id: ClientOrderId,
712 pub venue_order_id: Option<VenueOrderId>,
713 pub position_id: Option<PositionId>,
714 pub account_id: Option<AccountId>,
715 pub last_trade_id: Option<TradeId>,
716 pub side: OrderSide,
717 pub order_type: OrderType,
718 pub quantity: Quantity,
719 pub time_in_force: TimeInForce,
720 pub liquidity_side: Option<LiquiditySide>,
721 pub is_reduce_only: bool,
722 pub is_quote_quantity: bool,
723 pub emulation_trigger: Option<TriggerType>,
724 pub contingency_type: Option<ContingencyType>,
725 pub order_list_id: Option<OrderListId>,
726 pub linked_order_ids: Option<Vec<ClientOrderId>>,
727 pub parent_order_id: Option<ClientOrderId>,
728 pub exec_algorithm_id: Option<ExecAlgorithmId>,
729 pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
730 pub exec_spawn_id: Option<ClientOrderId>,
731 pub tags: Option<Vec<Ustr>>,
732 pub filled_qty: Quantity,
733 #[serde(default)]
734 pub voided_qty: Quantity,
735 pub leaves_qty: Quantity,
736 pub overfill_qty: Quantity,
737 pub avg_px: Option<Decimal>,
738 pub slippage: Option<Decimal>,
739 pub init_id: UUID4,
740 pub ts_init: UnixNanos,
741 pub ts_submitted: Option<UnixNanos>,
742 pub ts_accepted: Option<UnixNanos>,
743 pub ts_closed: Option<UnixNanos>,
744 pub ts_last: UnixNanos,
745}
746
747impl OrderCore {
748 #[must_use]
750 pub fn new(init: OrderInitialized) -> Self {
751 let events: Vec<OrderEventAny> = vec![OrderEventAny::Initialized(init.clone())];
752 Self {
753 events,
754 commissions: IndexMap::new(),
755 venue_order_ids: Vec::new(),
756 trade_ids: Vec::new(),
757 previous_status: None,
758 status: OrderStatus::Initialized,
759 trader_id: init.trader_id,
760 strategy_id: init.strategy_id,
761 instrument_id: init.instrument_id,
762 client_order_id: init.client_order_id,
763 venue_order_id: None,
764 position_id: None,
765 account_id: None,
766 last_trade_id: None,
767 side: init.order_side,
768 order_type: init.order_type,
769 quantity: init.quantity,
770 time_in_force: init.time_in_force,
771 liquidity_side: Some(LiquiditySide::NoLiquiditySide),
772 is_reduce_only: init.reduce_only,
773 is_quote_quantity: init.quote_quantity,
774 emulation_trigger: init.emulation_trigger.or(Some(TriggerType::NoTrigger)),
775 contingency_type: init
776 .contingency_type
777 .or(Some(ContingencyType::NoContingency)),
778 order_list_id: init.order_list_id,
779 linked_order_ids: init.linked_order_ids,
780 parent_order_id: init.parent_order_id,
781 exec_algorithm_id: init.exec_algorithm_id,
782 exec_algorithm_params: init.exec_algorithm_params,
783 exec_spawn_id: init.exec_spawn_id,
784 tags: init.tags,
785 filled_qty: Quantity::zero(init.quantity.precision),
786 voided_qty: Quantity::zero(init.quantity.precision),
787 leaves_qty: init.quantity,
788 overfill_qty: Quantity::zero(init.quantity.precision),
789 avg_px: None,
790 slippage: None,
791 init_id: init.event_id,
792 ts_init: init.ts_event,
793 ts_submitted: None,
794 ts_accepted: None,
795 ts_closed: None,
796 ts_last: init.ts_event,
797 }
798 }
799
800 pub fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
807 if self.client_order_id != event.client_order_id() {
808 return Err(CorrectnessError::PredicateViolation {
809 message: format!(
810 "Event client_order_id {} does not match order client_order_id {}",
811 event.client_order_id(),
812 self.client_order_id
813 ),
814 }
815 .into());
816 }
817
818 if self.strategy_id != event.strategy_id() {
819 return Err(CorrectnessError::PredicateViolation {
820 message: format!(
821 "Event strategy_id {} does not match order strategy_id {}",
822 event.strategy_id(),
823 self.strategy_id
824 ),
825 }
826 .into());
827 }
828
829 if let OrderEventAny::FillVoided(event) = &event {
830 self.validate_fill_void(event)?;
831 }
832
833 if let OrderEventAny::Filled(fill) = &event
835 && self.events.iter().any(
836 |event| matches!(event, OrderEventAny::Filled(existing) if existing.trade_id == fill.trade_id),
837 )
838 {
839 return Err(OrderError::DuplicateFill(fill.trade_id));
840 }
841
842 if matches!(event, OrderEventAny::Triggered(_))
843 && !TRIGGERABLE_ORDER_TYPES.contains(&self.order_type)
844 {
845 return Err(OrderError::InvalidOrderEvent);
846 }
847
848 if matches!(event, OrderEventAny::Initialized(_)) {
849 return Err(OrderError::AlreadyInitialized);
850 }
851
852 let new_status = self.status.transition(&event)?;
853 let rejection_status = if matches!(
854 event,
855 OrderEventAny::ModifyRejected(_) | OrderEventAny::CancelRejected(_)
856 ) {
857 self.previous_status.ok_or(OrderError::NoPreviousState)?
858 } else {
859 self.status
860 };
861
862 if let OrderEventAny::Updated(update) = &event {
866 let invalid_fields = match self.order_type {
867 OrderType::Market => update.price.is_some() || update.trigger_price.is_some(),
868 OrderType::StopMarket
869 | OrderType::MarketIfTouched
870 | OrderType::TrailingStopMarket => update.price.is_some(),
871 OrderType::Limit | OrderType::MarketToLimit => update.trigger_price.is_some(),
872 OrderType::StopLimit | OrderType::LimitIfTouched | OrderType::TrailingStopLimit => {
873 false
874 }
875 };
876
877 if invalid_fields {
878 return Err(OrderError::InvalidOrderEvent);
879 }
880 }
881
882 let source_status = self.status;
883
884 if !matches!(
889 event,
890 OrderEventAny::Initialized(_)
891 | OrderEventAny::ModifyRejected(_)
892 | OrderEventAny::CancelRejected(_)
893 ) && !matches!(
894 self.status,
895 OrderStatus::PendingUpdate | OrderStatus::PendingCancel
896 ) {
897 self.previous_status = Some(self.status);
898 }
899
900 self.status = new_status;
901
902 match &event {
903 OrderEventAny::Initialized(_) => return Err(OrderError::AlreadyInitialized),
905 OrderEventAny::Denied(event) => self.denied(event),
906 OrderEventAny::Emulated(event) => self.emulated(event),
907 OrderEventAny::Released(event) => self.released(event),
908 OrderEventAny::Submitted(event) => self.submitted(event),
909 OrderEventAny::Rejected(event) => self.rejected(event),
910 OrderEventAny::Accepted(event) => self.accepted(event),
911 OrderEventAny::PendingUpdate(event) => self.pending_update(event),
912 OrderEventAny::PendingCancel(event) => self.pending_cancel(event),
913 OrderEventAny::ModifyRejected(event) => self.modify_rejected(event, rejection_status),
914 OrderEventAny::CancelRejected(event) => self.cancel_rejected(event, rejection_status),
915 OrderEventAny::Updated(event) => self.updated(event),
916 OrderEventAny::Triggered(event) => self.triggered(event),
917 OrderEventAny::Canceled(event) => self.canceled(event),
918 OrderEventAny::Expired(event) => self.expired(event),
919 OrderEventAny::Filled(event) => self.filled(event),
920 OrderEventAny::FillVoided(event) => self.fill_voided(event, source_status),
921 }
922
923 self.ts_last = event.ts_event();
924 self.events.push(event);
925 Ok(())
926 }
927
928 fn denied(&mut self, event: &OrderDenied) {
929 self.ts_closed = Some(event.ts_event);
930 }
931
932 fn emulated(&self, _event: &OrderEmulated) {
933 }
935
936 fn released(&mut self, _event: &OrderReleased) {
937 self.emulation_trigger = None;
938 }
939
940 fn submitted(&mut self, event: &OrderSubmitted) {
941 self.account_id = Some(event.account_id);
942 self.ts_submitted = Some(event.ts_event);
943 }
944
945 fn accepted(&mut self, event: &OrderAccepted) {
946 self.account_id = Some(event.account_id);
947 self.venue_order_id = Some(event.venue_order_id);
948 self.venue_order_ids.push(event.venue_order_id);
949 self.ts_accepted = Some(event.ts_event);
950 }
951
952 fn rejected(&mut self, event: &OrderRejected) {
953 self.ts_closed = Some(event.ts_event);
954 }
955
956 fn pending_update(&self, _event: &OrderPendingUpdate) {
957 }
959
960 fn pending_cancel(&self, _event: &OrderPendingCancel) {
961 }
963
964 fn modify_rejected(&mut self, _event: &OrderModifyRejected, previous_status: OrderStatus) {
965 self.status = previous_status;
966 }
967
968 fn cancel_rejected(&mut self, _event: &OrderCancelRejected, previous_status: OrderStatus) {
969 self.status = previous_status;
970 }
971
972 fn triggered(&self, _event: &OrderTriggered) {}
973
974 fn canceled(&mut self, event: &OrderCanceled) {
975 self.ts_closed = Some(event.ts_event);
976 }
977
978 fn expired(&mut self, event: &OrderExpired) {
979 self.ts_closed = Some(event.ts_event);
980 }
981
982 fn validate_fill_void(&self, event: &OrderFillVoided) -> Result<(), OrderError> {
983 let fill = self.events.iter().find_map(|candidate| match candidate {
984 OrderEventAny::Filled(fill) if fill.trade_id == event.trade_id => Some(fill),
985 _ => None,
986 });
987
988 if event.voided_qty.is_zero() {
989 return Err(OrderError::OverVoid(event.trade_id));
990 }
991
992 let previous = self.latest_fill_void(event.trade_id);
993 if let Some(previous) = previous {
994 if event.voided_qty < previous.voided_qty {
995 return Err(OrderError::StaleFillVoid(event.trade_id));
996 }
997
998 if event.voided_qty == previous.voided_qty
999 && event.commission_voided == previous.commission_voided
1000 && event.is_reopened == previous.is_reopened
1001 {
1002 return Err(OrderError::DuplicateFillVoid(event.trade_id));
1003 }
1004 }
1005
1006 let Some(fill) = fill else {
1007 if event.is_reopened {
1008 return Err(OrderError::InvalidOrderEvent);
1009 }
1010
1011 if !self.matches_fill_void_identity(event) {
1012 return Err(OrderError::InvalidOrderEvent);
1013 }
1014
1015 if event.voided_qty > self.quantity
1016 || event
1017 .commission_voided
1018 .is_some_and(|commission| !commission.is_zero())
1019 {
1020 return Err(OrderError::OverVoid(event.trade_id));
1021 }
1022 return Ok(());
1023 };
1024
1025 if fill.venue_order_id != event.venue_order_id
1026 || fill.account_id != event.account_id
1027 || fill.order_side != event.order_side
1028 || fill.order_type != event.order_type
1029 || fill.last_px != event.last_px
1030 || fill.currency != event.currency
1031 || fill.liquidity_side != event.liquidity_side
1032 || fill.position_id != event.position_id
1033 {
1034 return Err(OrderError::InvalidOrderEvent);
1035 }
1036
1037 if event.voided_qty > fill.last_qty {
1038 return Err(OrderError::OverVoid(event.trade_id));
1039 }
1040
1041 self.validate_fill_void_commission(fill, event, previous)
1042 }
1043
1044 fn matches_fill_void_identity(&self, event: &OrderFillVoided) -> bool {
1045 event.trader_id == self.trader_id
1046 && event.instrument_id == self.instrument_id
1047 && event.order_side == self.side
1048 && event.order_type == self.order_type
1049 && (self.venue_order_id == Some(event.venue_order_id)
1050 || self.venue_order_ids.contains(&event.venue_order_id))
1051 && self.account_id == Some(event.account_id)
1052 }
1053
1054 fn validate_fill_void_commission(
1055 &self,
1056 fill: &OrderFilled,
1057 event: &OrderFillVoided,
1058 previous: Option<&OrderFillVoided>,
1059 ) -> Result<(), OrderError> {
1060 match (fill.commission, event.commission_voided) {
1061 (None, None) => Ok(()),
1062 (None, Some(commission)) if commission.is_zero() => Ok(()),
1063 (Some(_), None) if previous.and_then(|prior| prior.commission_voided).is_none() => {
1064 Ok(())
1065 }
1066 (Some(commission), Some(voided))
1067 if commission.currency == voided.currency
1068 && commission.raw.signum() == voided.raw.signum()
1069 && voided.raw.abs() <= commission.raw.abs()
1070 && previous
1071 .and_then(|prior| prior.commission_voided)
1072 .is_none_or(|prior| voided.raw.abs() >= prior.raw.abs()) =>
1073 {
1074 Ok(())
1075 }
1076 _ => Err(OrderError::InvalidFillVoidCommission(event.trade_id)),
1077 }
1078 }
1079
1080 fn latest_fill_void(&self, trade_id: TradeId) -> Option<&OrderFillVoided> {
1081 self.events
1082 .iter()
1083 .rev()
1084 .find_map(|candidate| match candidate {
1085 OrderEventAny::FillVoided(event) if event.trade_id == trade_id => Some(event),
1086 _ => None,
1087 })
1088 }
1089
1090 fn fill_voided(&mut self, event: &OrderFillVoided, source_status: OrderStatus) {
1091 let has_local_fill = self.events.iter().any(
1092 |candidate| matches!(candidate, OrderEventAny::Filled(fill) if fill.trade_id == event.trade_id),
1093 );
1094 let non_reopened_voided_qty = self.recompute_fill_state(Some(event));
1095 let unfilled_qty = self.quantity.saturating_sub(self.filled_qty);
1096 let working_leaves = unfilled_qty.saturating_sub(non_reopened_voided_qty);
1097
1098 match source_status {
1099 OrderStatus::Voided => {
1100 self.status = OrderStatus::Voided;
1101 self.leaves_qty = Quantity::zero(self.quantity.precision);
1102 }
1103 _ if !has_local_fill && !event.is_reopened => {
1104 self.status = OrderStatus::Voided;
1105 self.leaves_qty = Quantity::zero(self.quantity.precision);
1106 }
1107 OrderStatus::Canceled | OrderStatus::Expired => {
1108 self.status = source_status;
1109 self.leaves_qty = unfilled_qty;
1110 }
1111 _ if working_leaves.is_zero() => {
1112 self.status = OrderStatus::Voided;
1113 self.leaves_qty = working_leaves;
1114 }
1115 OrderStatus::PendingUpdate | OrderStatus::PendingCancel | OrderStatus::Triggered => {
1116 self.status = source_status;
1117 self.leaves_qty = working_leaves;
1118 }
1119 _ => {
1120 self.status = if self.filled_qty.is_zero() {
1121 OrderStatus::Accepted
1122 } else {
1123 OrderStatus::PartiallyFilled
1124 };
1125 self.leaves_qty = working_leaves;
1126 }
1127 }
1128
1129 if self.status.is_open() {
1130 self.ts_closed = None;
1131 } else if self.status.is_closed() {
1132 self.ts_closed = self.ts_closed.or(Some(event.ts_event));
1133 }
1134 }
1135
1136 fn recompute_fill_state(&mut self, additional: Option<&OrderFillVoided>) -> Quantity {
1137 let corrections = self.fill_corrections(additional);
1138
1139 let mut filled_raw = Quantity::zero(self.quantity.precision).raw;
1140 let mut voided_raw = Quantity::zero(self.quantity.precision).raw;
1141 let mut non_reopened_voided_raw = Quantity::zero(self.quantity.precision).raw;
1142 let mut commissions = IndexMap::<Currency, Money>::new();
1143 let mut trade_ids = Vec::new();
1144 let mut last_trade_id = None;
1145 let mut position_id = None;
1146 let mut liquidity_side = Some(LiquiditySide::NoLiquiditySide);
1147 let mut matched_corrections = AHashSet::new();
1148
1149 for candidate in &self.events {
1150 let OrderEventAny::Filled(fill) = candidate else {
1151 continue;
1152 };
1153 let correction = corrections.get(&fill.trade_id).copied();
1154 if correction.is_some() {
1155 matched_corrections.insert(fill.trade_id);
1156 }
1157 let removed = Self::removed_fill_qty(fill, correction);
1158 let effective = fill.last_qty - removed;
1159 voided_raw = voided_raw.saturating_add(removed.raw);
1160 if correction.is_some_and(|event| !event.is_reopened) {
1161 non_reopened_voided_raw = non_reopened_voided_raw.saturating_add(removed.raw);
1162 }
1163
1164 if !effective.is_zero() {
1165 filled_raw = filled_raw.saturating_add(effective.raw);
1166 trade_ids.push(fill.trade_id);
1167 last_trade_id = Some(fill.trade_id);
1168 position_id = fill.position_id;
1169 liquidity_side = Some(fill.liquidity_side);
1170 }
1171
1172 if let Some(commission) = fill.commission {
1173 let surviving = correction
1174 .and_then(|event| event.commission_voided)
1175 .filter(|voided| !voided.is_zero())
1176 .map_or(commission, |voided| commission - voided);
1177
1178 if !surviving.is_zero() {
1179 commissions
1180 .entry(surviving.currency)
1181 .and_modify(|total| *total = *total + surviving)
1182 .or_insert(surviving);
1183 }
1184 }
1185 }
1186
1187 for (trade_id, correction) in corrections {
1188 if !matched_corrections.contains(&trade_id) {
1189 voided_raw = voided_raw.saturating_add(correction.voided_qty.raw);
1190 }
1191 }
1192
1193 self.filled_qty = Quantity::from_raw(filled_raw, self.quantity.precision);
1194 self.voided_qty = Quantity::from_raw(voided_raw, self.quantity.precision);
1195 self.overfill_qty = self.filled_qty.saturating_sub(self.quantity);
1196 self.avg_px = self.avg_px_from_fills(additional, None);
1197 self.commissions = commissions;
1198 self.trade_ids = trade_ids;
1199 self.last_trade_id = last_trade_id;
1200 self.position_id = position_id;
1201 self.liquidity_side = liquidity_side;
1202
1203 Quantity::from_raw(non_reopened_voided_raw, self.quantity.precision)
1204 }
1205
1206 fn updated(&mut self, event: &OrderUpdated) {
1207 if matches!(
1208 self.status,
1209 OrderStatus::PendingUpdate | OrderStatus::PendingCancel,
1210 ) && let Some(previous) = self.previous_status
1211 {
1212 self.status = previous;
1213 }
1214
1215 if event.reconciliation && !self.filled_qty.is_zero() && event.quantity == self.filled_qty {
1216 self.status = OrderStatus::Filled;
1217 self.ts_closed = Some(event.ts_event);
1218 }
1219
1220 if let Some(venue_order_id) = &event.venue_order_id
1221 && (self.venue_order_id.is_none()
1222 || venue_order_id != self.venue_order_id.as_ref().unwrap())
1223 {
1224 self.venue_order_id = Some(*venue_order_id);
1225 self.venue_order_ids.push(*venue_order_id);
1226 }
1227
1228 self.is_quote_quantity = event.is_quote_quantity;
1229 }
1230
1231 fn filled(&mut self, event: &OrderFilled) {
1232 let new_filled_qty = Quantity::from_raw(
1234 self.filled_qty.raw.saturating_add(event.last_qty.raw),
1235 self.filled_qty.precision,
1236 );
1237
1238 if new_filled_qty > self.quantity {
1240 let overfill_raw = new_filled_qty.raw - self.quantity.raw;
1241 self.overfill_qty = Quantity::from_raw(
1242 self.overfill_qty.raw.saturating_add(overfill_raw),
1243 self.filled_qty.precision,
1244 );
1245 }
1246
1247 let new_leaves_qty = self.leaves_qty.saturating_sub(event.last_qty);
1248
1249 if new_filled_qty >= self.quantity {
1250 self.status = OrderStatus::Filled;
1251 self.ts_closed = Some(event.ts_event);
1252 } else if new_leaves_qty.is_zero() && !self.voided_qty.is_zero() {
1253 self.status = OrderStatus::Voided;
1254 self.ts_closed = Some(event.ts_event);
1255 } else {
1256 self.status = OrderStatus::PartiallyFilled;
1257 }
1258
1259 self.venue_order_id = Some(event.venue_order_id);
1260 self.position_id = event.position_id;
1261 self.trade_ids.push(event.trade_id);
1262 self.last_trade_id = Some(event.trade_id);
1263 self.liquidity_side = Some(event.liquidity_side);
1264 self.filled_qty = new_filled_qty;
1265 self.leaves_qty = new_leaves_qty;
1266 self.ts_last = event.ts_event;
1267
1268 if let Some(commission) = event.commission {
1269 let commission_currency = commission.currency;
1270 if let Some(existing_commission) = self.commissions.get_mut(&commission_currency) {
1271 *existing_commission = *existing_commission + commission;
1272 } else {
1273 self.commissions.insert(commission_currency, commission);
1274 }
1275 }
1276
1277 if self.ts_accepted.is_none() {
1278 self.ts_accepted = Some(event.ts_event);
1280 }
1281
1282 self.avg_px = self.avg_px_from_fills(None, Some(event));
1283
1284 debug_assert!(
1285 matches!(
1286 self.status,
1287 OrderStatus::PartiallyFilled | OrderStatus::Filled | OrderStatus::Voided
1288 ),
1289 "Invariant: status must be PartiallyFilled, Filled, or Voided after fill handler (status={:?})",
1290 self.status
1291 );
1292 debug_assert!(
1293 self.venue_order_id.is_some()
1294 && self.last_trade_id.is_some()
1295 && !self.trade_ids.is_empty(),
1296 "Invariant: venue_order_id, last_trade_id, and trade_ids must be set after fill"
1297 );
1298 debug_assert!(
1299 self.filled_qty
1300 .raw
1301 .saturating_add(self.voided_qty.raw)
1302 .saturating_add(self.leaves_qty.raw)
1303 >= self.quantity.raw,
1304 "Invariant: filled_qty + voided_qty + leaves_qty >= quantity (filled={}, voided={}, leaves={}, quantity={})",
1305 self.filled_qty,
1306 self.voided_qty,
1307 self.leaves_qty,
1308 self.quantity
1309 );
1310 }
1311
1312 fn avg_px_from_fills(
1315 &self,
1316 additional: Option<&OrderFillVoided>,
1317 pending: Option<&OrderFilled>,
1318 ) -> Option<Decimal> {
1319 let corrections = self.fill_corrections(additional);
1320 let mut notional = Decimal::ZERO;
1321 let mut quantity = Decimal::ZERO;
1322
1323 for candidate in &self.events {
1324 let OrderEventAny::Filled(fill) = candidate else {
1325 continue;
1326 };
1327 let removed = Self::removed_fill_qty(fill, corrections.get(&fill.trade_id).copied());
1328 let effective = (fill.last_qty - removed).as_decimal();
1329 if effective.is_zero() {
1330 continue;
1331 }
1332
1333 notional = notional.saturating_add(effective.saturating_mul(fill.last_px.as_decimal()));
1334 quantity = quantity.saturating_add(effective);
1335 }
1336
1337 if let Some(fill) = pending {
1338 let last_qty = fill.last_qty.as_decimal();
1339 notional = notional.saturating_add(last_qty.saturating_mul(fill.last_px.as_decimal()));
1340 quantity = quantity.saturating_add(last_qty);
1341 }
1342
1343 notional.checked_div(quantity)
1344 }
1345
1346 fn fill_corrections<'a>(
1347 &'a self,
1348 additional: Option<&'a OrderFillVoided>,
1349 ) -> IndexMap<TradeId, &'a OrderFillVoided> {
1350 let mut corrections = IndexMap::new();
1351
1352 for candidate in &self.events {
1353 if let OrderEventAny::FillVoided(event) = candidate {
1354 corrections.insert(event.trade_id, event);
1355 }
1356 }
1357
1358 if let Some(event) = additional {
1359 corrections.insert(event.trade_id, event);
1360 }
1361
1362 corrections
1363 }
1364
1365 fn removed_fill_qty(fill: &OrderFilled, correction: Option<&OrderFillVoided>) -> Quantity {
1366 correction.map_or_else(
1367 || Quantity::zero(fill.last_qty.precision),
1368 |event| event.voided_qty.min(fill.last_qty),
1369 )
1370 }
1371
1372 pub fn set_slippage(&mut self, price: Price) {
1373 self.slippage = self.avg_px.and_then(|avg_px| {
1374 let current_price = price.as_decimal();
1375 match self.side {
1376 OrderSide::Buy if avg_px > current_price => avg_px.checked_sub(current_price),
1377 OrderSide::Sell if avg_px < current_price => current_price.checked_sub(avg_px),
1378 _ => None,
1379 }
1380 });
1381 }
1382
1383 #[must_use]
1385 pub fn opposite_side(side: OrderSide) -> OrderSide {
1386 match side {
1387 OrderSide::Buy => OrderSide::Sell,
1388 OrderSide::Sell => OrderSide::Buy,
1389 OrderSide::NoOrderSide => OrderSide::NoOrderSide,
1390 }
1391 }
1392
1393 #[must_use]
1395 pub fn closing_side(side: PositionSide) -> OrderSide {
1396 match side {
1397 PositionSide::Long => OrderSide::Sell,
1398 PositionSide::Short => OrderSide::Buy,
1399 PositionSide::Flat => OrderSide::NoOrderSide,
1400 PositionSide::NoPositionSide => OrderSide::NoOrderSide,
1401 }
1402 }
1403
1404 #[must_use]
1408 pub fn signed_decimal_qty(&self) -> Decimal {
1409 match self.side {
1410 OrderSide::Buy => self.quantity.as_decimal(),
1411 OrderSide::Sell => -self.quantity.as_decimal(),
1412 OrderSide::NoOrderSide => panic!("Invalid order side"),
1413 }
1414 }
1415
1416 #[must_use]
1417 pub fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
1418 if side == PositionSide::Flat {
1419 return false;
1420 }
1421
1422 match (self.side, side) {
1423 (OrderSide::Buy, PositionSide::Long) => false,
1424 (OrderSide::Buy, PositionSide::Short) => self.leaves_qty <= position_qty,
1425 (OrderSide::Sell, PositionSide::Short) => false,
1426 (OrderSide::Sell, PositionSide::Long) => self.leaves_qty <= position_qty,
1427 _ => true,
1428 }
1429 }
1430
1431 #[must_use]
1432 pub fn commission(&self, currency: &Currency) -> Option<Money> {
1433 self.commissions.get(currency).copied()
1434 }
1435
1436 #[must_use]
1437 pub fn commissions(&self) -> IndexMap<Currency, Money> {
1438 self.commissions.clone()
1439 }
1440
1441 #[must_use]
1442 pub fn commissions_vec(&self) -> Vec<Money> {
1443 self.commissions.values().copied().collect()
1444 }
1445
1446 #[must_use]
1447 pub fn init_event(&self) -> Option<OrderEventAny> {
1448 self.events.first().cloned()
1449 }
1450}
1451
1452#[cfg(test)]
1453mod tests {
1454 use std::str::FromStr;
1455
1456 use rstest::rstest;
1457 use rust_decimal_macros::dec;
1458
1459 use super::*;
1460 use crate::{
1461 enums::{LiquiditySide, OrderSide, OrderStatus, PositionSide, TriggerType},
1462 events::order::spec::{
1463 OrderAcceptedSpec, OrderCancelRejectedSpec, OrderCanceledSpec, OrderDeniedSpec,
1464 OrderExpiredSpec, OrderFillVoidedSpec, OrderFilledSpec, OrderInitializedSpec,
1465 OrderModifyRejectedSpec, OrderPendingCancelSpec, OrderPendingUpdateSpec,
1466 OrderRejectedSpec, OrderSubmittedSpec, OrderTriggeredSpec, OrderUpdatedSpec,
1467 },
1468 identifiers::InstrumentId,
1469 instruments::{CurrencyPair, Instrument, InstrumentAny, stubs::audusd_sim},
1470 orders::{MarketOrder, builder::OrderTestBuilder, stubs::TestOrderStubs},
1471 types::{Price, Quantity},
1472 };
1473
1474 #[rstest]
1485 #[case(OrderSide::Buy, OrderSide::Sell)]
1486 #[case(OrderSide::Sell, OrderSide::Buy)]
1487 #[case(OrderSide::NoOrderSide, OrderSide::NoOrderSide)]
1488 fn test_order_opposite_side(#[case] order_side: OrderSide, #[case] expected_side: OrderSide) {
1489 let result = OrderCore::opposite_side(order_side);
1490 assert_eq!(result, expected_side);
1491 }
1492
1493 #[rstest]
1494 #[case(PositionSide::Long, OrderSide::Sell)]
1495 #[case(PositionSide::Short, OrderSide::Buy)]
1496 #[case(PositionSide::NoPositionSide, OrderSide::NoOrderSide)]
1497 fn test_closing_side(#[case] position_side: PositionSide, #[case] expected_side: OrderSide) {
1498 let result = OrderCore::closing_side(position_side);
1499 assert_eq!(result, expected_side);
1500 }
1501
1502 #[rstest]
1503 #[case(OrderSide::Buy, dec!(10_000))]
1504 #[case(OrderSide::Sell, dec!(-10_000))]
1505 fn test_signed_decimal_qty(#[case] order_side: OrderSide, #[case] expected: Decimal) {
1506 let order: MarketOrder = OrderInitializedSpec::builder()
1507 .order_side(order_side)
1508 .quantity(Quantity::from(10_000))
1509 .build()
1510 .try_into()
1511 .unwrap();
1512
1513 let result = order.signed_decimal_qty();
1514 assert_eq!(result, expected);
1515 }
1516
1517 #[rustfmt::skip]
1518 #[rstest]
1519 #[case(OrderSide::Buy, Quantity::from(100), PositionSide::Long, Quantity::from(50), false)]
1520 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(50), true)]
1521 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(100), true)]
1522 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1523 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1524 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(50), true)]
1525 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(100), true)]
1526 #[case(OrderSide::Sell, Quantity::from(100), PositionSide::Short, Quantity::from(50), false)]
1527 fn test_would_reduce_only(
1528 #[case] order_side: OrderSide,
1529 #[case] order_qty: Quantity,
1530 #[case] position_side: PositionSide,
1531 #[case] position_qty: Quantity,
1532 #[case] expected: bool,
1533 ) {
1534 let order: MarketOrder = OrderInitializedSpec::builder()
1535 .order_side(order_side)
1536 .quantity(order_qty)
1537 .build()
1538 .try_into()
1539 .unwrap();
1540
1541 assert_eq!(
1542 order.would_reduce_only(position_side, position_qty),
1543 expected
1544 );
1545 }
1546
1547 #[rstest]
1548 fn test_order_state_transition_denied() {
1549 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
1550 let denied = OrderDeniedSpec::builder().build();
1551 let event = OrderEventAny::Denied(denied);
1552
1553 order.apply(event.clone()).unwrap();
1554
1555 assert_eq!(order.status, OrderStatus::Denied);
1556 assert!(order.is_closed());
1557 assert!(!order.is_open());
1558 assert_eq!(order.event_count(), 2);
1559 assert_eq!(order.last_event(), &event);
1560 }
1561
1562 #[rstest]
1563 fn test_order_life_cycle_to_filled() {
1564 let init = OrderInitializedSpec::builder().build();
1565 let submitted = OrderSubmittedSpec::builder().build();
1566 let accepted = OrderAcceptedSpec::builder().build();
1567 let filled = OrderFilledSpec::builder().build();
1568
1569 let mut order: MarketOrder = init.clone().try_into().unwrap();
1570 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1571 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1572 order.apply(OrderEventAny::Filled(filled)).unwrap();
1573
1574 assert_eq!(order.client_order_id, init.client_order_id);
1575 assert_eq!(order.status(), OrderStatus::Filled);
1576 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1577 assert_eq!(order.leaves_qty(), Quantity::from(0));
1578 assert_eq!(order.avg_px(), Some(dec!(1.0)));
1579 assert!(!order.is_open());
1580 assert!(order.is_closed());
1581 assert_eq!(order.commission(&Currency::USD()), None);
1582 assert_eq!(order.commissions(), &IndexMap::new());
1583 }
1584
1585 #[rstest]
1586 fn test_order_life_cycle_fills_with_negative_prices() {
1587 let init = OrderInitializedSpec::builder()
1591 .quantity(Quantity::from(100_000))
1592 .build();
1593 let submitted = OrderSubmittedSpec::builder().build();
1594 let accepted = OrderAcceptedSpec::builder().build();
1595 let fill1 = OrderFilledSpec::builder()
1596 .last_qty(Quantity::from(50_000))
1597 .last_px(Price::from("-5.00000"))
1598 .trade_id(TradeId::from("TRADE-1"))
1599 .build();
1600 let fill2 = OrderFilledSpec::builder()
1601 .last_qty(Quantity::from(50_000))
1602 .last_px(Price::from("-7.00000"))
1603 .trade_id(TradeId::from("TRADE-2"))
1604 .build();
1605
1606 let mut order: MarketOrder = init.try_into().unwrap();
1607 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1608 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1609 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1610 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1611
1612 assert_eq!(order.status(), OrderStatus::Filled);
1613 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1614 assert_eq!(order.leaves_qty(), Quantity::from(0));
1615 assert_eq!(order.avg_px(), Some(dec!(-6.0)));
1617 }
1618
1619 fn fill(trade_id: &str, last_qty: &str, last_px: &str) -> (TradeId, Quantity, Price) {
1620 (
1621 TradeId::from(trade_id),
1622 Quantity::from(last_qty),
1623 Price::from(last_px),
1624 )
1625 }
1626
1627 fn market_order_with_fills(
1628 order_side: OrderSide,
1629 quantity: Quantity,
1630 fills: &[(TradeId, Quantity, Price)],
1631 ) -> MarketOrder {
1632 let mut order: MarketOrder = OrderInitializedSpec::builder()
1633 .order_side(order_side)
1634 .quantity(quantity)
1635 .build()
1636 .try_into()
1637 .unwrap();
1638 order
1639 .apply(OrderEventAny::Accepted(
1640 OrderAcceptedSpec::builder().build(),
1641 ))
1642 .unwrap();
1643
1644 for (trade_id, last_qty, last_px) in fills {
1645 order
1646 .apply(OrderEventAny::Filled(
1647 OrderFilledSpec::builder()
1648 .order_side(order_side)
1649 .trade_id(*trade_id)
1650 .last_qty(*last_qty)
1651 .last_px(*last_px)
1652 .build(),
1653 ))
1654 .unwrap();
1655 }
1656
1657 order
1658 }
1659
1660 #[rstest]
1661 #[case(OrderSide::Buy)]
1662 #[case(OrderSide::Sell)]
1663 fn test_avg_px_weighted_average_is_exact(#[case] order_side: OrderSide) {
1664 let order = market_order_with_fills(
1667 order_side,
1668 Quantity::from(6),
1669 &[
1670 fill("TRADE-1", "1", "0.07"),
1671 fill("TRADE-2", "2", "0.13"),
1672 fill("TRADE-3", "3", "0.29"),
1673 ],
1674 );
1675
1676 assert_eq!(order.status(), OrderStatus::Filled);
1677 assert_eq!(order.filled_qty(), Quantity::from(6));
1678 assert_eq!(order.leaves_qty(), Quantity::from(0));
1679 assert_eq!(order.voided_qty(), Quantity::from(0));
1680 assert_eq!(order.avg_px(), Some(dec!(0.2)));
1681 }
1682
1683 #[rstest]
1684 fn test_avg_px_keeps_a_quotient_no_f64_can_hold() {
1685 let order = market_order_with_fills(
1688 OrderSide::Buy,
1689 Quantity::from(3),
1690 &[fill("TRADE-1", "1", "1.00"), fill("TRADE-2", "2", "2.00")],
1691 );
1692
1693 assert_eq!(order.status(), OrderStatus::Filled);
1694 assert_eq!(order.filled_qty(), Quantity::from(3));
1695 assert_eq!(order.avg_px(), Some(dec!(1.6666666666666666666666666667)));
1696 assert_ne!(
1697 order.avg_px(),
1698 Some(Decimal::from_f64_retain(5.0_f64 / 3.0).unwrap())
1699 );
1700 }
1701
1702 #[rstest]
1703 #[case(OrderSide::Buy, "1.07", "1.00", Some(dec!(0.07)))]
1704 #[case(OrderSide::Sell, "0.93", "1.00", Some(dec!(0.07)))]
1705 #[case(OrderSide::Buy, "0.93", "1.00", None)]
1706 #[case(OrderSide::Sell, "1.07", "1.00", None)]
1707 fn test_set_slippage_by_side(
1708 #[case] order_side: OrderSide,
1709 #[case] fill_px: &str,
1710 #[case] reference_px: &str,
1711 #[case] expected: Option<Decimal>,
1712 ) {
1713 let mut order =
1716 market_order_with_fills(order_side, Quantity::from(1), &[fill("T-1", "1", fill_px)]);
1717
1718 order.set_slippage(Price::from(reference_px));
1719
1720 assert_eq!(order.avg_px(), Some(Decimal::from_str(fill_px).unwrap()));
1721 assert_eq!(order.slippage(), expected);
1722 }
1723
1724 #[rstest]
1725 fn test_avg_px_invariant_to_fill_arrival_order() {
1726 let mut fills = [
1730 fill("TRADE-1", "50000", "1.00001"),
1731 fill("TRADE-2", "30000", "1.00002"),
1732 fill("TRADE-3", "20000", "1.00003"),
1733 ];
1734
1735 let ascending = market_order_with_fills(OrderSide::Buy, Quantity::from(100_000), &fills);
1736 fills.reverse();
1737 let descending = market_order_with_fills(OrderSide::Buy, Quantity::from(100_000), &fills);
1738
1739 assert_eq!(ascending.avg_px(), Some(dec!(1.000_017)));
1740 assert_eq!(descending.avg_px(), ascending.avg_px());
1741 assert_eq!(descending.filled_qty(), ascending.filled_qty());
1742 }
1743
1744 #[rstest]
1745 fn test_avg_px_after_fill_void_matches_order_without_voided_fill() {
1746 let surviving = [fill("TRADE-1", "1", "0.05"), fill("TRADE-2", "3", "0.15")];
1747 let voided = fill("TRADE-VOIDED", "2", "0.13");
1748
1749 let expected = market_order_with_fills(OrderSide::Buy, Quantity::from(6), &surviving);
1750 let mut corrected = market_order_with_fills(
1751 OrderSide::Buy,
1752 Quantity::from(6),
1753 &[surviving[0], surviving[1], voided],
1754 );
1755 corrected
1756 .apply(OrderEventAny::FillVoided(
1757 OrderFillVoidedSpec::builder()
1758 .trade_id(voided.0)
1759 .voided_qty(voided.1)
1760 .last_px(voided.2)
1761 .build(),
1762 ))
1763 .unwrap();
1764
1765 let replayed =
1766 OrderAny::from_events(corrected.events().into_iter().cloned().collect()).unwrap();
1767
1768 assert_eq!(expected.avg_px(), Some(dec!(0.125)));
1772 assert_eq!(corrected.avg_px(), expected.avg_px());
1773 assert_eq!(replayed.avg_px(), expected.avg_px());
1774 assert_eq!(corrected.status(), OrderStatus::Voided);
1775 assert_eq!(corrected.filled_qty(), Quantity::from(4));
1776 assert_eq!(corrected.voided_qty(), Quantity::from(2));
1777 assert_eq!(corrected.leaves_qty(), Quantity::from(0));
1778 assert_eq!(replayed.filled_qty(), corrected.filled_qty());
1779 assert_eq!(replayed.voided_qty(), corrected.voided_qty());
1780 }
1781
1782 #[rstest]
1783 fn test_avg_px_recomputed_from_partially_voided_fill() {
1784 let mut order = market_order_with_fills(
1785 OrderSide::Buy,
1786 Quantity::from(6),
1787 &[fill("TRADE-1", "4", "1.00"), fill("TRADE-2", "2", "1.14")],
1788 );
1789 order
1790 .apply(OrderEventAny::FillVoided(
1791 OrderFillVoidedSpec::builder()
1792 .trade_id(TradeId::from("TRADE-1"))
1793 .voided_qty(Quantity::from(2))
1794 .last_px(Price::from("1.00"))
1795 .build(),
1796 ))
1797 .unwrap();
1798
1799 assert_eq!(order.status(), OrderStatus::Voided);
1802 assert_eq!(order.filled_qty(), Quantity::from(4));
1803 assert_eq!(order.voided_qty(), Quantity::from(2));
1804 assert_eq!(order.leaves_qty(), Quantity::from(0));
1805 assert_eq!(order.avg_px(), Some(dec!(1.07)));
1806 }
1807
1808 #[rstest]
1809 fn test_avg_px_over_reopened_fill_void_and_replacement_fill() {
1810 let mut order = market_order_with_fills(
1811 OrderSide::Buy,
1812 Quantity::from(6),
1813 &[fill("TRADE-1", "4", "1.00")],
1814 );
1815 order
1816 .apply(OrderEventAny::FillVoided(
1817 OrderFillVoidedSpec::builder()
1818 .trade_id(TradeId::from("TRADE-1"))
1819 .voided_qty(Quantity::from(2))
1820 .last_px(Price::from("1.00"))
1821 .is_reopened(true)
1822 .build(),
1823 ))
1824 .unwrap();
1825 order
1826 .apply(OrderEventAny::Filled(
1827 OrderFilledSpec::builder()
1828 .trade_id(TradeId::from("TRADE-2"))
1829 .last_qty(Quantity::from(2))
1830 .last_px(Price::from("1.14"))
1831 .build(),
1832 ))
1833 .unwrap();
1834 order.set_slippage(Price::from("1.00"));
1835
1836 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1840 assert_eq!(order.filled_qty(), Quantity::from(4));
1841 assert_eq!(order.voided_qty(), Quantity::from(2));
1842 assert_eq!(order.leaves_qty(), Quantity::from(2));
1843 assert_eq!(order.avg_px(), Some(dec!(1.07)));
1844 assert_eq!(order.slippage(), Some(dec!(0.07)));
1845 }
1846
1847 #[rstest]
1848 fn test_avg_px_cleared_when_every_fill_is_voided() {
1849 let mut order = market_order_with_fills(
1850 OrderSide::Buy,
1851 Quantity::from(6),
1852 &[fill("TRADE-1", "4", "1.50")],
1853 );
1854 order
1855 .apply(OrderEventAny::FillVoided(
1856 OrderFillVoidedSpec::builder()
1857 .trade_id(TradeId::from("TRADE-1"))
1858 .voided_qty(Quantity::from(4))
1859 .last_px(Price::from("1.50"))
1860 .build(),
1861 ))
1862 .unwrap();
1863
1864 assert_eq!(order.status(), OrderStatus::Accepted);
1865 assert_eq!(order.filled_qty(), Quantity::from(0));
1866 assert_eq!(order.voided_qty(), Quantity::from(4));
1867 assert_eq!(order.leaves_qty(), Quantity::from(2));
1868 assert_eq!(order.avg_px(), None);
1869 }
1870
1871 #[cfg(feature = "high-precision")]
1872 #[rstest]
1873 fn test_avg_px_exact_over_high_precision_quantities() {
1874 let order = market_order_with_fills(
1877 OrderSide::Buy,
1878 Quantity::from("0.000000000010"),
1879 &[
1880 fill("TRADE-1", "0.000000000005", "1.00001"),
1881 fill("TRADE-2", "0.000000000003", "1.00002"),
1882 fill("TRADE-3", "0.000000000002", "1.00003"),
1883 ],
1884 );
1885
1886 assert_eq!(order.status(), OrderStatus::Filled);
1887 assert_eq!(order.filled_qty(), Quantity::from("0.000000000010"));
1888 assert_eq!(order.avg_px(), Some(dec!(1.000_017)));
1889 }
1890
1891 #[cfg(feature = "high-precision")]
1892 #[rstest]
1893 fn test_avg_px_exact_at_the_widest_representable_fill_scale() {
1894 let order = market_order_with_fills(
1897 OrderSide::Buy,
1898 Quantity::from("0.0000000000000008"),
1899 &[
1900 fill("TRADE-1", "0.0000000000000002", "0.000000000001"),
1901 fill("TRADE-2", "0.0000000000000006", "0.000000000003"),
1902 ],
1903 );
1904
1905 assert_eq!(order.status(), OrderStatus::Filled);
1907 assert_eq!(order.filled_qty(), Quantity::from("0.0000000000000008"));
1908 assert_eq!(order.avg_px(), Some(dec!(2.5e-12)));
1909 }
1910
1911 #[rstest]
1912 fn test_order_life_cycle_accumulates_fill_commissions() {
1913 let init = OrderInitializedSpec::builder()
1914 .quantity(Quantity::from(100_000))
1915 .build();
1916 let submitted = OrderSubmittedSpec::builder().build();
1917 let accepted = OrderAcceptedSpec::builder().build();
1918 let fill1 = OrderFilledSpec::builder()
1919 .last_qty(Quantity::from(50_000))
1920 .trade_id(TradeId::from("TRADE-1"))
1921 .commission(Money::from("1.25 USD"))
1922 .build();
1923 let fill2 = OrderFilledSpec::builder()
1924 .last_qty(Quantity::from(50_000))
1925 .trade_id(TradeId::from("TRADE-2"))
1926 .commission(Money::from("1.35 USD"))
1927 .build();
1928
1929 let mut order: MarketOrder = init.try_into().unwrap();
1930 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1931 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1932 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1933 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1934
1935 assert_eq!(order.status(), OrderStatus::Filled);
1936 assert_eq!(
1937 order.commission(&Currency::USD()),
1938 Some(Money::from("2.60 USD"))
1939 );
1940 assert_eq!(order.commissions_vec(), vec![Money::from("2.60 USD")]);
1941 }
1942
1943 #[rstest]
1944 fn test_fill_void_reopens_only_when_explicitly_marked() {
1945 let init = OrderInitializedSpec::builder()
1946 .quantity(Quantity::from(100_000))
1947 .build();
1948 let filled = OrderFilledSpec::builder()
1949 .trade_id(TradeId::from("TRADE-VOID"))
1950 .last_qty(Quantity::from(100_000))
1951 .commission(Money::from("1.00 USD"))
1952 .build();
1953 let fill_voided = OrderFillVoidedSpec::builder()
1954 .trade_id(filled.trade_id)
1955 .voided_qty(Quantity::from(40_000))
1956 .commission_voided(Money::from("0.40 USD"))
1957 .build();
1958 let later_fill = OrderFilledSpec::builder()
1959 .trade_id(TradeId::from("TRADE-AFTER-REOPEN"))
1960 .last_qty(Quantity::from(40_000))
1961 .build();
1962 let mut order: MarketOrder = init.try_into().unwrap();
1963 order
1964 .apply(OrderEventAny::Accepted(
1965 OrderAcceptedSpec::builder().build(),
1966 ))
1967 .unwrap();
1968 order.apply(OrderEventAny::Filled(filled)).unwrap();
1969
1970 let mut terminal = order.clone();
1971 terminal
1972 .apply(OrderEventAny::FillVoided(fill_voided.clone()))
1973 .unwrap();
1974 let fill_while_terminal = terminal.apply(OrderEventAny::Filled(later_fill.clone()));
1975
1976 order
1977 .apply(OrderEventAny::FillVoided(OrderFillVoided {
1978 is_reopened: true,
1979 ..fill_voided
1980 }))
1981 .unwrap();
1982 let status_after_correction = order.status();
1983 order.apply(OrderEventAny::Filled(later_fill)).unwrap();
1984
1985 assert!(matches!(
1986 fill_while_terminal,
1987 Err(OrderError::InvalidStateTransition)
1988 ));
1989 assert_eq!(terminal.status(), OrderStatus::Voided);
1990 assert_eq!(status_after_correction, OrderStatus::PartiallyFilled);
1991 assert_eq!(order.status(), OrderStatus::Filled);
1992 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1993 assert_eq!(order.voided_qty(), Quantity::from(40_000));
1994 assert_eq!(order.leaves_qty(), Quantity::from(0));
1995 assert_eq!(
1996 order.commission(&Currency::USD()),
1997 Some(Money::from("0.60 USD"))
1998 );
1999 }
2000
2001 #[rstest]
2002 fn test_fill_void_preserves_existing_working_remainder() {
2003 let init = OrderInitializedSpec::builder()
2004 .quantity(Quantity::from(100_000))
2005 .build();
2006 let filled = OrderFilledSpec::builder()
2007 .trade_id(TradeId::from("TRADE-VOID"))
2008 .last_qty(Quantity::from(40_000))
2009 .build();
2010 let later_fill = OrderFilledSpec::builder()
2011 .trade_id(TradeId::from("TRADE-REMAINDER"))
2012 .last_qty(Quantity::from(60_000))
2013 .build();
2014 let mut order: MarketOrder = init.try_into().unwrap();
2015 order
2016 .apply(OrderEventAny::Accepted(
2017 OrderAcceptedSpec::builder().build(),
2018 ))
2019 .unwrap();
2020 order.apply(OrderEventAny::Filled(filled.clone())).unwrap();
2021
2022 order
2023 .apply(OrderEventAny::FillVoided(
2024 OrderFillVoidedSpec::builder()
2025 .trade_id(filled.trade_id)
2026 .voided_qty(filled.last_qty)
2027 .build(),
2028 ))
2029 .unwrap();
2030
2031 assert_eq!(order.status(), OrderStatus::Accepted);
2032 assert_eq!(order.filled_qty(), Quantity::from(0));
2033 assert_eq!(order.voided_qty(), Quantity::from(40_000));
2034 assert_eq!(order.leaves_qty(), Quantity::from(60_000));
2035 assert!(order.is_open());
2036
2037 order.apply(OrderEventAny::Filled(later_fill)).unwrap();
2038
2039 assert_eq!(order.status(), OrderStatus::Voided);
2040 assert_eq!(order.filled_qty(), Quantity::from(60_000));
2041 assert_eq!(order.voided_qty(), Quantity::from(40_000));
2042 assert_eq!(order.leaves_qty(), Quantity::from(0));
2043 assert!(order.is_closed());
2044 }
2045
2046 #[rstest]
2047 fn test_reopened_fill_void_requires_existing_fill() {
2048 let trade_id = TradeId::from("TRADE-OUT-OF-ORDER");
2049 let init = OrderInitializedSpec::builder()
2050 .quantity(Quantity::from(100_000))
2051 .build();
2052 let early_void = OrderFillVoidedSpec::builder()
2053 .trade_id(trade_id)
2054 .voided_qty(Quantity::from(10_000))
2055 .is_reopened(true)
2056 .build();
2057 let late_fill = OrderFilledSpec::builder()
2058 .trade_id(trade_id)
2059 .last_qty(Quantity::from(40_000))
2060 .build();
2061 let mut order: MarketOrder = init.try_into().unwrap();
2062 order
2063 .apply(OrderEventAny::Accepted(
2064 OrderAcceptedSpec::builder().build(),
2065 ))
2066 .unwrap();
2067
2068 let result = order.apply(OrderEventAny::FillVoided(early_void));
2069 order.apply(OrderEventAny::Filled(late_fill)).unwrap();
2070
2071 assert!(matches!(result, Err(OrderError::InvalidOrderEvent)));
2072 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
2073 assert_eq!(order.filled_qty(), Quantity::from(40_000));
2074 assert_eq!(order.voided_qty(), Quantity::from(0));
2075 assert_eq!(order.leaves_qty(), Quantity::from(60_000));
2076 }
2077
2078 #[rstest]
2079 #[case(OrderStatus::Accepted)]
2080 #[case(OrderStatus::Canceled)]
2081 #[case(OrderStatus::Expired)]
2082 fn test_terminal_fill_void_rejects_later_order_events(#[case] status_before_void: OrderStatus) {
2083 let trade_id = TradeId::from("TRADE-TERMINAL-VOID");
2084 let init = OrderInitializedSpec::builder()
2085 .quantity(Quantity::from(100_000))
2086 .build();
2087 let terminal_void = OrderFillVoidedSpec::builder()
2088 .trade_id(trade_id)
2089 .voided_qty(Quantity::from(40_000))
2090 .commission_voided(Money::from("0 USD"))
2091 .build();
2092 let late_fill = OrderFilledSpec::builder()
2093 .trade_id(trade_id)
2094 .last_qty(Quantity::from(40_000))
2095 .build();
2096 let mut order: MarketOrder = init.try_into().unwrap();
2097 order
2098 .apply(OrderEventAny::Accepted(
2099 OrderAcceptedSpec::builder().build(),
2100 ))
2101 .unwrap();
2102
2103 match status_before_void {
2104 OrderStatus::Accepted => {}
2105 OrderStatus::Canceled => order
2106 .apply(OrderEventAny::Canceled(
2107 OrderCanceledSpec::builder().build(),
2108 ))
2109 .unwrap(),
2110 OrderStatus::Expired => order
2111 .apply(OrderEventAny::Expired(OrderExpiredSpec::builder().build()))
2112 .unwrap(),
2113 _ => unreachable!(),
2114 }
2115
2116 order
2117 .apply(OrderEventAny::FillVoided(terminal_void))
2118 .unwrap();
2119 let update_result = order.apply(OrderEventAny::Updated(
2120 OrderUpdatedSpec::builder()
2121 .quantity(Quantity::from(120_000))
2122 .build(),
2123 ));
2124 let cancel_result = order.apply(OrderEventAny::Canceled(
2125 OrderCanceledSpec::builder().build(),
2126 ));
2127 let fill_result = order.apply(OrderEventAny::Filled(late_fill));
2128
2129 assert!(matches!(
2130 update_result,
2131 Err(OrderError::InvalidStateTransition)
2132 ));
2133 assert!(matches!(
2134 cancel_result,
2135 Err(OrderError::InvalidStateTransition)
2136 ));
2137 assert!(matches!(
2138 fill_result,
2139 Err(OrderError::InvalidStateTransition)
2140 ));
2141 assert_eq!(order.status(), OrderStatus::Voided);
2142 assert_eq!(order.filled_qty(), Quantity::from(0));
2143 assert_eq!(order.voided_qty(), Quantity::from(40_000));
2144 assert_eq!(order.leaves_qty(), Quantity::from(0));
2145 }
2146
2147 #[rstest]
2148 #[case(Quantity::from(100_001), None)]
2149 #[case(Quantity::from(100_000), Some(Money::from("1 USD")))]
2150 fn test_terminal_fill_void_rejects_invalid_economic_values(
2151 #[case] voided_qty: Quantity,
2152 #[case] commission_voided: Option<Money>,
2153 ) {
2154 let trade_id = TradeId::from("TRADE-TERMINAL-VOID");
2155 let init = OrderInitializedSpec::builder()
2156 .quantity(Quantity::from(100_000))
2157 .build();
2158 let mut order: MarketOrder = init.try_into().unwrap();
2159 order
2160 .apply(OrderEventAny::Accepted(
2161 OrderAcceptedSpec::builder().build(),
2162 ))
2163 .unwrap();
2164 let mut invalid = OrderFillVoidedSpec::builder()
2165 .trade_id(trade_id)
2166 .voided_qty(voided_qty)
2167 .build();
2168 invalid.commission_voided = commission_voided;
2169
2170 let result = order.apply(OrderEventAny::FillVoided(invalid));
2171
2172 assert!(matches!(
2173 result,
2174 Err(OrderError::OverVoid(error_trade_id)) if error_trade_id == trade_id
2175 ));
2176 assert_eq!(order.status(), OrderStatus::Accepted);
2177 assert_eq!(order.voided_qty(), Quantity::from(0));
2178 }
2179
2180 #[rstest]
2181 fn test_terminal_fill_void_accepts_current_venue_order_id_after_fill_before_accept() {
2182 let init = OrderInitializedSpec::builder()
2183 .quantity(Quantity::from(100_000))
2184 .build();
2185 let submitted = OrderSubmittedSpec::builder().build();
2186 let filled = OrderFilledSpec::builder()
2187 .account_id(submitted.account_id)
2188 .trade_id(TradeId::from("TRADE-APPLIED"))
2189 .last_qty(Quantity::from(40_000))
2190 .build();
2191 let terminal_void = OrderFillVoidedSpec::builder()
2192 .venue_order_id(filled.venue_order_id)
2193 .account_id(filled.account_id)
2194 .trade_id(TradeId::from("TRADE-TERMINAL-VOID"))
2195 .voided_qty(Quantity::from(10_000))
2196 .build();
2197 let mut order: MarketOrder = init.try_into().unwrap();
2198 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2199 order.apply(OrderEventAny::Filled(filled)).unwrap();
2200
2201 order
2202 .apply(OrderEventAny::FillVoided(terminal_void))
2203 .unwrap();
2204
2205 assert_eq!(order.status(), OrderStatus::Voided);
2206 assert_eq!(order.filled_qty(), Quantity::from(40_000));
2207 assert_eq!(order.voided_qty(), Quantity::from(10_000));
2208 assert_eq!(order.leaves_qty(), Quantity::from(0));
2209 }
2210
2211 #[rstest]
2212 fn test_terminal_fill_void_requires_matching_order_identity() {
2213 let init = OrderInitializedSpec::builder()
2214 .quantity(Quantity::from(100_000))
2215 .build();
2216 let mut order: MarketOrder = init.try_into().unwrap();
2217 order
2218 .apply(OrderEventAny::Accepted(
2219 OrderAcceptedSpec::builder().build(),
2220 ))
2221 .unwrap();
2222 let mismatched = OrderFillVoidedSpec::builder()
2223 .instrument_id(InstrumentId::from("OTHER.SIM"))
2224 .trade_id(TradeId::from("TRADE-TERMINAL-VOID"))
2225 .voided_qty(Quantity::from(100_000))
2226 .build();
2227
2228 let result = order.apply(OrderEventAny::FillVoided(mismatched));
2229
2230 assert!(matches!(result, Err(OrderError::InvalidOrderEvent)));
2231 assert_eq!(order.status(), OrderStatus::Accepted);
2232 assert_eq!(order.voided_qty(), Quantity::from(0));
2233 }
2234
2235 #[rstest]
2236 #[case(OrderStatus::Canceled)]
2237 #[case(OrderStatus::Expired)]
2238 fn test_fill_void_does_not_reopen_canceled_or_expired_order(
2239 #[case] terminal_status: OrderStatus,
2240 ) {
2241 let init = OrderInitializedSpec::builder()
2242 .quantity(Quantity::from(100_000))
2243 .build();
2244 let filled = OrderFilledSpec::builder()
2245 .trade_id(TradeId::from("TRADE-TERMINAL-VOID"))
2246 .last_qty(Quantity::from(60_000))
2247 .build();
2248 let mut order: MarketOrder = init.try_into().unwrap();
2249 order
2250 .apply(OrderEventAny::Accepted(
2251 OrderAcceptedSpec::builder().build(),
2252 ))
2253 .unwrap();
2254 order.apply(OrderEventAny::Filled(filled.clone())).unwrap();
2255 match terminal_status {
2256 OrderStatus::Canceled => order
2257 .apply(OrderEventAny::Canceled(
2258 OrderCanceledSpec::builder().build(),
2259 ))
2260 .unwrap(),
2261 OrderStatus::Expired => order
2262 .apply(OrderEventAny::Expired(OrderExpiredSpec::builder().build()))
2263 .unwrap(),
2264 _ => unreachable!(),
2265 }
2266 let voided = OrderFillVoidedSpec::builder()
2267 .trade_id(filled.trade_id)
2268 .voided_qty(Quantity::from(20_000))
2269 .is_reopened(true)
2270 .build();
2271
2272 order.apply(OrderEventAny::FillVoided(voided)).unwrap();
2273
2274 assert_eq!(order.status(), terminal_status);
2275 assert_eq!(order.filled_qty(), Quantity::from(40_000));
2276 assert_eq!(order.voided_qty(), Quantity::from(20_000));
2277 assert!(order.is_closed());
2278 assert!(order.ts_closed().is_some());
2279 }
2280
2281 #[rstest]
2282 fn test_fill_void_rejects_duplicate_stale_and_over_void() {
2283 let init = OrderInitializedSpec::builder()
2284 .quantity(Quantity::from(100_000))
2285 .build();
2286 let filled = OrderFilledSpec::builder()
2287 .trade_id(TradeId::from("TRADE-VOID"))
2288 .last_qty(Quantity::from(100_000))
2289 .build();
2290 let mut order: MarketOrder = init.try_into().unwrap();
2291 order
2292 .apply(OrderEventAny::Accepted(
2293 OrderAcceptedSpec::builder().build(),
2294 ))
2295 .unwrap();
2296 order.apply(OrderEventAny::Filled(filled)).unwrap();
2297 order
2298 .apply(OrderEventAny::FillVoided(
2299 OrderFillVoidedSpec::builder()
2300 .trade_id(TradeId::from("TRADE-VOID"))
2301 .voided_qty(Quantity::from(40_000))
2302 .build(),
2303 ))
2304 .unwrap();
2305
2306 let duplicate = order.apply(OrderEventAny::FillVoided(
2307 OrderFillVoidedSpec::builder()
2308 .trade_id(TradeId::from("TRADE-VOID"))
2309 .voided_qty(Quantity::from(40_000))
2310 .build(),
2311 ));
2312 let stale = order.apply(OrderEventAny::FillVoided(
2313 OrderFillVoidedSpec::builder()
2314 .trade_id(TradeId::from("TRADE-VOID"))
2315 .voided_qty(Quantity::from(30_000))
2316 .build(),
2317 ));
2318 let over_void = order.apply(OrderEventAny::FillVoided(
2319 OrderFillVoidedSpec::builder()
2320 .trade_id(TradeId::from("TRADE-VOID"))
2321 .voided_qty(Quantity::from(100_001))
2322 .build(),
2323 ));
2324 assert!(matches!(
2325 duplicate,
2326 Err(OrderError::DuplicateFillVoid(trade_id))
2327 if trade_id == TradeId::from("TRADE-VOID")
2328 ));
2329 assert!(matches!(
2330 stale,
2331 Err(OrderError::StaleFillVoid(trade_id))
2332 if trade_id == TradeId::from("TRADE-VOID")
2333 ));
2334 assert!(matches!(
2335 over_void,
2336 Err(OrderError::OverVoid(trade_id))
2337 if trade_id == TradeId::from("TRADE-VOID")
2338 ));
2339 assert_eq!(order.filled_qty(), Quantity::from(60_000));
2340 assert_eq!(order.voided_qty(), Quantity::from(40_000));
2341 }
2342
2343 #[rstest]
2344 fn test_unknown_fill_void_does_not_reverse_surviving_fill() {
2345 let init = OrderInitializedSpec::builder()
2346 .quantity(Quantity::from(100_000))
2347 .build();
2348 let filled = OrderFilledSpec::builder()
2349 .trade_id(TradeId::from("TRADE-APPLIED"))
2350 .last_qty(Quantity::from(60_000))
2351 .build();
2352 let voided = OrderFillVoidedSpec::builder()
2353 .trade_id(TradeId::from("TRADE-NEVER-APPLIED"))
2354 .voided_qty(Quantity::from(40_000))
2355 .build();
2356 let mut order: MarketOrder = init.try_into().unwrap();
2357 order
2358 .apply(OrderEventAny::Accepted(
2359 OrderAcceptedSpec::builder().build(),
2360 ))
2361 .unwrap();
2362 order.apply(OrderEventAny::Filled(filled)).unwrap();
2363
2364 order.apply(OrderEventAny::FillVoided(voided)).unwrap();
2365 order
2366 .apply(OrderEventAny::FillVoided(
2367 OrderFillVoidedSpec::builder()
2368 .trade_id(TradeId::from("TRADE-APPLIED"))
2369 .voided_qty(Quantity::from(10_000))
2370 .is_reopened(true)
2371 .build(),
2372 ))
2373 .unwrap();
2374 let late_fill = order.apply(OrderEventAny::Filled(
2375 OrderFilledSpec::builder()
2376 .trade_id(TradeId::from("TRADE-LATE"))
2377 .last_qty(Quantity::from(10_000))
2378 .build(),
2379 ));
2380
2381 assert_eq!(order.status(), OrderStatus::Voided);
2382 assert_eq!(order.filled_qty(), Quantity::from(50_000));
2383 assert_eq!(order.voided_qty(), Quantity::from(50_000));
2384 assert_eq!(order.leaves_qty(), Quantity::zero(0));
2385 assert!(order.is_closed());
2386 assert!(matches!(late_fill, Err(OrderError::InvalidStateTransition)));
2387 }
2388
2389 #[rstest]
2390 fn test_order_state_transition_to_canceled() {
2391 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
2392 let submitted = OrderSubmittedSpec::builder().build();
2393 let canceled = OrderCanceledSpec::builder().build();
2394
2395 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2396 order.apply(OrderEventAny::Canceled(canceled)).unwrap();
2397
2398 assert_eq!(order.status(), OrderStatus::Canceled);
2399 assert!(order.is_closed());
2400 assert!(!order.is_open());
2401 }
2402
2403 #[rstest]
2404 fn test_order_life_cycle_to_partially_filled() {
2405 let init = OrderInitializedSpec::builder().build();
2406 let submitted = OrderSubmittedSpec::builder().build();
2407 let accepted = OrderAcceptedSpec::builder().build();
2408 let filled = OrderFilledSpec::builder()
2409 .last_qty(Quantity::from(50_000))
2410 .build();
2411
2412 let mut order: MarketOrder = init.clone().try_into().unwrap();
2413 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2414 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2415 order.apply(OrderEventAny::Filled(filled)).unwrap();
2416
2417 assert_eq!(order.client_order_id, init.client_order_id);
2418 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
2419 assert_eq!(order.filled_qty(), Quantity::from(50_000));
2420 assert_eq!(order.leaves_qty(), Quantity::from(50_000));
2421 assert!(order.is_open());
2422 assert!(!order.is_closed());
2423 }
2424
2425 #[rstest]
2426 fn test_order_commission_calculation() {
2427 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
2428 order
2429 .commissions
2430 .insert(Currency::USD(), Money::new(10.0, Currency::USD()));
2431
2432 assert_eq!(
2433 order.commission(&Currency::USD()),
2434 Some(Money::new(10.0, Currency::USD()))
2435 );
2436 assert_eq!(
2437 order.commissions_vec(),
2438 vec![Money::new(10.0, Currency::USD())]
2439 );
2440 }
2441
2442 #[rstest]
2443 fn test_order_is_primary() {
2444 let order: MarketOrder = OrderInitializedSpec::builder()
2445 .exec_algorithm_id(ExecAlgorithmId::from("ALGO-001"))
2446 .exec_spawn_id(ClientOrderId::from("O-001"))
2447 .client_order_id(ClientOrderId::from("O-001"))
2448 .build()
2449 .try_into()
2450 .unwrap();
2451
2452 assert!(order.is_primary());
2453 assert!(!order.is_spawned());
2454 }
2455
2456 #[rstest]
2457 fn test_order_is_spawned() {
2458 let order: MarketOrder = OrderInitializedSpec::builder()
2459 .exec_algorithm_id(ExecAlgorithmId::from("ALGO-001"))
2460 .exec_spawn_id(ClientOrderId::from("O-002"))
2461 .client_order_id(ClientOrderId::from("O-001"))
2462 .build()
2463 .try_into()
2464 .unwrap();
2465
2466 assert!(!order.is_primary());
2467 assert!(order.is_spawned());
2468 }
2469
2470 #[rstest]
2471 fn test_order_is_contingency() {
2472 let order: MarketOrder = OrderInitializedSpec::builder()
2473 .contingency_type(ContingencyType::Oto)
2474 .linked_order_ids(vec![ClientOrderId::from("O-LINKED")])
2475 .build()
2476 .try_into()
2477 .unwrap();
2478
2479 assert!(order.is_contingency());
2480 assert!(order.is_parent_order());
2481 assert!(!order.is_child_order());
2482 }
2483
2484 #[rstest]
2485 fn test_order_is_child_order() {
2486 let order: MarketOrder = OrderInitializedSpec::builder()
2487 .parent_order_id(ClientOrderId::from("PARENT-001"))
2488 .build()
2489 .try_into()
2490 .unwrap();
2491
2492 assert!(order.is_child_order());
2493 assert!(!order.is_parent_order());
2494 }
2495
2496 #[rstest]
2497 fn test_to_own_book_order_timestamp_ordering() {
2498 use crate::orders::limit::LimitOrder;
2499
2500 let init = OrderInitializedSpec::builder()
2502 .price(Price::from("100.00"))
2503 .build();
2504 let submitted = OrderSubmittedSpec::builder()
2505 .ts_event(UnixNanos::from(1_000_000))
2506 .build();
2507 let accepted = OrderAcceptedSpec::builder()
2508 .ts_event(UnixNanos::from(2_000_000))
2509 .build();
2510
2511 let mut order: LimitOrder = init.try_into().unwrap();
2512 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2513 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2514
2515 let own_book_order = order.to_own_book_order();
2516
2517 assert_eq!(own_book_order.ts_submitted, UnixNanos::from(1_000_000));
2519 assert_eq!(own_book_order.ts_accepted, UnixNanos::from(2_000_000));
2520 assert_eq!(own_book_order.ts_last, UnixNanos::from(2_000_000));
2521 }
2522
2523 #[rstest]
2524 fn test_to_own_book_order_partial_fill_uses_leaves_qty() {
2525 use crate::orders::limit::LimitOrder;
2526
2527 let init = OrderInitializedSpec::builder()
2528 .price(Price::from("100.00"))
2529 .quantity(Quantity::from(100_000))
2530 .build();
2531 let submitted = OrderSubmittedSpec::builder().build();
2532 let accepted = OrderAcceptedSpec::builder().build();
2533 let filled = OrderFilledSpec::builder()
2534 .order_type(OrderType::Limit)
2535 .last_qty(Quantity::from(40_000))
2536 .last_px(Price::from("100.00"))
2537 .build();
2538
2539 let mut order: LimitOrder = init.try_into().unwrap();
2540 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2541 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2542 order.apply(OrderEventAny::Filled(filled)).unwrap();
2543
2544 let own_book_order = order.to_own_book_order();
2545
2546 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
2547 assert_eq!(order.leaves_qty(), Quantity::from(60_000));
2548 assert_eq!(own_book_order.size, Quantity::from(60_000));
2549 }
2550
2551 #[rstest]
2552 fn test_order_accepted_without_submitted_sets_account_id() {
2553 let init = OrderInitializedSpec::builder().build();
2555 let accepted = OrderAcceptedSpec::builder()
2556 .account_id(AccountId::from("EXTERNAL-001"))
2557 .build();
2558
2559 let mut order: MarketOrder = init.try_into().unwrap();
2560
2561 assert_eq!(order.account_id(), None);
2563
2564 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2566
2567 assert_eq!(order.account_id(), Some(AccountId::from("EXTERNAL-001")));
2569 assert_eq!(order.status(), OrderStatus::Accepted);
2570 }
2571
2572 #[rstest]
2573 fn test_order_accepted_after_submitted_preserves_account_id() {
2574 let init = OrderInitializedSpec::builder().build();
2576 let submitted = OrderSubmittedSpec::builder()
2577 .account_id(AccountId::from("SUBMITTED-001"))
2578 .build();
2579 let accepted = OrderAcceptedSpec::builder()
2580 .account_id(AccountId::from("ACCEPTED-001"))
2581 .build();
2582
2583 let mut order: MarketOrder = init.try_into().unwrap();
2584 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2585
2586 assert_eq!(order.account_id(), Some(AccountId::from("SUBMITTED-001")));
2588
2589 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2591
2592 assert_eq!(order.account_id(), Some(AccountId::from("ACCEPTED-001")));
2594 assert_eq!(order.status(), OrderStatus::Accepted);
2595 }
2596
2597 #[rstest]
2598 fn test_overfill_tracks_overfill_qty() {
2599 let init = OrderInitializedSpec::builder()
2601 .quantity(Quantity::from(100_000))
2602 .build();
2603 let submitted = OrderSubmittedSpec::builder().build();
2604 let accepted = OrderAcceptedSpec::builder().build();
2605 let overfill = OrderFilledSpec::builder()
2606 .last_qty(Quantity::from(110_000)) .build();
2608
2609 let mut order: MarketOrder = init.try_into().unwrap();
2610 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2611 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2612 order.apply(OrderEventAny::Filled(overfill)).unwrap();
2613
2614 assert_eq!(order.overfill_qty(), Quantity::from(10_000));
2616 assert_eq!(order.filled_qty(), Quantity::from(110_000));
2617 assert_eq!(order.leaves_qty(), Quantity::from(0));
2618 assert_eq!(order.status(), OrderStatus::Filled);
2619 }
2620
2621 #[rstest]
2622 fn test_partial_fill_then_overfill() {
2623 let init = OrderInitializedSpec::builder()
2625 .quantity(Quantity::from(100_000))
2626 .build();
2627 let submitted = OrderSubmittedSpec::builder().build();
2628 let accepted = OrderAcceptedSpec::builder().build();
2629 let fill1 = OrderFilledSpec::builder()
2630 .last_qty(Quantity::from(80_000))
2631 .trade_id(TradeId::from("TRADE-1"))
2632 .build();
2633 let fill2 = OrderFilledSpec::builder()
2634 .last_qty(Quantity::from(30_000)) .trade_id(TradeId::from("TRADE-2"))
2636 .build();
2637
2638 let mut order: MarketOrder = init.try_into().unwrap();
2639 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2640 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2641 order.apply(OrderEventAny::Filled(fill1)).unwrap();
2642
2643 assert_eq!(order.overfill_qty(), Quantity::from(0));
2645 assert_eq!(order.filled_qty(), Quantity::from(80_000));
2646 assert_eq!(order.leaves_qty(), Quantity::from(20_000));
2647
2648 order.apply(OrderEventAny::Filled(fill2)).unwrap();
2649
2650 assert_eq!(order.overfill_qty(), Quantity::from(10_000));
2652 assert_eq!(order.filled_qty(), Quantity::from(110_000));
2653 assert_eq!(order.leaves_qty(), Quantity::from(0));
2654 assert_eq!(order.status(), OrderStatus::Filled);
2655 }
2656
2657 #[rstest]
2658 fn test_exact_fill_no_overfill() {
2659 let init = OrderInitializedSpec::builder()
2661 .quantity(Quantity::from(100_000))
2662 .build();
2663 let submitted = OrderSubmittedSpec::builder().build();
2664 let accepted = OrderAcceptedSpec::builder().build();
2665 let filled = OrderFilledSpec::builder()
2666 .last_qty(Quantity::from(100_000)) .build();
2668
2669 let mut order: MarketOrder = init.try_into().unwrap();
2670 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2671 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2672 order.apply(OrderEventAny::Filled(filled)).unwrap();
2673
2674 assert_eq!(order.overfill_qty(), Quantity::from(0));
2676 assert_eq!(order.filled_qty(), Quantity::from(100_000));
2677 assert_eq!(order.leaves_qty(), Quantity::from(0));
2678 }
2679
2680 #[rstest]
2681 fn test_partial_fill_then_overfill_with_fractional_quantities() {
2682 let init = OrderInitializedSpec::builder()
2686 .quantity(Quantity::from("2450.5"))
2687 .build();
2688 let submitted = OrderSubmittedSpec::builder().build();
2689 let accepted = OrderAcceptedSpec::builder().build();
2690 let fill1 = OrderFilledSpec::builder()
2691 .last_qty(Quantity::from("1202.5"))
2692 .trade_id(TradeId::from("TRADE-1"))
2693 .build();
2694 let fill2 = OrderFilledSpec::builder()
2695 .last_qty(Quantity::from("1285.5")) .trade_id(TradeId::from("TRADE-2"))
2697 .build();
2698
2699 let mut order: MarketOrder = init.try_into().unwrap();
2700 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2701 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2702 order.apply(OrderEventAny::Filled(fill1)).unwrap();
2703
2704 assert_eq!(order.overfill_qty(), Quantity::from(0));
2706 assert_eq!(order.filled_qty(), Quantity::from("1202.5"));
2707 assert_eq!(order.leaves_qty(), Quantity::from("1248.0"));
2708 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
2709
2710 order.apply(OrderEventAny::Filled(fill2)).unwrap();
2711
2712 assert_eq!(order.overfill_qty(), Quantity::from("37.5"));
2714 assert_eq!(order.filled_qty(), Quantity::from("2488.0"));
2715 assert_eq!(order.leaves_qty(), Quantity::from(0));
2716 assert_eq!(order.status(), OrderStatus::Filled);
2717 }
2718
2719 #[rstest]
2720 fn test_calculate_overfill_returns_zero_when_no_overfill() {
2721 let order: MarketOrder = OrderInitializedSpec::builder()
2722 .quantity(Quantity::from(100_000))
2723 .build()
2724 .try_into()
2725 .unwrap();
2726
2727 let overfill = order.calculate_overfill(Quantity::from(50_000));
2729 assert_eq!(overfill, Quantity::from(0));
2730
2731 let overfill = order.calculate_overfill(Quantity::from(100_000));
2733 assert_eq!(overfill, Quantity::from(0));
2734 }
2735
2736 #[rstest]
2737 fn test_calculate_overfill_returns_overfill_amount() {
2738 let order: MarketOrder = OrderInitializedSpec::builder()
2739 .quantity(Quantity::from(100_000))
2740 .build()
2741 .try_into()
2742 .unwrap();
2743
2744 let overfill = order.calculate_overfill(Quantity::from(110_000));
2746 assert_eq!(overfill, Quantity::from(10_000));
2747 }
2748
2749 #[rstest]
2750 fn test_calculate_overfill_accounts_for_existing_fills() {
2751 let init = OrderInitializedSpec::builder()
2752 .quantity(Quantity::from(100_000))
2753 .build();
2754 let submitted = OrderSubmittedSpec::builder().build();
2755 let accepted = OrderAcceptedSpec::builder().build();
2756 let partial_fill = OrderFilledSpec::builder()
2757 .last_qty(Quantity::from(60_000))
2758 .build();
2759
2760 let mut order: MarketOrder = init.try_into().unwrap();
2761 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2762 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2763 order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
2764
2765 let overfill = order.calculate_overfill(Quantity::from(50_000));
2768 assert_eq!(overfill, Quantity::from(10_000));
2769
2770 let overfill = order.calculate_overfill(Quantity::from(40_000));
2772 assert_eq!(overfill, Quantity::from(0));
2773 }
2774
2775 #[rstest]
2776 fn test_calculate_overfill_with_fractional_quantities() {
2777 let order: MarketOrder = OrderInitializedSpec::builder()
2778 .quantity(Quantity::from("2450.5"))
2779 .build()
2780 .try_into()
2781 .unwrap();
2782
2783 let overfill = order.calculate_overfill(Quantity::from("2488.0"));
2786 assert_eq!(overfill, Quantity::from("37.5"));
2787 }
2788
2789 #[rstest]
2790 fn test_calculate_overfill_zero_after_fractional_partial_fill() {
2791 let init = OrderInitializedSpec::builder()
2792 .quantity(Quantity::from("1.000"))
2793 .build();
2794 let submitted = OrderSubmittedSpec::builder().build();
2795 let accepted = OrderAcceptedSpec::builder().build();
2796 let partial_fill = OrderFilledSpec::builder()
2797 .last_qty(Quantity::from("0.072"))
2798 .build();
2799
2800 let mut order: MarketOrder = init.try_into().unwrap();
2801 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2802 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2803 order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
2804
2805 let overfill = order.calculate_overfill(Quantity::from("0.072"));
2807 assert_eq!(overfill, Quantity::from("0.000"));
2808 }
2809
2810 #[rstest]
2811 fn test_duplicate_fill_rejected() {
2812 let init = OrderInitializedSpec::builder()
2813 .quantity(Quantity::from(100_000))
2814 .build();
2815 let submitted = OrderSubmittedSpec::builder().build();
2816 let accepted = OrderAcceptedSpec::builder().build();
2817 let fill1 = OrderFilledSpec::builder()
2818 .last_qty(Quantity::from(50_000))
2819 .trade_id(TradeId::from("TRADE-001"))
2820 .build();
2821 let fill2_duplicate = OrderFilledSpec::builder()
2822 .last_qty(Quantity::from(50_000))
2823 .trade_id(TradeId::from("TRADE-001")) .build();
2825
2826 let mut order: MarketOrder = init.try_into().unwrap();
2827 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2828 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2829 order.apply(OrderEventAny::Filled(fill1)).unwrap();
2830
2831 assert_eq!(order.filled_qty(), Quantity::from(50_000));
2833 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
2834
2835 let result = order.apply(OrderEventAny::Filled(fill2_duplicate));
2837 assert!(result.is_err());
2838 match result.unwrap_err() {
2839 OrderError::DuplicateFill(trade_id) => {
2840 assert_eq!(trade_id, TradeId::from("TRADE-001"));
2841 }
2842 e => panic!("Expected DuplicateFill error, was: {e:?}"),
2843 }
2844
2845 assert_eq!(order.filled_qty(), Quantity::from(50_000));
2847 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
2848 }
2849
2850 #[rstest]
2851 fn test_check_display_qty_returns_typed_invariant_with_stable_display() {
2852 let error = check_display_qty(Some(Quantity::from(2)), Quantity::from(1)).unwrap_err();
2853
2854 match error {
2855 OrderError::Invariant(CorrectnessError::PredicateViolation { ref message }) => {
2856 assert_eq!(message, "`display_qty` may not exceed `quantity`");
2857 }
2858 other => panic!("Expected typed invariant error, was: {other:?}"),
2859 }
2860
2861 assert_eq!(error.to_string(), "`display_qty` may not exceed `quantity`");
2862 }
2863
2864 #[rstest]
2865 fn test_check_time_in_force_returns_typed_invariant_with_stable_display() {
2866 let error = check_time_in_force(TimeInForce::Gtd, None).unwrap_err();
2867
2868 match error {
2869 OrderError::Invariant(CorrectnessError::PredicateViolation { ref message }) => {
2870 assert_eq!(message, "`expire_time` is required for `GTD` order");
2871 }
2872 other => panic!("Expected typed invariant error, was: {other:?}"),
2873 }
2874
2875 assert_eq!(
2876 error.to_string(),
2877 "`expire_time` is required for `GTD` order"
2878 );
2879 }
2880
2881 #[rstest]
2882 fn test_different_trade_ids_allowed() {
2883 let init = OrderInitializedSpec::builder()
2884 .quantity(Quantity::from(100_000))
2885 .build();
2886 let submitted = OrderSubmittedSpec::builder().build();
2887 let accepted = OrderAcceptedSpec::builder().build();
2888 let fill1 = OrderFilledSpec::builder()
2889 .last_qty(Quantity::from(50_000))
2890 .trade_id(TradeId::from("TRADE-001"))
2891 .build();
2892 let fill2 = OrderFilledSpec::builder()
2893 .last_qty(Quantity::from(50_000))
2894 .trade_id(TradeId::from("TRADE-002")) .build();
2896
2897 let mut order: MarketOrder = init.try_into().unwrap();
2898 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2899 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2900 order.apply(OrderEventAny::Filled(fill1)).unwrap();
2901 order.apply(OrderEventAny::Filled(fill2)).unwrap();
2902
2903 assert_eq!(order.filled_qty(), Quantity::from(100_000));
2905 assert_eq!(order.status(), OrderStatus::Filled);
2906 assert_eq!(order.trade_ids.len(), 2);
2907 }
2908
2909 #[rstest]
2910 fn test_pending_update_order_restores_status_on_updated() {
2911 let init = OrderInitializedSpec::builder()
2912 .quantity(Quantity::from(100_000))
2913 .build();
2914 let submitted = OrderSubmittedSpec::builder().build();
2915 let accepted = OrderAcceptedSpec::builder().build();
2916 let pending_update = OrderPendingUpdateSpec::builder().build();
2917 let updated = OrderUpdatedSpec::builder()
2918 .quantity(Quantity::from(50_000))
2919 .build();
2920
2921 let mut order: MarketOrder = init.try_into().unwrap();
2922 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2923 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2924
2925 assert_eq!(order.status(), OrderStatus::Accepted);
2926
2927 order
2928 .apply(OrderEventAny::PendingUpdate(pending_update))
2929 .unwrap();
2930 assert_eq!(order.status(), OrderStatus::PendingUpdate);
2931
2932 order.apply(OrderEventAny::Updated(updated)).unwrap();
2933
2934 assert_eq!(order.status(), OrderStatus::Accepted);
2935 assert_eq!(order.quantity(), Quantity::from(50_000));
2936 }
2937
2938 #[rstest]
2939 fn test_pending_update_accepts_delayed_submitted() {
2940 let init = OrderInitializedSpec::builder()
2941 .quantity(Quantity::from(100_000))
2942 .build();
2943 let submitted = OrderSubmittedSpec::builder()
2944 .ts_event(UnixNanos::from(1_000))
2945 .build();
2946 let accepted = OrderAcceptedSpec::builder().build();
2947 let pending_update = OrderPendingUpdateSpec::builder().build();
2948
2949 let delayed_submitted = OrderSubmittedSpec::builder()
2953 .account_id(AccountId::from("SIM-002"))
2954 .ts_event(UnixNanos::from(3_000))
2955 .build();
2956 let updated = OrderUpdatedSpec::builder()
2957 .quantity(Quantity::from(50_000))
2958 .build();
2959
2960 let mut order: MarketOrder = init.try_into().unwrap();
2961 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
2962 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
2963 order
2964 .apply(OrderEventAny::PendingUpdate(pending_update))
2965 .unwrap();
2966
2967 assert_eq!(order.status(), OrderStatus::PendingUpdate);
2968 assert_eq!(order.previous_status(), Some(OrderStatus::Accepted));
2969 assert_eq!(order.account_id(), Some(AccountId::from("SIM-001")));
2970 assert_eq!(order.ts_submitted(), Some(UnixNanos::from(1_000)));
2971
2972 order
2973 .apply(OrderEventAny::Submitted(delayed_submitted))
2974 .unwrap();
2975
2976 assert_eq!(order.status(), OrderStatus::PendingUpdate);
2979 assert_eq!(order.previous_status(), Some(OrderStatus::Accepted));
2980
2981 assert_eq!(order.account_id(), Some(AccountId::from("SIM-002")));
2984 assert_eq!(order.ts_submitted(), Some(UnixNanos::from(3_000)));
2985
2986 order.apply(OrderEventAny::Updated(updated)).unwrap();
2987
2988 assert_eq!(order.status(), OrderStatus::Accepted);
2989 assert_eq!(order.quantity(), Quantity::from(50_000));
2990 }
2991
2992 #[rstest]
2993 fn test_rejected_event_preserves_status_restoration() {
2994 let init = OrderInitializedSpec::builder().build();
2995 let submitted = OrderSubmittedSpec::builder().build();
2996 let accepted = OrderAcceptedSpec::builder().build();
2997 let invalid = OrderSubmittedSpec::builder().build();
2998 let pending_update = OrderPendingUpdateSpec::builder().build();
2999 let modify_rejected = OrderModifyRejectedSpec::builder().build();
3000 let mut order: MarketOrder = init.try_into().unwrap();
3001 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3002 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3003 let previous_status = order.previous_status();
3004
3005 let result = order.apply(OrderEventAny::Submitted(invalid));
3006
3007 assert!(matches!(result, Err(OrderError::InvalidStateTransition)));
3008 assert_eq!(order.previous_status(), previous_status);
3009
3010 order
3011 .apply(OrderEventAny::PendingUpdate(pending_update))
3012 .unwrap();
3013 order
3014 .apply(OrderEventAny::ModifyRejected(modify_rejected))
3015 .unwrap();
3016 assert_eq!(order.status(), OrderStatus::Accepted);
3017 }
3018
3019 #[rstest]
3020 fn test_rejected_event_preserves_cancel_rejected_restoration() {
3021 let init = OrderInitializedSpec::builder().build();
3022 let submitted = OrderSubmittedSpec::builder().build();
3023 let accepted = OrderAcceptedSpec::builder().build();
3024 let invalid = OrderSubmittedSpec::builder().build();
3025 let pending_cancel = OrderPendingCancelSpec::builder().build();
3026 let cancel_rejected = OrderCancelRejectedSpec::builder().build();
3027 let mut order: MarketOrder = init.try_into().unwrap();
3028 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3029 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3030 let previous_status = order.previous_status();
3031
3032 let result = order.apply(OrderEventAny::Submitted(invalid));
3033
3034 assert!(matches!(result, Err(OrderError::InvalidStateTransition)));
3035 assert_eq!(order.previous_status(), previous_status);
3036
3037 order
3038 .apply(OrderEventAny::PendingCancel(pending_cancel))
3039 .unwrap();
3040 order
3041 .apply(OrderEventAny::CancelRejected(cancel_rejected))
3042 .unwrap();
3043 assert_eq!(order.status(), OrderStatus::Accepted);
3044 }
3045
3046 #[rstest]
3047 #[case(true, false, "client_order_id")]
3048 #[case(false, true, "strategy_id")]
3049 fn test_update_identity_mismatch_precedes_field_validation(
3050 #[case] wrong_client: bool,
3051 #[case] wrong_strategy: bool,
3052 #[case] expected_field: &str,
3053 ) {
3054 let init = OrderInitializedSpec::builder().build();
3055 let submitted = OrderSubmittedSpec::builder().build();
3056 let accepted = OrderAcceptedSpec::builder().build();
3057 let mut order: MarketOrder = init.try_into().unwrap();
3058 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3059 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3060 let state = (
3061 order.status(),
3062 order.previous_status(),
3063 order.ts_last(),
3064 order.events().len(),
3065 );
3066 let client_order_id = if wrong_client {
3067 ClientOrderId::from("O-INVALID")
3068 } else {
3069 order.client_order_id()
3070 };
3071 let strategy_id = if wrong_strategy {
3072 StrategyId::from("OTHER-001")
3073 } else {
3074 order.strategy_id()
3075 };
3076 let updated = OrderUpdatedSpec::builder()
3079 .client_order_id(client_order_id)
3080 .strategy_id(strategy_id)
3081 .price(Price::new(95.0, 2))
3082 .build();
3083
3084 let result = order.apply(OrderEventAny::Updated(updated));
3085
3086 let Err(OrderError::Invariant(CorrectnessError::PredicateViolation { message })) = result
3088 else {
3089 panic!("expected an identity predicate violation, was {result:?}");
3090 };
3091 assert!(
3092 message.contains(expected_field),
3093 "message did not name {expected_field}: {message}"
3094 );
3095 assert_eq!(order.status(), state.0);
3096 assert_eq!(order.previous_status(), state.1);
3097 assert_eq!(order.ts_last(), state.2);
3098 assert_eq!(order.events().len(), state.3);
3099 }
3100
3101 #[rstest]
3102 fn test_update_invalid_transition_precedes_field_validation() {
3103 let init = OrderInitializedSpec::builder().build();
3104 let denied = OrderDeniedSpec::builder().build();
3105 let mut order: MarketOrder = init.try_into().unwrap();
3106 order.apply(OrderEventAny::Denied(denied)).unwrap();
3107 let state = (
3108 order.status(),
3109 order.previous_status(),
3110 order.ts_last(),
3111 order.events().len(),
3112 );
3113 let updated = OrderUpdatedSpec::builder()
3116 .price(Price::new(95.0, 2))
3117 .build();
3118
3119 let result = order.apply(OrderEventAny::Updated(updated));
3120
3121 assert!(matches!(result, Err(OrderError::InvalidStateTransition)));
3122 assert_eq!(order.status(), state.0);
3123 assert_eq!(order.previous_status(), state.1);
3124 assert_eq!(order.ts_last(), state.2);
3125 assert_eq!(order.events().len(), state.3);
3126 }
3127
3128 #[rstest]
3129 fn test_direct_update_panics_on_invalid_field_before_mutation() {
3130 let init = OrderInitializedSpec::builder()
3131 .quantity(Quantity::from(100_000))
3132 .build();
3133 let mut order: MarketOrder = init.try_into().unwrap();
3134 let original_quantity = order.quantity();
3135 let updated = OrderUpdatedSpec::builder()
3140 .quantity(Quantity::from(50_000))
3141 .price(Price::new(95.0, 2))
3142 .build();
3143
3144 let result =
3145 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| order.update(&updated)));
3146
3147 assert!(result.is_err());
3148 assert_eq!(order.quantity(), original_quantity);
3149 }
3150
3151 #[rstest]
3152 fn test_partially_filled_order_can_be_updated() {
3153 let init = OrderInitializedSpec::builder()
3156 .quantity(Quantity::from(100_000))
3157 .build();
3158 let submitted = OrderSubmittedSpec::builder().build();
3159 let accepted = OrderAcceptedSpec::builder().build();
3160 let partial_fill = OrderFilledSpec::builder()
3161 .last_qty(Quantity::from(40_000))
3162 .build();
3163 let updated = OrderUpdatedSpec::builder()
3164 .quantity(Quantity::from(80_000)) .build();
3166
3167 let mut order: MarketOrder = init.try_into().unwrap();
3168 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3169 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3170 order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
3171
3172 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
3173 assert_eq!(order.filled_qty(), Quantity::from(40_000));
3174
3175 order.apply(OrderEventAny::Updated(updated)).unwrap();
3176
3177 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
3178 assert_eq!(order.quantity(), Quantity::from(80_000));
3179 assert_eq!(order.leaves_qty(), Quantity::from(40_000)); }
3181
3182 #[rstest]
3183 fn test_triggered_order_can_be_updated() {
3184 let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
3187 let submitted = OrderSubmittedSpec::builder().build();
3188 let accepted = OrderAcceptedSpec::builder().build();
3189 let triggered = OrderTriggeredSpec::builder().build();
3190 let updated = OrderUpdatedSpec::builder()
3191 .quantity(Quantity::from(80_000))
3192 .build();
3193
3194 let mut order = OrderTestBuilder::new(OrderType::StopLimit)
3195 .instrument_id(instrument_id)
3196 .quantity(Quantity::from(100_000))
3197 .price(Price::from("0.99500"))
3198 .trigger_price(Price::from("1.00000"))
3199 .trigger_type(TriggerType::LastPrice)
3200 .build();
3201 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3202 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3203 order.apply(OrderEventAny::Triggered(triggered)).unwrap();
3204
3205 assert_eq!(order.status(), OrderStatus::Triggered);
3206
3207 order.apply(OrderEventAny::Updated(updated)).unwrap();
3208
3209 assert_eq!(order.status(), OrderStatus::Triggered);
3210 assert_eq!(order.quantity(), Quantity::from(80_000));
3211 }
3212
3213 #[rstest]
3214 fn test_order_updated_with_is_quote_quantity_clears_flag() {
3215 let init = OrderInitializedSpec::builder()
3216 .quantity(Quantity::new(10.0, 6))
3217 .quote_quantity(true)
3218 .build();
3219 let submitted = OrderSubmittedSpec::builder().build();
3220 let accepted = OrderAcceptedSpec::builder().build();
3221 let updated = OrderUpdatedSpec::builder()
3222 .quantity(Quantity::new(47.393_365, 6))
3223 .is_quote_quantity(false)
3224 .build();
3225
3226 let mut order: MarketOrder = init.try_into().unwrap();
3227 assert!(order.is_quote_quantity());
3228
3229 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3230 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3231 order.apply(OrderEventAny::Updated(updated)).unwrap();
3232
3233 assert!(!order.is_quote_quantity());
3234 assert_eq!(order.quantity(), Quantity::new(47.393_365, 6));
3235 assert_eq!(order.leaves_qty(), Quantity::new(47.393_365, 6));
3236 }
3237
3238 #[rstest]
3239 fn test_order_updated_default_is_quote_quantity_clears_flag() {
3240 let init = OrderInitializedSpec::builder()
3241 .quantity(Quantity::new(10.0, 6))
3242 .quote_quantity(true)
3243 .build();
3244 let submitted = OrderSubmittedSpec::builder().build();
3245 let accepted = OrderAcceptedSpec::builder().build();
3246 let updated = OrderUpdatedSpec::builder()
3248 .quantity(Quantity::new(8.0, 6))
3249 .build();
3250
3251 let mut order: MarketOrder = init.try_into().unwrap();
3252 assert!(order.is_quote_quantity());
3253
3254 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3255 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3256 order.apply(OrderEventAny::Updated(updated)).unwrap();
3257
3258 assert!(!order.is_quote_quantity());
3259 assert_eq!(order.quantity(), Quantity::new(8.0, 6));
3260 }
3261
3262 #[rstest]
3263 fn test_canceled_then_partial_fill_then_canceled() {
3264 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
3265 let submitted = OrderSubmittedSpec::builder().build();
3266 let accepted = OrderAcceptedSpec::builder().build();
3267 let canceled1 = OrderCanceledSpec::builder().build();
3268 let fill = OrderFilledSpec::builder()
3269 .last_qty(Quantity::from(50_000))
3270 .trade_id(TradeId::from("FILL-1"))
3271 .build();
3272 let canceled2 = OrderCanceledSpec::builder().build();
3273
3274 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3275 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3276 order.apply(OrderEventAny::Canceled(canceled1)).unwrap();
3277 assert_eq!(order.status(), OrderStatus::Canceled);
3278 assert!(order.is_closed());
3279
3280 order.apply(OrderEventAny::Filled(fill)).unwrap();
3282 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
3283 assert_eq!(order.filled_qty(), Quantity::from(50_000));
3284 assert!(order.is_open());
3285
3286 order.apply(OrderEventAny::Canceled(canceled2)).unwrap();
3288 assert_eq!(order.status(), OrderStatus::Canceled);
3289 assert!(order.is_closed());
3290 }
3291
3292 #[rstest]
3293 fn test_updated_restores_status_before_pending_cancel() {
3294 let mut order: MarketOrder = OrderInitializedSpec::builder().build().try_into().unwrap();
3295 let submitted = OrderSubmittedSpec::builder().build();
3296 let accepted = OrderAcceptedSpec::builder().build();
3297 let pending_cancel = OrderPendingCancelSpec::builder().build();
3298 let updated = OrderUpdatedSpec::builder()
3299 .quantity(Quantity::from(150_000))
3300 .build();
3301
3302 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3303 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3304 let ts_accepted = order.ts_accepted();
3305 let venue_order_ids: Vec<VenueOrderId> =
3306 order.venue_order_ids().into_iter().copied().collect();
3307 order
3308 .apply(OrderEventAny::PendingCancel(pending_cancel))
3309 .unwrap();
3310 order.apply(OrderEventAny::Updated(updated)).unwrap();
3311
3312 assert_eq!(order.status(), OrderStatus::Accepted);
3313 assert_eq!(order.quantity(), Quantity::from(150_000));
3314 assert_eq!(order.ts_accepted(), ts_accepted);
3315 assert_eq!(
3316 order
3317 .venue_order_ids()
3318 .into_iter()
3319 .copied()
3320 .collect::<Vec<_>>(),
3321 venue_order_ids,
3322 );
3323 }
3324
3325 #[rstest]
3326 fn test_apply_triggered_to_stop_market_order_returns_error() {
3327 let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
3328 let submitted = OrderSubmittedSpec::builder().build();
3329 let accepted = OrderAcceptedSpec::builder().build();
3330 let triggered = OrderTriggeredSpec::builder().build();
3331
3332 let mut order = OrderTestBuilder::new(OrderType::StopMarket)
3333 .instrument_id(instrument_id)
3334 .quantity(Quantity::from(1))
3335 .trigger_price(Price::from("1.00000"))
3336 .trigger_type(TriggerType::LastPrice)
3337 .build();
3338 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3339 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3340
3341 let result = order.apply(OrderEventAny::Triggered(triggered));
3342 assert!(result.is_err());
3343 assert_eq!(order.status(), OrderStatus::Accepted);
3344 }
3345
3346 #[rstest]
3347 fn test_apply_triggered_to_stop_limit_order_succeeds() {
3348 let instrument_id = InstrumentId::from("ETHUSDT-LINEAR.BYBIT");
3349 let submitted = OrderSubmittedSpec::builder().build();
3350 let accepted = OrderAcceptedSpec::builder().build();
3351 let triggered = OrderTriggeredSpec::builder().build();
3352
3353 let mut order = OrderTestBuilder::new(OrderType::StopLimit)
3354 .instrument_id(instrument_id)
3355 .quantity(Quantity::from(1))
3356 .price(Price::from("0.99500"))
3357 .trigger_price(Price::from("1.00000"))
3358 .trigger_type(TriggerType::LastPrice)
3359 .build();
3360 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3361 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3362 order.apply(OrderEventAny::Triggered(triggered)).unwrap();
3363
3364 assert_eq!(order.status(), OrderStatus::Triggered);
3365 }
3366
3367 #[rstest]
3368 fn test_to_order_status_report_for_accepted_limit_order() {
3369 let order = OrderTestBuilder::new(OrderType::Limit)
3370 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3371 .side(OrderSide::Buy)
3372 .quantity(Quantity::from(100_000))
3373 .price(Price::from("1.00000"))
3374 .build();
3375 let accepted = TestOrderStubs::make_accepted_order(&order);
3376
3377 let report = accepted.to_order_status_report(None).unwrap();
3378
3379 assert_eq!(report.account_id, AccountId::from("SIM-001"));
3380 assert_eq!(report.instrument_id, InstrumentId::from("AUDUSD.SIM"));
3381 assert_eq!(report.client_order_id, Some(accepted.client_order_id()));
3382 assert_eq!(report.venue_order_id, VenueOrderId::from("V-001"));
3383 assert_eq!(report.order_side, OrderSide::Buy);
3384 assert_eq!(report.order_type, OrderType::Limit);
3385 assert_eq!(report.time_in_force, accepted.time_in_force());
3386 assert_eq!(report.order_status, OrderStatus::Accepted);
3387 assert_eq!(report.quantity, Quantity::from(100_000));
3388 assert_eq!(report.filled_qty, Quantity::from(0));
3389 assert_eq!(report.price, Some(Price::from("1.00000")));
3390 assert_eq!(report.avg_px, None);
3391 assert_eq!(report.ts_accepted, accepted.ts_accepted().unwrap());
3392 assert_eq!(report.ts_last, accepted.ts_last());
3393 assert_eq!(report.ts_init, accepted.ts_init());
3394 }
3395
3396 #[rstest]
3397 fn test_to_order_status_report_for_filled_market_order(audusd_sim: CurrencyPair) {
3398 let audusd_sim = InstrumentAny::CurrencyPair(audusd_sim);
3399 let order = OrderTestBuilder::new(OrderType::Market)
3400 .instrument_id(audusd_sim.id())
3401 .side(OrderSide::Buy)
3402 .quantity(Quantity::from(100_000))
3403 .build();
3404 let filled = TestOrderStubs::make_filled_order(&order, &audusd_sim, LiquiditySide::Maker);
3405 let report_id = UUID4::new();
3406
3407 let report = filled.to_order_status_report(Some(report_id)).unwrap();
3408
3409 assert_eq!(report.report_id, report_id);
3410 assert_eq!(report.order_status, OrderStatus::Filled);
3411 assert_eq!(report.quantity, Quantity::from(100_000));
3412 assert_eq!(report.filled_qty, Quantity::from(100_000));
3413 assert_eq!(report.price, None);
3414 assert_eq!(report.avg_px, Some(dec!(1)));
3415 assert_eq!(report.venue_position_id, Some(PositionId::from("1")));
3416 assert_eq!(report.ts_accepted, filled.ts_accepted().unwrap());
3417 assert_eq!(report.ts_last, filled.ts_last());
3418 }
3419
3420 #[rstest]
3421 fn test_to_order_status_report_maps_optional_fields() {
3422 let order = OrderTestBuilder::new(OrderType::StopLimit)
3423 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3424 .side(OrderSide::Sell)
3425 .quantity(Quantity::from(100_000))
3426 .price(Price::from("0.99500"))
3427 .trigger_price(Price::from("1.00000"))
3428 .trigger_type(TriggerType::LastPrice)
3429 .time_in_force(TimeInForce::Gtd)
3430 .expire_time(UnixNanos::from(5_000_000_000))
3431 .display_qty(Quantity::from(10_000))
3432 .post_only(true)
3433 .reduce_only(true)
3434 .contingency_type(ContingencyType::Oto)
3435 .order_list_id(OrderListId::from("OL-001"))
3436 .linked_order_ids(vec![ClientOrderId::from("O-CHILD")])
3437 .parent_order_id(ClientOrderId::from("O-PARENT"))
3438 .build();
3439 let accepted = TestOrderStubs::make_accepted_order(&order);
3440
3441 let report = accepted.to_order_status_report(None).unwrap();
3442
3443 assert_eq!(report.price, Some(Price::from("0.99500")));
3444 assert_eq!(report.trigger_price, Some(Price::from("1.00000")));
3445 assert_eq!(report.trigger_type, Some(TriggerType::LastPrice));
3446 assert_eq!(report.expire_time, Some(UnixNanos::from(5_000_000_000)));
3447 assert_eq!(report.display_qty, Some(Quantity::from(10_000)));
3448 assert!(report.post_only);
3449 assert!(report.reduce_only);
3450 assert_eq!(report.contingency_type, ContingencyType::Oto);
3451 assert_eq!(report.order_list_id, Some(OrderListId::from("OL-001")));
3452 assert_eq!(
3453 report.linked_order_ids,
3454 Some(vec![ClientOrderId::from("O-CHILD")])
3455 );
3456 assert_eq!(
3457 report.parent_order_id,
3458 Some(ClientOrderId::from("O-PARENT"))
3459 );
3460 }
3461
3462 #[rstest]
3463 fn test_to_order_status_report_maps_ts_triggered() {
3464 let order = OrderTestBuilder::new(OrderType::StopLimit)
3465 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3466 .side(OrderSide::Buy)
3467 .quantity(Quantity::from(100_000))
3468 .price(Price::from("0.99500"))
3469 .trigger_price(Price::from("1.00000"))
3470 .trigger_type(TriggerType::LastPrice)
3471 .build();
3472 let mut order = TestOrderStubs::make_accepted_order(&order);
3473 let triggered = OrderTriggeredSpec::builder()
3474 .ts_event(UnixNanos::from(1_500_000_000))
3475 .build();
3476 order.apply(OrderEventAny::Triggered(triggered)).unwrap();
3477
3478 let report = order.to_order_status_report(None).unwrap();
3479
3480 assert_eq!(report.order_status, OrderStatus::Triggered);
3481 assert_eq!(report.ts_triggered, Some(UnixNanos::from(1_500_000_000)));
3482 }
3483
3484 #[rstest]
3485 fn test_to_order_status_report_maps_rejection_reason() {
3486 let order = OrderTestBuilder::new(OrderType::Limit)
3487 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3488 .side(OrderSide::Buy)
3489 .quantity(Quantity::from(100_000))
3490 .price(Price::from("1.00000"))
3491 .build();
3492 let mut order = TestOrderStubs::make_accepted_order(&order);
3493 let rejected = OrderRejectedSpec::builder()
3494 .reason(Ustr::from("INSUFFICIENT_MARGIN"))
3495 .build();
3496 order.apply(OrderEventAny::Rejected(rejected)).unwrap();
3497
3498 let report = order.to_order_status_report(None).unwrap();
3499
3500 assert_eq!(report.order_status, OrderStatus::Rejected);
3501 assert_eq!(
3502 report.cancel_reason,
3503 Some("INSUFFICIENT_MARGIN".to_string())
3504 );
3505 }
3506
3507 #[rstest]
3508 fn test_to_order_status_report_maps_distinct_timestamps() {
3509 let mut order = OrderTestBuilder::new(OrderType::Limit)
3510 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3511 .side(OrderSide::Buy)
3512 .quantity(Quantity::from(100_000))
3513 .price(Price::from("1.00000"))
3514 .ts_init(UnixNanos::from(1_000))
3515 .build();
3516 let submitted = OrderSubmittedSpec::builder()
3517 .ts_event(UnixNanos::from(2_000))
3518 .build();
3519 let accepted = OrderAcceptedSpec::builder()
3520 .ts_event(UnixNanos::from(3_000))
3521 .build();
3522 let filled = OrderFilledSpec::builder()
3523 .last_qty(Quantity::from(50_000))
3524 .ts_event(UnixNanos::from(4_000))
3525 .build();
3526 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3527 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
3528 order.apply(OrderEventAny::Filled(filled)).unwrap();
3529
3530 let report = order.to_order_status_report(None).unwrap();
3531
3532 assert_eq!(report.order_status, OrderStatus::PartiallyFilled);
3533 assert_eq!(report.filled_qty, Quantity::from(50_000));
3534 assert_eq!(report.ts_accepted, UnixNanos::from(3_000));
3535 assert_eq!(report.ts_last, UnixNanos::from(4_000));
3536 assert_eq!(report.ts_init, UnixNanos::from(1_000));
3537 }
3538
3539 #[rstest]
3540 fn test_to_order_status_report_ts_accepted_falls_back_to_ts_last() {
3541 let mut order = OrderTestBuilder::new(OrderType::Limit)
3543 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3544 .side(OrderSide::Buy)
3545 .quantity(Quantity::from(100_000))
3546 .price(Price::from("1.00000"))
3547 .build();
3548 let submitted = OrderSubmittedSpec::builder()
3549 .ts_event(UnixNanos::from(2_000))
3550 .build();
3551 let updated = OrderUpdatedSpec::builder()
3552 .venue_order_id(VenueOrderId::from("V-001"))
3553 .ts_event(UnixNanos::from(3_000))
3554 .build();
3555 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3556 order.apply(OrderEventAny::Updated(updated)).unwrap();
3557
3558 let report = order.to_order_status_report(None).unwrap();
3559
3560 assert_eq!(report.order_status, OrderStatus::Submitted);
3561 assert_eq!(report.venue_order_id, VenueOrderId::from("V-001"));
3562 assert_eq!(report.ts_accepted, UnixNanos::from(3_000));
3563 assert_eq!(report.ts_last, UnixNanos::from(3_000));
3564 }
3565
3566 #[rstest]
3567 fn test_to_order_status_report_maps_trailing_offsets() {
3568 let order = OrderTestBuilder::new(OrderType::TrailingStopLimit)
3569 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3570 .side(OrderSide::Sell)
3571 .quantity(Quantity::from(100_000))
3572 .price(Price::from("0.99500"))
3573 .activation_price(Price::from("1.00500"))
3574 .trigger_price(Price::from("1.00000"))
3575 .trigger_type(TriggerType::LastPrice)
3576 .limit_offset(dec!(0.0001))
3577 .trailing_offset(dec!(0.0002))
3578 .trailing_offset_type(TrailingOffsetType::Price)
3579 .build();
3580 let accepted = TestOrderStubs::make_accepted_order(&order);
3581
3582 let report = accepted.to_order_status_report(None).unwrap();
3583
3584 assert_eq!(report.activation_price, Some(Price::from("1.00500")));
3585 assert_eq!(report.limit_offset, Some(dec!(0.0001)));
3586 assert_eq!(report.trailing_offset, Some(dec!(0.0002)));
3587 assert_eq!(report.trailing_offset_type, TrailingOffsetType::Price);
3588 }
3589
3590 #[rstest]
3591 fn test_to_order_status_report_returns_none_before_venue_ack() {
3592 let order = OrderTestBuilder::new(OrderType::Limit)
3593 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3594 .side(OrderSide::Buy)
3595 .quantity(Quantity::from(100_000))
3596 .price(Price::from("1.00000"))
3597 .build();
3598
3599 assert!(order.to_order_status_report(None).is_none());
3600 }
3601
3602 #[rstest]
3603 fn test_to_order_status_report_returns_none_for_submitted_order() {
3604 let mut order = OrderTestBuilder::new(OrderType::Limit)
3606 .instrument_id(InstrumentId::from("AUDUSD.SIM"))
3607 .side(OrderSide::Buy)
3608 .quantity(Quantity::from(100_000))
3609 .price(Price::from("1.00000"))
3610 .build();
3611 let submitted = OrderSubmittedSpec::builder().build();
3612 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
3613
3614 assert!(order.account_id().is_some());
3615 assert!(order.to_order_status_report(None).is_none());
3616 }
3617}