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