1pub mod any;
19pub mod builder;
20pub mod default;
21pub mod limit;
22pub mod limit_if_touched;
23pub mod list;
24pub mod market;
25pub mod market_if_touched;
26pub mod market_to_limit;
27pub mod stop_limit;
28pub mod stop_market;
29pub mod trailing_stop_limit;
30pub mod trailing_stop_market;
31
32#[cfg(any(test, feature = "stubs"))]
33pub mod stubs;
34
35use ahash::AHashSet;
37use enum_dispatch::enum_dispatch;
38use indexmap::IndexMap;
39use nautilus_core::{UUID4, UnixNanos};
40use rust_decimal::Decimal;
41use serde::{Deserialize, Serialize};
42use ustr::Ustr;
43
44pub use crate::orders::{
45 any::{LimitOrderAny, OrderAny, PassiveOrderAny, StopOrderAny},
46 builder::OrderTestBuilder,
47 limit::LimitOrder,
48 limit_if_touched::LimitIfTouchedOrder,
49 list::OrderList,
50 market::MarketOrder,
51 market_if_touched::MarketIfTouchedOrder,
52 market_to_limit::MarketToLimitOrder,
53 stop_limit::StopLimitOrder,
54 stop_market::StopMarketOrder,
55 trailing_stop_limit::TrailingStopLimitOrder,
56 trailing_stop_market::TrailingStopMarketOrder,
57};
58use crate::{
59 enums::{
60 ContingencyType, LiquiditySide, OrderSide, OrderSideSpecified, OrderStatus, OrderType,
61 PositionSide, TimeInForce, TrailingOffsetType, TriggerType,
62 },
63 events::{
64 OrderAccepted, OrderCancelRejected, OrderCanceled, OrderDenied, OrderEmulated,
65 OrderEventAny, OrderExpired, OrderFilled, OrderInitialized, OrderModifyRejected,
66 OrderPendingCancel, OrderPendingUpdate, OrderRejected, OrderReleased, OrderSubmitted,
67 OrderTriggered, OrderUpdated,
68 },
69 identifiers::{
70 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
71 StrategyId, Symbol, TradeId, TraderId, Venue, VenueOrderId,
72 },
73 orderbook::OwnBookOrder,
74 types::{Currency, Money, Price, Quantity},
75};
76
77pub const STOP_ORDER_TYPES: &[OrderType] = &[
79 OrderType::StopMarket,
80 OrderType::StopLimit,
81 OrderType::MarketIfTouched,
82 OrderType::LimitIfTouched,
83];
84
85pub const LIMIT_ORDER_TYPES: &[OrderType] = &[
87 OrderType::Limit,
88 OrderType::StopLimit,
89 OrderType::LimitIfTouched,
90 OrderType::MarketIfTouched,
91];
92
93pub const LOCAL_ACTIVE_ORDER_STATUSES: &[OrderStatus] = &[
95 OrderStatus::Initialized,
96 OrderStatus::Emulated,
97 OrderStatus::Released,
98];
99
100pub const CANCELLABLE_ORDER_STATUSES: &[OrderStatus] = &[
109 OrderStatus::Accepted,
110 OrderStatus::Triggered,
111 OrderStatus::PendingUpdate,
112 OrderStatus::PartiallyFilled,
113];
114
115#[must_use]
124pub fn cancellable_order_statuses_set() -> &'static AHashSet<OrderStatus> {
125 OrderStatus::cancellable_statuses_set()
126}
127
128#[derive(thiserror::Error, Debug)]
129pub enum OrderError {
130 #[error("Order not found: {0}")]
131 NotFound(ClientOrderId),
132 #[error("Order invariant failed: must have a side for this operation")]
133 NoOrderSide,
134 #[error("Invalid event for order type")]
135 InvalidOrderEvent,
136 #[error("Invalid order state transition")]
137 InvalidStateTransition,
138 #[error("Order was already initialized")]
139 AlreadyInitialized,
140 #[error("Order had no previous state")]
141 NoPreviousState,
142 #[error("Duplicate fill: trade_id {0} already applied to order")]
143 DuplicateFill(TradeId),
144 #[error("{0}")]
145 Invariant(#[from] anyhow::Error),
146}
147
148#[must_use]
150pub fn ustr_indexmap_to_str(h: IndexMap<Ustr, Ustr>) -> IndexMap<String, String> {
151 h.into_iter()
152 .map(|(k, v)| (k.to_string(), v.to_string()))
153 .collect()
154}
155
156#[must_use]
158pub fn str_indexmap_to_ustr(h: IndexMap<String, String>) -> IndexMap<Ustr, Ustr> {
159 h.into_iter()
160 .map(|(k, v)| (Ustr::from(&k), Ustr::from(&v)))
161 .collect()
162}
163
164#[inline]
165pub(crate) fn check_display_qty(
166 display_qty: Option<Quantity>,
167 quantity: Quantity,
168) -> Result<(), OrderError> {
169 if let Some(q) = display_qty
170 && q > quantity
171 {
172 return Err(OrderError::Invariant(anyhow::anyhow!(
173 "`display_qty` may not exceed `quantity`"
174 )));
175 }
176 Ok(())
177}
178
179#[inline]
180pub(crate) fn check_time_in_force(
181 time_in_force: TimeInForce,
182 expire_time: Option<UnixNanos>,
183) -> Result<(), OrderError> {
184 if time_in_force == TimeInForce::Gtd && expire_time.unwrap_or_default() == 0 {
185 return Err(OrderError::Invariant(anyhow::anyhow!(
186 "`expire_time` is required for `GTD` order"
187 )));
188 }
189 Ok(())
190}
191
192impl OrderStatus {
193 #[rustfmt::skip]
199 pub fn transition(&mut self, event: &OrderEventAny) -> Result<Self, OrderError> {
200 let new_state = match (self, event) {
201 (Self::Initialized, OrderEventAny::Denied(_)) => Self::Denied,
202 (Self::Initialized, OrderEventAny::Emulated(_)) => Self::Emulated, (Self::Initialized, OrderEventAny::Released(_)) => Self::Released, (Self::Initialized, OrderEventAny::Submitted(_)) => Self::Submitted,
205 (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::Emulated, OrderEventAny::Canceled(_)) => Self::Canceled, (Self::Emulated, OrderEventAny::Expired(_)) => Self::Expired, (Self::Emulated, OrderEventAny::Released(_)) => Self::Released, (Self::Released, OrderEventAny::Submitted(_)) => Self::Submitted, (Self::Released, OrderEventAny::Denied(_)) => Self::Denied, (Self::Released, OrderEventAny::Canceled(_)) => Self::Canceled, (Self::Submitted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
217 (Self::Submitted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
218 (Self::Submitted, OrderEventAny::Rejected(_)) => Self::Rejected,
219 (Self::Submitted, OrderEventAny::Canceled(_)) => Self::Canceled, (Self::Submitted, OrderEventAny::Accepted(_)) => Self::Accepted,
221 (Self::Submitted, OrderEventAny::Updated(_)) => Self::Submitted,
222 (Self::Submitted, OrderEventAny::Filled(_)) => Self::Filled,
223 (Self::Accepted, OrderEventAny::Rejected(_)) => Self::Rejected, (Self::Accepted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
225 (Self::Accepted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
226 (Self::Accepted, OrderEventAny::Canceled(_)) => Self::Canceled,
227 (Self::Accepted, OrderEventAny::Triggered(_)) => Self::Triggered,
228 (Self::Accepted, OrderEventAny::Updated(_)) => Self::Accepted, (Self::Accepted, OrderEventAny::Expired(_)) => Self::Expired,
230 (Self::Accepted, OrderEventAny::Filled(_)) => Self::Filled,
231 (Self::Canceled, OrderEventAny::Filled(_)) => Self::Filled, (Self::PendingUpdate, OrderEventAny::Rejected(_)) => Self::Rejected,
233 (Self::PendingUpdate, OrderEventAny::Accepted(_)) => Self::Accepted,
234 (Self::PendingUpdate, OrderEventAny::Canceled(_)) => Self::Canceled,
235 (Self::PendingUpdate, OrderEventAny::Expired(_)) => Self::Expired,
236 (Self::PendingUpdate, OrderEventAny::Triggered(_)) => Self::Triggered,
237 (Self::PendingUpdate, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
239 (Self::PendingUpdate, OrderEventAny::ModifyRejected(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::Filled(_)) => Self::Filled,
241 (Self::PendingCancel, OrderEventAny::Rejected(_)) => Self::Rejected,
242 (Self::PendingCancel, OrderEventAny::PendingCancel(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::CancelRejected(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::Canceled(_)) => Self::Canceled,
245 (Self::PendingCancel, OrderEventAny::Expired(_)) => Self::Expired,
246 (Self::PendingCancel, OrderEventAny::Accepted(_)) => Self::Accepted, (Self::PendingCancel, OrderEventAny::Filled(_)) => Self::Filled,
248 (Self::Triggered, OrderEventAny::Rejected(_)) => Self::Rejected,
249 (Self::Triggered, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
250 (Self::Triggered, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
251 (Self::Triggered, OrderEventAny::Canceled(_)) => Self::Canceled,
252 (Self::Triggered, OrderEventAny::Expired(_)) => Self::Expired,
253 (Self::Triggered, OrderEventAny::Filled(_)) => Self::Filled,
254 (Self::Triggered, OrderEventAny::Updated(_)) => Self::Triggered,
255 (Self::PartiallyFilled, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
256 (Self::PartiallyFilled, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
257 (Self::PartiallyFilled, OrderEventAny::Canceled(_)) => Self::Canceled,
258 (Self::PartiallyFilled, OrderEventAny::Expired(_)) => Self::Expired,
259 (Self::PartiallyFilled, OrderEventAny::Filled(_)) => Self::Filled,
260 (Self::PartiallyFilled, OrderEventAny::Accepted(_)) => Self::Accepted,
261 (Self::PartiallyFilled, OrderEventAny::Updated(_)) => Self::PartiallyFilled,
262 _ => return Err(OrderError::InvalidStateTransition),
263 };
264 Ok(new_state)
265 }
266}
267
268#[enum_dispatch]
269pub trait Order: 'static + Send {
270 fn into_any(self) -> OrderAny;
271 fn status(&self) -> OrderStatus;
272 fn trader_id(&self) -> TraderId;
273 fn strategy_id(&self) -> StrategyId;
274 fn instrument_id(&self) -> InstrumentId;
275 fn symbol(&self) -> Symbol;
276 fn venue(&self) -> Venue;
277 fn client_order_id(&self) -> ClientOrderId;
278 fn venue_order_id(&self) -> Option<VenueOrderId>;
279 fn position_id(&self) -> Option<PositionId>;
280 fn account_id(&self) -> Option<AccountId>;
281 fn last_trade_id(&self) -> Option<TradeId>;
282 fn order_side(&self) -> OrderSide;
283 fn order_type(&self) -> OrderType;
284 fn quantity(&self) -> Quantity;
285 fn time_in_force(&self) -> TimeInForce;
286 fn expire_time(&self) -> Option<UnixNanos>;
287 fn price(&self) -> Option<Price>;
288 fn trigger_price(&self) -> Option<Price>;
289 fn activation_price(&self) -> Option<Price> {
290 None
291 }
292 fn trigger_type(&self) -> Option<TriggerType>;
293 fn liquidity_side(&self) -> Option<LiquiditySide>;
294 fn is_post_only(&self) -> bool;
295 fn is_reduce_only(&self) -> bool;
296 fn is_quote_quantity(&self) -> bool;
297 fn display_qty(&self) -> Option<Quantity>;
298 fn limit_offset(&self) -> Option<Decimal>;
299 fn trailing_offset(&self) -> Option<Decimal>;
300 fn trailing_offset_type(&self) -> Option<TrailingOffsetType>;
301 fn emulation_trigger(&self) -> Option<TriggerType>;
302 fn trigger_instrument_id(&self) -> Option<InstrumentId>;
303 fn contingency_type(&self) -> Option<ContingencyType>;
304 fn order_list_id(&self) -> Option<OrderListId>;
305 fn linked_order_ids(&self) -> Option<&[ClientOrderId]>;
306 fn parent_order_id(&self) -> Option<ClientOrderId>;
307 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId>;
308 fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>>;
309 fn exec_spawn_id(&self) -> Option<ClientOrderId>;
310 fn tags(&self) -> Option<&[Ustr]>;
311 fn filled_qty(&self) -> Quantity;
312 fn leaves_qty(&self) -> Quantity;
313 fn overfill_qty(&self) -> Quantity;
314
315 fn calculate_overfill(&self, fill_qty: Quantity) -> Quantity {
317 let potential_filled = self.filled_qty() + fill_qty;
318 potential_filled.saturating_sub(self.quantity())
319 }
320
321 fn avg_px(&self) -> Option<f64>;
322 fn slippage(&self) -> Option<f64>;
323 fn init_id(&self) -> UUID4;
324 fn ts_init(&self) -> UnixNanos;
325 fn ts_submitted(&self) -> Option<UnixNanos>;
326 fn ts_accepted(&self) -> Option<UnixNanos>;
327 fn ts_closed(&self) -> Option<UnixNanos>;
328 fn ts_last(&self) -> UnixNanos;
329
330 fn order_side_specified(&self) -> OrderSideSpecified {
331 self.order_side().as_specified()
332 }
333 fn commissions(&self) -> &IndexMap<Currency, Money>;
334
335 fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError>;
341 fn update(&mut self, event: &OrderUpdated);
342
343 fn events(&self) -> Vec<&OrderEventAny>;
344
345 fn last_event(&self) -> &OrderEventAny {
346 self.events().last().unwrap()
348 }
349
350 fn event_count(&self) -> usize {
351 self.events().len()
352 }
353
354 fn venue_order_ids(&self) -> Vec<&VenueOrderId>;
355
356 fn trade_ids(&self) -> Vec<&TradeId>;
357
358 fn has_price(&self) -> bool;
359
360 fn is_duplicate_fill(&self, fill: &OrderFilled) -> bool {
362 self.events().iter().any(|event| {
363 if let OrderEventAny::Filled(existing) = event {
364 existing.trade_id == fill.trade_id
365 && existing.order_side == fill.order_side
366 && existing.last_qty == fill.last_qty
367 && existing.last_px == fill.last_px
368 } else {
369 false
370 }
371 })
372 }
373
374 fn is_buy(&self) -> bool {
375 self.order_side() == OrderSide::Buy
376 }
377
378 fn is_sell(&self) -> bool {
379 self.order_side() == OrderSide::Sell
380 }
381
382 fn is_passive(&self) -> bool {
383 self.order_type() != OrderType::Market
384 }
385
386 fn is_aggressive(&self) -> bool {
387 self.order_type() == OrderType::Market
388 }
389
390 fn is_emulated(&self) -> bool {
391 self.status() == OrderStatus::Emulated
392 }
393
394 fn is_active_local(&self) -> bool {
395 matches!(
396 self.status(),
397 OrderStatus::Initialized | OrderStatus::Emulated | OrderStatus::Released
398 )
399 }
400
401 fn is_primary(&self) -> bool {
402 self.exec_algorithm_id().is_some()
403 && self
404 .exec_spawn_id()
405 .is_some_and(|spawn_id| self.client_order_id() == spawn_id)
406 }
407
408 fn is_secondary(&self) -> bool {
409 self.exec_algorithm_id().is_some()
410 && self
411 .exec_spawn_id()
412 .is_some_and(|spawn_id| self.client_order_id() != spawn_id)
413 }
414
415 fn is_contingency(&self) -> bool {
416 self.contingency_type().is_some()
417 }
418
419 fn is_parent_order(&self) -> bool {
420 match self.contingency_type() {
421 Some(c) => c == ContingencyType::Oto,
422 None => false,
423 }
424 }
425
426 fn is_child_order(&self) -> bool {
427 self.parent_order_id().is_some()
428 }
429
430 fn is_open(&self) -> bool {
431 if let Some(emulation_trigger) = self.emulation_trigger()
432 && emulation_trigger != TriggerType::NoTrigger
433 {
434 return false;
435 }
436
437 matches!(
438 self.status(),
439 OrderStatus::Accepted
440 | OrderStatus::Triggered
441 | OrderStatus::PendingCancel
442 | OrderStatus::PendingUpdate
443 | OrderStatus::PartiallyFilled
444 )
445 }
446
447 fn is_canceled(&self) -> bool {
448 self.status() == OrderStatus::Canceled
449 }
450
451 fn is_closed(&self) -> bool {
452 matches!(
453 self.status(),
454 OrderStatus::Denied
455 | OrderStatus::Rejected
456 | OrderStatus::Canceled
457 | OrderStatus::Expired
458 | OrderStatus::Filled
459 )
460 }
461
462 fn is_inflight(&self) -> bool {
463 if let Some(emulation_trigger) = self.emulation_trigger()
464 && emulation_trigger != TriggerType::NoTrigger
465 {
466 return false;
467 }
468
469 matches!(
470 self.status(),
471 OrderStatus::Submitted | OrderStatus::PendingCancel | OrderStatus::PendingUpdate
472 )
473 }
474
475 fn is_pending_update(&self) -> bool {
476 self.status() == OrderStatus::PendingUpdate
477 }
478
479 fn is_pending_cancel(&self) -> bool {
480 self.status() == OrderStatus::PendingCancel
481 }
482
483 fn is_spawned(&self) -> bool {
484 self.exec_spawn_id()
485 .is_some_and(|exec_spawn_id| exec_spawn_id != self.client_order_id())
486 }
487
488 fn to_own_book_order(&self) -> OwnBookOrder {
489 OwnBookOrder::new(
490 self.trader_id(),
491 self.client_order_id(),
492 self.venue_order_id(),
493 self.order_side().as_specified(),
494 self.price().expect("`OwnBookOrder` must have a price"), self.quantity(),
496 self.order_type(),
497 self.time_in_force(),
498 self.status(),
499 self.ts_last(),
500 self.ts_accepted().unwrap_or_default(),
501 self.ts_submitted().unwrap_or_default(),
502 self.ts_init(),
503 )
504 }
505
506 fn is_triggered(&self) -> Option<bool>; fn set_position_id(&mut self, position_id: Option<PositionId>);
508 fn set_quantity(&mut self, quantity: Quantity);
509 fn set_leaves_qty(&mut self, leaves_qty: Quantity);
510 fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>);
511 fn set_is_quote_quantity(&mut self, is_quote_quantity: bool);
512 fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide);
513 fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool;
514 fn previous_status(&self) -> Option<OrderStatus>;
515}
516
517impl<T> From<&T> for OrderInitialized
518where
519 T: Order,
520{
521 fn from(order: &T) -> Self {
522 Self {
523 trader_id: order.trader_id(),
524 strategy_id: order.strategy_id(),
525 instrument_id: order.instrument_id(),
526 client_order_id: order.client_order_id(),
527 order_side: order.order_side(),
528 order_type: order.order_type(),
529 quantity: order.quantity(),
530 price: order.price(),
531 trigger_price: order.trigger_price(),
532 trigger_type: order.trigger_type(),
533 time_in_force: order.time_in_force(),
534 expire_time: order.expire_time(),
535 post_only: order.is_post_only(),
536 reduce_only: order.is_reduce_only(),
537 quote_quantity: order.is_quote_quantity(),
538 display_qty: order.display_qty(),
539 limit_offset: order.limit_offset(),
540 trailing_offset: order.trailing_offset(),
541 trailing_offset_type: order.trailing_offset_type(),
542 emulation_trigger: order.emulation_trigger(),
543 trigger_instrument_id: order.trigger_instrument_id(),
544 contingency_type: order.contingency_type(),
545 order_list_id: order.order_list_id(),
546 linked_order_ids: order.linked_order_ids().map(|x| x.to_vec()),
547 parent_order_id: order.parent_order_id(),
548 exec_algorithm_id: order.exec_algorithm_id(),
549 exec_algorithm_params: order.exec_algorithm_params().map(|x| x.to_owned()),
550 exec_spawn_id: order.exec_spawn_id(),
551 tags: order.tags().map(|x| x.to_vec()),
552 event_id: order.init_id(),
553 ts_event: order.ts_init(),
554 ts_init: order.ts_init(),
555 reconciliation: false,
556 }
557 }
558}
559
560#[derive(Clone, Debug, Serialize, Deserialize)]
561pub struct OrderCore {
562 pub events: Vec<OrderEventAny>,
563 pub commissions: IndexMap<Currency, Money>,
564 pub venue_order_ids: Vec<VenueOrderId>,
565 pub trade_ids: Vec<TradeId>,
566 pub previous_status: Option<OrderStatus>,
567 pub status: OrderStatus,
568 pub trader_id: TraderId,
569 pub strategy_id: StrategyId,
570 pub instrument_id: InstrumentId,
571 pub client_order_id: ClientOrderId,
572 pub venue_order_id: Option<VenueOrderId>,
573 pub position_id: Option<PositionId>,
574 pub account_id: Option<AccountId>,
575 pub last_trade_id: Option<TradeId>,
576 pub side: OrderSide,
577 pub order_type: OrderType,
578 pub quantity: Quantity,
579 pub time_in_force: TimeInForce,
580 pub liquidity_side: Option<LiquiditySide>,
581 pub is_reduce_only: bool,
582 pub is_quote_quantity: bool,
583 pub emulation_trigger: Option<TriggerType>,
584 pub contingency_type: Option<ContingencyType>,
585 pub order_list_id: Option<OrderListId>,
586 pub linked_order_ids: Option<Vec<ClientOrderId>>,
587 pub parent_order_id: Option<ClientOrderId>,
588 pub exec_algorithm_id: Option<ExecAlgorithmId>,
589 pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
590 pub exec_spawn_id: Option<ClientOrderId>,
591 pub tags: Option<Vec<Ustr>>,
592 pub filled_qty: Quantity,
593 pub leaves_qty: Quantity,
594 pub overfill_qty: Quantity,
595 pub avg_px: Option<f64>,
596 pub slippage: Option<f64>,
597 pub init_id: UUID4,
598 pub ts_init: UnixNanos,
599 pub ts_submitted: Option<UnixNanos>,
600 pub ts_accepted: Option<UnixNanos>,
601 pub ts_closed: Option<UnixNanos>,
602 pub ts_last: UnixNanos,
603}
604
605impl OrderCore {
606 pub fn new(init: OrderInitialized) -> Self {
608 let events: Vec<OrderEventAny> = vec![OrderEventAny::Initialized(init.clone())];
609 Self {
610 events,
611 commissions: IndexMap::new(),
612 venue_order_ids: Vec::new(),
613 trade_ids: Vec::new(),
614 previous_status: None,
615 status: OrderStatus::Initialized,
616 trader_id: init.trader_id,
617 strategy_id: init.strategy_id,
618 instrument_id: init.instrument_id,
619 client_order_id: init.client_order_id,
620 venue_order_id: None,
621 position_id: None,
622 account_id: None,
623 last_trade_id: None,
624 side: init.order_side,
625 order_type: init.order_type,
626 quantity: init.quantity,
627 time_in_force: init.time_in_force,
628 liquidity_side: Some(LiquiditySide::NoLiquiditySide),
629 is_reduce_only: init.reduce_only,
630 is_quote_quantity: init.quote_quantity,
631 emulation_trigger: init.emulation_trigger.or(Some(TriggerType::NoTrigger)),
632 contingency_type: init
633 .contingency_type
634 .or(Some(ContingencyType::NoContingency)),
635 order_list_id: init.order_list_id,
636 linked_order_ids: init.linked_order_ids,
637 parent_order_id: init.parent_order_id,
638 exec_algorithm_id: init.exec_algorithm_id,
639 exec_algorithm_params: init.exec_algorithm_params,
640 exec_spawn_id: init.exec_spawn_id,
641 tags: init.tags,
642 filled_qty: Quantity::zero(init.quantity.precision),
643 leaves_qty: init.quantity,
644 overfill_qty: Quantity::zero(init.quantity.precision),
645 avg_px: None,
646 slippage: None,
647 init_id: init.event_id,
648 ts_init: init.ts_event,
649 ts_submitted: None,
650 ts_accepted: None,
651 ts_closed: None,
652 ts_last: init.ts_event,
653 }
654 }
655
656 pub fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
663 if self.client_order_id != event.client_order_id() {
664 return Err(OrderError::Invariant(anyhow::anyhow!(
665 "Event client_order_id {} does not match order client_order_id {}",
666 event.client_order_id(),
667 self.client_order_id
668 )));
669 }
670 if self.strategy_id != event.strategy_id() {
671 return Err(OrderError::Invariant(anyhow::anyhow!(
672 "Event strategy_id {} does not match order strategy_id {}",
673 event.strategy_id(),
674 self.strategy_id
675 )));
676 }
677
678 if !matches!(
683 event,
684 OrderEventAny::Initialized(_)
685 | OrderEventAny::ModifyRejected(_)
686 | OrderEventAny::CancelRejected(_)
687 ) && !matches!(
688 self.status,
689 OrderStatus::PendingUpdate | OrderStatus::PendingCancel
690 ) {
691 self.previous_status = Some(self.status);
692 }
693
694 if let OrderEventAny::Filled(fill) = &event
696 && self.trade_ids.contains(&fill.trade_id)
697 {
698 return Err(OrderError::DuplicateFill(fill.trade_id));
699 }
700
701 let new_status = self.status.transition(&event)?;
702 self.status = new_status;
703
704 match &event {
705 OrderEventAny::Initialized(_) => return Err(OrderError::AlreadyInitialized),
706 OrderEventAny::Denied(event) => self.denied(event),
707 OrderEventAny::Emulated(event) => self.emulated(event),
708 OrderEventAny::Released(event) => self.released(event),
709 OrderEventAny::Submitted(event) => self.submitted(event),
710 OrderEventAny::Rejected(event) => self.rejected(event),
711 OrderEventAny::Accepted(event) => self.accepted(event),
712 OrderEventAny::PendingUpdate(event) => self.pending_update(event),
713 OrderEventAny::PendingCancel(event) => self.pending_cancel(event),
714 OrderEventAny::ModifyRejected(event) => self.modify_rejected(event),
715 OrderEventAny::CancelRejected(event) => self.cancel_rejected(event),
716 OrderEventAny::Updated(event) => self.updated(event),
717 OrderEventAny::Triggered(event) => self.triggered(event),
718 OrderEventAny::Canceled(event) => self.canceled(event),
719 OrderEventAny::Expired(event) => self.expired(event),
720 OrderEventAny::Filled(event) => self.filled(event),
721 }
722
723 self.ts_last = event.ts_event();
724 self.events.push(event);
725 Ok(())
726 }
727
728 fn denied(&mut self, event: &OrderDenied) {
729 self.ts_closed = Some(event.ts_event);
730 }
731
732 fn emulated(&self, _event: &OrderEmulated) {
733 }
735
736 fn released(&mut self, _event: &OrderReleased) {
737 self.emulation_trigger = None;
738 }
739
740 fn submitted(&mut self, event: &OrderSubmitted) {
741 self.account_id = Some(event.account_id);
742 self.ts_submitted = Some(event.ts_event);
743 }
744
745 fn accepted(&mut self, event: &OrderAccepted) {
746 self.account_id = Some(event.account_id);
747 self.venue_order_id = Some(event.venue_order_id);
748 self.venue_order_ids.push(event.venue_order_id);
749 self.ts_accepted = Some(event.ts_event);
750 }
751
752 fn rejected(&mut self, event: &OrderRejected) {
753 self.ts_closed = Some(event.ts_event);
754 }
755
756 fn pending_update(&self, _event: &OrderPendingUpdate) {
757 }
759
760 fn pending_cancel(&self, _event: &OrderPendingCancel) {
761 }
763
764 fn modify_rejected(&mut self, _event: &OrderModifyRejected) {
765 self.status = self
766 .previous_status
767 .unwrap_or_else(|| panic!("{}", OrderError::NoPreviousState));
768 }
769
770 fn cancel_rejected(&mut self, _event: &OrderCancelRejected) {
771 self.status = self
772 .previous_status
773 .unwrap_or_else(|| panic!("{}", OrderError::NoPreviousState));
774 }
775
776 fn triggered(&mut self, _event: &OrderTriggered) {}
777
778 fn canceled(&mut self, event: &OrderCanceled) {
779 self.ts_closed = Some(event.ts_event);
780 }
781
782 fn expired(&mut self, event: &OrderExpired) {
783 self.ts_closed = Some(event.ts_event);
784 }
785
786 fn updated(&mut self, event: &OrderUpdated) {
787 if let Some(venue_order_id) = &event.venue_order_id
788 && (self.venue_order_id.is_none()
789 || venue_order_id != self.venue_order_id.as_ref().unwrap())
790 {
791 self.venue_order_id = Some(*venue_order_id);
792 self.venue_order_ids.push(*venue_order_id);
793 }
794 }
795
796 fn filled(&mut self, event: &OrderFilled) {
797 let new_filled_qty = Quantity::from_raw(
799 self.filled_qty.raw.saturating_add(event.last_qty.raw),
800 self.filled_qty.precision,
801 );
802
803 if new_filled_qty > self.quantity {
805 let overfill_raw = new_filled_qty.raw - self.quantity.raw;
806 self.overfill_qty = Quantity::from_raw(
807 self.overfill_qty.raw.saturating_add(overfill_raw),
808 self.filled_qty.precision,
809 );
810 }
811
812 if new_filled_qty < self.quantity {
813 self.status = OrderStatus::PartiallyFilled;
814 } else {
815 self.status = OrderStatus::Filled;
816 self.ts_closed = Some(event.ts_event);
817 }
818
819 self.venue_order_id = Some(event.venue_order_id);
820 self.position_id = event.position_id;
821 self.trade_ids.push(event.trade_id);
822 self.last_trade_id = Some(event.trade_id);
823 self.liquidity_side = Some(event.liquidity_side);
824 self.filled_qty = new_filled_qty;
825 self.leaves_qty = self.leaves_qty.saturating_sub(event.last_qty);
826 self.ts_last = event.ts_event;
827 if self.ts_accepted.is_none() {
828 self.ts_accepted = Some(event.ts_event);
830 }
831
832 self.set_avg_px(event.last_qty, event.last_px);
833 }
834
835 fn set_avg_px(&mut self, last_qty: Quantity, last_px: Price) {
836 if self.avg_px.is_none() {
837 self.avg_px = Some(last_px.as_f64());
838 return;
839 }
840
841 let prev_filled_qty = (self.filled_qty - last_qty).as_f64();
843 let last_qty_f64 = last_qty.as_f64();
844 let total_qty = prev_filled_qty + last_qty_f64;
845
846 let avg_px = self
847 .avg_px
848 .unwrap()
849 .mul_add(prev_filled_qty, last_px.as_f64() * last_qty_f64)
850 / total_qty;
851 self.avg_px = Some(avg_px);
852 }
853
854 pub fn set_slippage(&mut self, price: Price) {
855 self.slippage = self.avg_px.and_then(|avg_px| {
856 let current_price = price.as_f64();
857 match self.side {
858 OrderSide::Buy if avg_px > current_price => Some(avg_px - current_price),
859 OrderSide::Sell if avg_px < current_price => Some(current_price - avg_px),
860 _ => None,
861 }
862 });
863 }
864
865 #[must_use]
867 pub fn opposite_side(side: OrderSide) -> OrderSide {
868 match side {
869 OrderSide::Buy => OrderSide::Sell,
870 OrderSide::Sell => OrderSide::Buy,
871 OrderSide::NoOrderSide => OrderSide::NoOrderSide,
872 }
873 }
874
875 #[must_use]
877 pub fn closing_side(side: PositionSide) -> OrderSide {
878 match side {
879 PositionSide::Long => OrderSide::Sell,
880 PositionSide::Short => OrderSide::Buy,
881 PositionSide::Flat => OrderSide::NoOrderSide,
882 PositionSide::NoPositionSide => OrderSide::NoOrderSide,
883 }
884 }
885
886 #[must_use]
890 pub fn signed_decimal_qty(&self) -> Decimal {
891 match self.side {
892 OrderSide::Buy => self.quantity.as_decimal(),
893 OrderSide::Sell => -self.quantity.as_decimal(),
894 _ => panic!("Invalid order side"),
895 }
896 }
897
898 #[must_use]
899 pub fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
900 if side == PositionSide::Flat {
901 return false;
902 }
903
904 match (self.side, side) {
905 (OrderSide::Buy, PositionSide::Long) => false,
906 (OrderSide::Buy, PositionSide::Short) => self.leaves_qty <= position_qty,
907 (OrderSide::Sell, PositionSide::Short) => false,
908 (OrderSide::Sell, PositionSide::Long) => self.leaves_qty <= position_qty,
909 _ => true,
910 }
911 }
912
913 #[must_use]
914 pub fn commission(&self, currency: &Currency) -> Option<Money> {
915 self.commissions.get(currency).copied()
916 }
917
918 #[must_use]
919 pub fn commissions(&self) -> IndexMap<Currency, Money> {
920 self.commissions.clone()
921 }
922
923 #[must_use]
924 pub fn commissions_vec(&self) -> Vec<Money> {
925 self.commissions.values().copied().collect()
926 }
927
928 #[must_use]
929 pub fn init_event(&self) -> Option<OrderEventAny> {
930 self.events.first().cloned()
931 }
932}
933
934#[cfg(test)]
935mod tests {
936 use rstest::rstest;
937 use rust_decimal_macros::dec;
938
939 use super::*;
940 use crate::{
941 enums::{OrderSide, OrderStatus, PositionSide},
942 events::order::{
943 accepted::OrderAcceptedBuilder, canceled::OrderCanceledBuilder,
944 denied::OrderDeniedBuilder, filled::OrderFilledBuilder,
945 initialized::OrderInitializedBuilder, submitted::OrderSubmittedBuilder,
946 triggered::OrderTriggeredBuilder, updated::OrderUpdatedBuilder,
947 },
948 orders::MarketOrder,
949 };
950
951 #[rstest]
962 #[case(OrderSide::Buy, OrderSide::Sell)]
963 #[case(OrderSide::Sell, OrderSide::Buy)]
964 #[case(OrderSide::NoOrderSide, OrderSide::NoOrderSide)]
965 fn test_order_opposite_side(#[case] order_side: OrderSide, #[case] expected_side: OrderSide) {
966 let result = OrderCore::opposite_side(order_side);
967 assert_eq!(result, expected_side);
968 }
969
970 #[rstest]
971 #[case(PositionSide::Long, OrderSide::Sell)]
972 #[case(PositionSide::Short, OrderSide::Buy)]
973 #[case(PositionSide::NoPositionSide, OrderSide::NoOrderSide)]
974 fn test_closing_side(#[case] position_side: PositionSide, #[case] expected_side: OrderSide) {
975 let result = OrderCore::closing_side(position_side);
976 assert_eq!(result, expected_side);
977 }
978
979 #[rstest]
980 #[case(OrderSide::Buy, dec!(10_000))]
981 #[case(OrderSide::Sell, dec!(-10_000))]
982 fn test_signed_decimal_qty(#[case] order_side: OrderSide, #[case] expected: Decimal) {
983 let order: MarketOrder = OrderInitializedBuilder::default()
984 .order_side(order_side)
985 .quantity(Quantity::from(10_000))
986 .build()
987 .unwrap()
988 .into();
989
990 let result = order.signed_decimal_qty();
991 assert_eq!(result, expected);
992 }
993
994 #[rustfmt::skip]
995 #[rstest]
996 #[case(OrderSide::Buy, Quantity::from(100), PositionSide::Long, Quantity::from(50), false)]
997 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(50), true)]
998 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(100), true)]
999 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1000 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
1001 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(50), true)]
1002 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(100), true)]
1003 #[case(OrderSide::Sell, Quantity::from(100), PositionSide::Short, Quantity::from(50), false)]
1004 fn test_would_reduce_only(
1005 #[case] order_side: OrderSide,
1006 #[case] order_qty: Quantity,
1007 #[case] position_side: PositionSide,
1008 #[case] position_qty: Quantity,
1009 #[case] expected: bool,
1010 ) {
1011 let order: MarketOrder = OrderInitializedBuilder::default()
1012 .order_side(order_side)
1013 .quantity(order_qty)
1014 .build()
1015 .unwrap()
1016 .into();
1017
1018 assert_eq!(
1019 order.would_reduce_only(position_side, position_qty),
1020 expected
1021 );
1022 }
1023
1024 #[rstest]
1025 fn test_order_state_transition_denied() {
1026 let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
1027 let denied = OrderDeniedBuilder::default().build().unwrap();
1028 let event = OrderEventAny::Denied(denied);
1029
1030 order.apply(event.clone()).unwrap();
1031
1032 assert_eq!(order.status, OrderStatus::Denied);
1033 assert!(order.is_closed());
1034 assert!(!order.is_open());
1035 assert_eq!(order.event_count(), 2);
1036 assert_eq!(order.last_event(), &event);
1037 }
1038
1039 #[rstest]
1040 fn test_order_life_cycle_to_filled() {
1041 let init = OrderInitializedBuilder::default().build().unwrap();
1042 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1043 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1044 let filled = OrderFilledBuilder::default().build().unwrap();
1045
1046 let mut order: MarketOrder = init.clone().into();
1047 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1048 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1049 order.apply(OrderEventAny::Filled(filled)).unwrap();
1050
1051 assert_eq!(order.client_order_id, init.client_order_id);
1052 assert_eq!(order.status(), OrderStatus::Filled);
1053 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1054 assert_eq!(order.leaves_qty(), Quantity::from(0));
1055 assert_eq!(order.avg_px(), Some(1.0));
1056 assert!(!order.is_open());
1057 assert!(order.is_closed());
1058 assert_eq!(order.commission(&Currency::USD()), None);
1059 assert_eq!(order.commissions(), &IndexMap::new());
1060 }
1061
1062 #[rstest]
1063 fn test_order_state_transition_to_canceled() {
1064 let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
1065 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1066 let canceled = OrderCanceledBuilder::default().build().unwrap();
1067
1068 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1069 order.apply(OrderEventAny::Canceled(canceled)).unwrap();
1070
1071 assert_eq!(order.status(), OrderStatus::Canceled);
1072 assert!(order.is_closed());
1073 assert!(!order.is_open());
1074 }
1075
1076 #[rstest]
1077 fn test_order_life_cycle_to_partially_filled() {
1078 let init = OrderInitializedBuilder::default().build().unwrap();
1079 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1080 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1081 let filled = OrderFilledBuilder::default()
1082 .last_qty(Quantity::from(50_000))
1083 .build()
1084 .unwrap();
1085
1086 let mut order: MarketOrder = init.clone().into();
1087 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1088 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1089 order.apply(OrderEventAny::Filled(filled)).unwrap();
1090
1091 assert_eq!(order.client_order_id, init.client_order_id);
1092 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1093 assert_eq!(order.filled_qty(), Quantity::from(50_000));
1094 assert_eq!(order.leaves_qty(), Quantity::from(50_000));
1095 assert!(order.is_open());
1096 assert!(!order.is_closed());
1097 }
1098
1099 #[rstest]
1100 fn test_order_commission_calculation() {
1101 let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
1102 order
1103 .commissions
1104 .insert(Currency::USD(), Money::new(10.0, Currency::USD()));
1105
1106 assert_eq!(
1107 order.commission(&Currency::USD()),
1108 Some(Money::new(10.0, Currency::USD()))
1109 );
1110 assert_eq!(
1111 order.commissions_vec(),
1112 vec![Money::new(10.0, Currency::USD())]
1113 );
1114 }
1115
1116 #[rstest]
1117 fn test_order_is_primary() {
1118 let order: MarketOrder = OrderInitializedBuilder::default()
1119 .exec_algorithm_id(Some(ExecAlgorithmId::from("ALGO-001")))
1120 .exec_spawn_id(Some(ClientOrderId::from("O-001")))
1121 .client_order_id(ClientOrderId::from("O-001"))
1122 .build()
1123 .unwrap()
1124 .into();
1125
1126 assert!(order.is_primary());
1127 assert!(!order.is_secondary());
1128 }
1129
1130 #[rstest]
1131 fn test_order_is_secondary() {
1132 let order: MarketOrder = OrderInitializedBuilder::default()
1133 .exec_algorithm_id(Some(ExecAlgorithmId::from("ALGO-001")))
1134 .exec_spawn_id(Some(ClientOrderId::from("O-002")))
1135 .client_order_id(ClientOrderId::from("O-001"))
1136 .build()
1137 .unwrap()
1138 .into();
1139
1140 assert!(!order.is_primary());
1141 assert!(order.is_secondary());
1142 }
1143
1144 #[rstest]
1145 fn test_order_is_contingency() {
1146 let order: MarketOrder = OrderInitializedBuilder::default()
1147 .contingency_type(Some(ContingencyType::Oto))
1148 .build()
1149 .unwrap()
1150 .into();
1151
1152 assert!(order.is_contingency());
1153 assert!(order.is_parent_order());
1154 assert!(!order.is_child_order());
1155 }
1156
1157 #[rstest]
1158 fn test_order_is_child_order() {
1159 let order: MarketOrder = OrderInitializedBuilder::default()
1160 .parent_order_id(Some(ClientOrderId::from("PARENT-001")))
1161 .build()
1162 .unwrap()
1163 .into();
1164
1165 assert!(order.is_child_order());
1166 assert!(!order.is_parent_order());
1167 }
1168
1169 #[rstest]
1170 fn test_to_own_book_order_timestamp_ordering() {
1171 use crate::orders::limit::LimitOrder;
1172
1173 let init = OrderInitializedBuilder::default()
1175 .price(Some(Price::from("100.00")))
1176 .build()
1177 .unwrap();
1178 let submitted = OrderSubmittedBuilder::default()
1179 .ts_event(UnixNanos::from(1_000_000))
1180 .build()
1181 .unwrap();
1182 let accepted = OrderAcceptedBuilder::default()
1183 .ts_event(UnixNanos::from(2_000_000))
1184 .build()
1185 .unwrap();
1186
1187 let mut order: LimitOrder = init.into();
1188 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1189 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1190
1191 let own_book_order = order.to_own_book_order();
1192
1193 assert_eq!(own_book_order.ts_submitted, UnixNanos::from(1_000_000));
1195 assert_eq!(own_book_order.ts_accepted, UnixNanos::from(2_000_000));
1196 assert_eq!(own_book_order.ts_last, UnixNanos::from(2_000_000));
1197 }
1198
1199 #[rstest]
1200 fn test_order_accepted_without_submitted_sets_account_id() {
1201 let init = OrderInitializedBuilder::default().build().unwrap();
1203 let accepted = OrderAcceptedBuilder::default()
1204 .account_id(AccountId::from("EXTERNAL-001"))
1205 .build()
1206 .unwrap();
1207
1208 let mut order: MarketOrder = init.into();
1209
1210 assert_eq!(order.account_id(), None);
1212
1213 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1215
1216 assert_eq!(order.account_id(), Some(AccountId::from("EXTERNAL-001")));
1218 assert_eq!(order.status(), OrderStatus::Accepted);
1219 }
1220
1221 #[rstest]
1222 fn test_order_accepted_after_submitted_preserves_account_id() {
1223 let init = OrderInitializedBuilder::default().build().unwrap();
1225 let submitted = OrderSubmittedBuilder::default()
1226 .account_id(AccountId::from("SUBMITTED-001"))
1227 .build()
1228 .unwrap();
1229 let accepted = OrderAcceptedBuilder::default()
1230 .account_id(AccountId::from("ACCEPTED-001"))
1231 .build()
1232 .unwrap();
1233
1234 let mut order: MarketOrder = init.into();
1235 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1236
1237 assert_eq!(order.account_id(), Some(AccountId::from("SUBMITTED-001")));
1239
1240 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1242
1243 assert_eq!(order.account_id(), Some(AccountId::from("ACCEPTED-001")));
1245 assert_eq!(order.status(), OrderStatus::Accepted);
1246 }
1247
1248 #[rstest]
1249 fn test_overfill_tracks_overfill_qty() {
1250 let init = OrderInitializedBuilder::default()
1252 .quantity(Quantity::from(100_000))
1253 .build()
1254 .unwrap();
1255 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1256 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1257 let overfill = OrderFilledBuilder::default()
1258 .last_qty(Quantity::from(110_000)) .build()
1260 .unwrap();
1261
1262 let mut order: MarketOrder = init.into();
1263 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1264 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1265 order.apply(OrderEventAny::Filled(overfill)).unwrap();
1266
1267 assert_eq!(order.overfill_qty(), Quantity::from(10_000));
1269 assert_eq!(order.filled_qty(), Quantity::from(110_000));
1270 assert_eq!(order.leaves_qty(), Quantity::from(0));
1271 assert_eq!(order.status(), OrderStatus::Filled);
1272 }
1273
1274 #[rstest]
1275 fn test_partial_fill_then_overfill() {
1276 let init = OrderInitializedBuilder::default()
1278 .quantity(Quantity::from(100_000))
1279 .build()
1280 .unwrap();
1281 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1282 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1283 let fill1 = OrderFilledBuilder::default()
1284 .last_qty(Quantity::from(80_000))
1285 .trade_id(TradeId::from("TRADE-1"))
1286 .build()
1287 .unwrap();
1288 let fill2 = OrderFilledBuilder::default()
1289 .last_qty(Quantity::from(30_000)) .trade_id(TradeId::from("TRADE-2"))
1291 .build()
1292 .unwrap();
1293
1294 let mut order: MarketOrder = init.into();
1295 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1296 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1297 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1298
1299 assert_eq!(order.overfill_qty(), Quantity::from(0));
1301 assert_eq!(order.filled_qty(), Quantity::from(80_000));
1302 assert_eq!(order.leaves_qty(), Quantity::from(20_000));
1303
1304 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1305
1306 assert_eq!(order.overfill_qty(), Quantity::from(10_000));
1308 assert_eq!(order.filled_qty(), Quantity::from(110_000));
1309 assert_eq!(order.leaves_qty(), Quantity::from(0));
1310 assert_eq!(order.status(), OrderStatus::Filled);
1311 }
1312
1313 #[rstest]
1314 fn test_exact_fill_no_overfill() {
1315 let init = OrderInitializedBuilder::default()
1317 .quantity(Quantity::from(100_000))
1318 .build()
1319 .unwrap();
1320 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1321 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1322 let filled = OrderFilledBuilder::default()
1323 .last_qty(Quantity::from(100_000)) .build()
1325 .unwrap();
1326
1327 let mut order: MarketOrder = init.into();
1328 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1329 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1330 order.apply(OrderEventAny::Filled(filled)).unwrap();
1331
1332 assert_eq!(order.overfill_qty(), Quantity::from(0));
1334 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1335 assert_eq!(order.leaves_qty(), Quantity::from(0));
1336 }
1337
1338 #[rstest]
1339 fn test_partial_fill_then_overfill_with_fractional_quantities() {
1340 let init = OrderInitializedBuilder::default()
1344 .quantity(Quantity::from("2450.5"))
1345 .build()
1346 .unwrap();
1347 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1348 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1349 let fill1 = OrderFilledBuilder::default()
1350 .last_qty(Quantity::from("1202.5"))
1351 .trade_id(TradeId::from("TRADE-1"))
1352 .build()
1353 .unwrap();
1354 let fill2 = OrderFilledBuilder::default()
1355 .last_qty(Quantity::from("1285.5")) .trade_id(TradeId::from("TRADE-2"))
1357 .build()
1358 .unwrap();
1359
1360 let mut order: MarketOrder = init.into();
1361 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1362 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1363 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1364
1365 assert_eq!(order.overfill_qty(), Quantity::from(0));
1367 assert_eq!(order.filled_qty(), Quantity::from("1202.5"));
1368 assert_eq!(order.leaves_qty(), Quantity::from("1248.0"));
1369 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1370
1371 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1372
1373 assert_eq!(order.overfill_qty(), Quantity::from("37.5"));
1375 assert_eq!(order.filled_qty(), Quantity::from("2488.0"));
1376 assert_eq!(order.leaves_qty(), Quantity::from(0));
1377 assert_eq!(order.status(), OrderStatus::Filled);
1378 }
1379
1380 #[rstest]
1381 fn test_calculate_overfill_returns_zero_when_no_overfill() {
1382 let order: MarketOrder = OrderInitializedBuilder::default()
1383 .quantity(Quantity::from(100_000))
1384 .build()
1385 .unwrap()
1386 .into();
1387
1388 let overfill = order.calculate_overfill(Quantity::from(50_000));
1390 assert_eq!(overfill, Quantity::from(0));
1391
1392 let overfill = order.calculate_overfill(Quantity::from(100_000));
1394 assert_eq!(overfill, Quantity::from(0));
1395 }
1396
1397 #[rstest]
1398 fn test_calculate_overfill_returns_overfill_amount() {
1399 let order: MarketOrder = OrderInitializedBuilder::default()
1400 .quantity(Quantity::from(100_000))
1401 .build()
1402 .unwrap()
1403 .into();
1404
1405 let overfill = order.calculate_overfill(Quantity::from(110_000));
1407 assert_eq!(overfill, Quantity::from(10_000));
1408 }
1409
1410 #[rstest]
1411 fn test_calculate_overfill_accounts_for_existing_fills() {
1412 let init = OrderInitializedBuilder::default()
1413 .quantity(Quantity::from(100_000))
1414 .build()
1415 .unwrap();
1416 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1417 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1418 let partial_fill = OrderFilledBuilder::default()
1419 .last_qty(Quantity::from(60_000))
1420 .build()
1421 .unwrap();
1422
1423 let mut order: MarketOrder = init.into();
1424 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1425 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1426 order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1427
1428 let overfill = order.calculate_overfill(Quantity::from(50_000));
1431 assert_eq!(overfill, Quantity::from(10_000));
1432
1433 let overfill = order.calculate_overfill(Quantity::from(40_000));
1435 assert_eq!(overfill, Quantity::from(0));
1436 }
1437
1438 #[rstest]
1439 fn test_calculate_overfill_with_fractional_quantities() {
1440 let order: MarketOrder = OrderInitializedBuilder::default()
1441 .quantity(Quantity::from("2450.5"))
1442 .build()
1443 .unwrap()
1444 .into();
1445
1446 let overfill = order.calculate_overfill(Quantity::from("2488.0"));
1449 assert_eq!(overfill, Quantity::from("37.5"));
1450 }
1451
1452 #[rstest]
1453 fn test_duplicate_fill_rejected() {
1454 let init = OrderInitializedBuilder::default()
1455 .quantity(Quantity::from(100_000))
1456 .build()
1457 .unwrap();
1458 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1459 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1460 let fill1 = OrderFilledBuilder::default()
1461 .last_qty(Quantity::from(50_000))
1462 .trade_id(TradeId::from("TRADE-001"))
1463 .build()
1464 .unwrap();
1465 let fill2_duplicate = OrderFilledBuilder::default()
1466 .last_qty(Quantity::from(50_000))
1467 .trade_id(TradeId::from("TRADE-001")) .build()
1469 .unwrap();
1470
1471 let mut order: MarketOrder = init.into();
1472 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1473 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1474 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1475
1476 assert_eq!(order.filled_qty(), Quantity::from(50_000));
1478 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1479
1480 let result = order.apply(OrderEventAny::Filled(fill2_duplicate));
1482 assert!(result.is_err());
1483 match result.unwrap_err() {
1484 OrderError::DuplicateFill(trade_id) => {
1485 assert_eq!(trade_id, TradeId::from("TRADE-001"));
1486 }
1487 e => panic!("Expected DuplicateFill error, got: {e:?}"),
1488 }
1489
1490 assert_eq!(order.filled_qty(), Quantity::from(50_000));
1492 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1493 }
1494
1495 #[rstest]
1496 fn test_different_trade_ids_allowed() {
1497 let init = OrderInitializedBuilder::default()
1498 .quantity(Quantity::from(100_000))
1499 .build()
1500 .unwrap();
1501 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1502 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1503 let fill1 = OrderFilledBuilder::default()
1504 .last_qty(Quantity::from(50_000))
1505 .trade_id(TradeId::from("TRADE-001"))
1506 .build()
1507 .unwrap();
1508 let fill2 = OrderFilledBuilder::default()
1509 .last_qty(Quantity::from(50_000))
1510 .trade_id(TradeId::from("TRADE-002")) .build()
1512 .unwrap();
1513
1514 let mut order: MarketOrder = init.into();
1515 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1516 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1517 order.apply(OrderEventAny::Filled(fill1)).unwrap();
1518 order.apply(OrderEventAny::Filled(fill2)).unwrap();
1519
1520 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1522 assert_eq!(order.status(), OrderStatus::Filled);
1523 assert_eq!(order.trade_ids.len(), 2);
1524 }
1525
1526 #[rstest]
1527 fn test_partially_filled_order_can_be_updated() {
1528 let init = OrderInitializedBuilder::default()
1531 .quantity(Quantity::from(100_000))
1532 .build()
1533 .unwrap();
1534 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1535 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1536 let partial_fill = OrderFilledBuilder::default()
1537 .last_qty(Quantity::from(40_000))
1538 .build()
1539 .unwrap();
1540 let updated = OrderUpdatedBuilder::default()
1541 .quantity(Quantity::from(80_000)) .build()
1543 .unwrap();
1544
1545 let mut order: MarketOrder = init.into();
1546 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1547 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1548 order.apply(OrderEventAny::Filled(partial_fill)).unwrap();
1549
1550 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1551 assert_eq!(order.filled_qty(), Quantity::from(40_000));
1552
1553 order.apply(OrderEventAny::Updated(updated)).unwrap();
1554
1555 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1556 assert_eq!(order.quantity(), Quantity::from(80_000));
1557 assert_eq!(order.leaves_qty(), Quantity::from(40_000)); }
1559
1560 #[rstest]
1561 fn test_triggered_order_can_be_updated() {
1562 let init = OrderInitializedBuilder::default()
1565 .quantity(Quantity::from(100_000))
1566 .build()
1567 .unwrap();
1568 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1569 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1570 let triggered = OrderTriggeredBuilder::default().build().unwrap();
1571 let updated = OrderUpdatedBuilder::default()
1572 .quantity(Quantity::from(80_000))
1573 .build()
1574 .unwrap();
1575
1576 let mut order: MarketOrder = init.into();
1577 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1578 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1579 order.apply(OrderEventAny::Triggered(triggered)).unwrap();
1580
1581 assert_eq!(order.status(), OrderStatus::Triggered);
1582
1583 order.apply(OrderEventAny::Updated(updated)).unwrap();
1584
1585 assert_eq!(order.status(), OrderStatus::Triggered);
1586 assert_eq!(order.quantity(), Quantity::from(80_000));
1587 }
1588}