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("{0}")]
143 Invariant(#[from] anyhow::Error),
144}
145
146#[must_use]
148pub fn ustr_indexmap_to_str(h: IndexMap<Ustr, Ustr>) -> IndexMap<String, String> {
149 h.into_iter()
150 .map(|(k, v)| (k.to_string(), v.to_string()))
151 .collect()
152}
153
154#[must_use]
156pub fn str_indexmap_to_ustr(h: IndexMap<String, String>) -> IndexMap<Ustr, Ustr> {
157 h.into_iter()
158 .map(|(k, v)| (Ustr::from(&k), Ustr::from(&v)))
159 .collect()
160}
161
162#[inline]
163pub(crate) fn check_display_qty(
164 display_qty: Option<Quantity>,
165 quantity: Quantity,
166) -> Result<(), OrderError> {
167 if let Some(q) = display_qty
168 && q > quantity
169 {
170 return Err(OrderError::Invariant(anyhow::anyhow!(
171 "`display_qty` may not exceed `quantity`"
172 )));
173 }
174 Ok(())
175}
176
177#[inline]
178pub(crate) fn check_time_in_force(
179 time_in_force: TimeInForce,
180 expire_time: Option<UnixNanos>,
181) -> Result<(), OrderError> {
182 if time_in_force == TimeInForce::Gtd && expire_time.unwrap_or_default() == 0 {
183 return Err(OrderError::Invariant(anyhow::anyhow!(
184 "`expire_time` is required for `GTD` order"
185 )));
186 }
187 Ok(())
188}
189
190impl OrderStatus {
191 #[rustfmt::skip]
197 pub fn transition(&mut self, event: &OrderEventAny) -> Result<Self, OrderError> {
198 let new_state = match (self, event) {
199 (Self::Initialized, OrderEventAny::Denied(_)) => Self::Denied,
200 (Self::Initialized, OrderEventAny::Emulated(_)) => Self::Emulated, (Self::Initialized, OrderEventAny::Released(_)) => Self::Released, (Self::Initialized, OrderEventAny::Submitted(_)) => Self::Submitted,
203 (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,
215 (Self::Submitted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
216 (Self::Submitted, OrderEventAny::Rejected(_)) => Self::Rejected,
217 (Self::Submitted, OrderEventAny::Canceled(_)) => Self::Canceled, (Self::Submitted, OrderEventAny::Accepted(_)) => Self::Accepted,
219 (Self::Submitted, OrderEventAny::Filled(_)) => Self::Filled,
220 (Self::Submitted, OrderEventAny::Updated(_)) => Self::Submitted,
221 (Self::Accepted, OrderEventAny::Rejected(_)) => Self::Rejected, (Self::Accepted, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
223 (Self::Accepted, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
224 (Self::Accepted, OrderEventAny::Canceled(_)) => Self::Canceled,
225 (Self::Accepted, OrderEventAny::Triggered(_)) => Self::Triggered,
226 (Self::Accepted, OrderEventAny::Expired(_)) => Self::Expired,
227 (Self::Accepted, OrderEventAny::Filled(_)) => Self::Filled,
228 (Self::Accepted, OrderEventAny::Updated(_)) => Self::Accepted, (Self::Canceled, OrderEventAny::Filled(_)) => Self::Filled, (Self::PendingUpdate, OrderEventAny::Rejected(_)) => Self::Rejected,
231 (Self::PendingUpdate, OrderEventAny::Accepted(_)) => Self::Accepted,
232 (Self::PendingUpdate, OrderEventAny::Canceled(_)) => Self::Canceled,
233 (Self::PendingUpdate, OrderEventAny::Expired(_)) => Self::Expired,
234 (Self::PendingUpdate, OrderEventAny::Triggered(_)) => Self::Triggered,
235 (Self::PendingUpdate, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
237 (Self::PendingUpdate, OrderEventAny::ModifyRejected(_)) => Self::PendingUpdate, (Self::PendingUpdate, OrderEventAny::Filled(_)) => Self::Filled,
239 (Self::PendingCancel, OrderEventAny::Rejected(_)) => Self::Rejected,
240 (Self::PendingCancel, OrderEventAny::PendingCancel(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::CancelRejected(_)) => Self::PendingCancel, (Self::PendingCancel, OrderEventAny::Canceled(_)) => Self::Canceled,
243 (Self::PendingCancel, OrderEventAny::Expired(_)) => Self::Expired,
244 (Self::PendingCancel, OrderEventAny::Accepted(_)) => Self::Accepted, (Self::PendingCancel, OrderEventAny::Filled(_)) => Self::Filled,
246 (Self::Triggered, OrderEventAny::Rejected(_)) => Self::Rejected,
247 (Self::Triggered, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
248 (Self::Triggered, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
249 (Self::Triggered, OrderEventAny::Canceled(_)) => Self::Canceled,
250 (Self::Triggered, OrderEventAny::Expired(_)) => Self::Expired,
251 (Self::Triggered, OrderEventAny::Filled(_)) => Self::Filled,
252 (Self::PartiallyFilled, OrderEventAny::PendingUpdate(_)) => Self::PendingUpdate,
253 (Self::PartiallyFilled, OrderEventAny::PendingCancel(_)) => Self::PendingCancel,
254 (Self::PartiallyFilled, OrderEventAny::Canceled(_)) => Self::Canceled,
255 (Self::PartiallyFilled, OrderEventAny::Expired(_)) => Self::Expired,
256 (Self::PartiallyFilled, OrderEventAny::Filled(_)) => Self::Filled,
257 (Self::PartiallyFilled, OrderEventAny::Accepted(_)) => Self::Accepted,
258 _ => return Err(OrderError::InvalidStateTransition),
259 };
260 Ok(new_state)
261 }
262}
263
264#[enum_dispatch]
265pub trait Order: 'static + Send {
266 fn into_any(self) -> OrderAny;
267 fn status(&self) -> OrderStatus;
268 fn trader_id(&self) -> TraderId;
269 fn strategy_id(&self) -> StrategyId;
270 fn instrument_id(&self) -> InstrumentId;
271 fn symbol(&self) -> Symbol;
272 fn venue(&self) -> Venue;
273 fn client_order_id(&self) -> ClientOrderId;
274 fn venue_order_id(&self) -> Option<VenueOrderId>;
275 fn position_id(&self) -> Option<PositionId>;
276 fn account_id(&self) -> Option<AccountId>;
277 fn last_trade_id(&self) -> Option<TradeId>;
278 fn order_side(&self) -> OrderSide;
279 fn order_type(&self) -> OrderType;
280 fn quantity(&self) -> Quantity;
281 fn time_in_force(&self) -> TimeInForce;
282 fn expire_time(&self) -> Option<UnixNanos>;
283 fn price(&self) -> Option<Price>;
284 fn trigger_price(&self) -> Option<Price>;
285 fn activation_price(&self) -> Option<Price> {
286 None
287 }
288 fn trigger_type(&self) -> Option<TriggerType>;
289 fn liquidity_side(&self) -> Option<LiquiditySide>;
290 fn is_post_only(&self) -> bool;
291 fn is_reduce_only(&self) -> bool;
292 fn is_quote_quantity(&self) -> bool;
293 fn display_qty(&self) -> Option<Quantity>;
294 fn limit_offset(&self) -> Option<Decimal>;
295 fn trailing_offset(&self) -> Option<Decimal>;
296 fn trailing_offset_type(&self) -> Option<TrailingOffsetType>;
297 fn emulation_trigger(&self) -> Option<TriggerType>;
298 fn trigger_instrument_id(&self) -> Option<InstrumentId>;
299 fn contingency_type(&self) -> Option<ContingencyType>;
300 fn order_list_id(&self) -> Option<OrderListId>;
301 fn linked_order_ids(&self) -> Option<&[ClientOrderId]>;
302 fn parent_order_id(&self) -> Option<ClientOrderId>;
303 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId>;
304 fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>>;
305 fn exec_spawn_id(&self) -> Option<ClientOrderId>;
306 fn tags(&self) -> Option<&[Ustr]>;
307 fn filled_qty(&self) -> Quantity;
308 fn leaves_qty(&self) -> Quantity;
309 fn avg_px(&self) -> Option<f64>;
310 fn slippage(&self) -> Option<f64>;
311 fn init_id(&self) -> UUID4;
312 fn ts_init(&self) -> UnixNanos;
313 fn ts_submitted(&self) -> Option<UnixNanos>;
314 fn ts_accepted(&self) -> Option<UnixNanos>;
315 fn ts_closed(&self) -> Option<UnixNanos>;
316 fn ts_last(&self) -> UnixNanos;
317
318 fn order_side_specified(&self) -> OrderSideSpecified {
319 self.order_side().as_specified()
320 }
321 fn commissions(&self) -> &IndexMap<Currency, Money>;
322
323 fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError>;
329 fn update(&mut self, event: &OrderUpdated);
330
331 fn events(&self) -> Vec<&OrderEventAny>;
332
333 fn last_event(&self) -> &OrderEventAny {
334 self.events().last().unwrap()
336 }
337
338 fn event_count(&self) -> usize {
339 self.events().len()
340 }
341
342 fn venue_order_ids(&self) -> Vec<&VenueOrderId>;
343
344 fn trade_ids(&self) -> Vec<&TradeId>;
345
346 fn has_price(&self) -> bool;
347
348 fn is_buy(&self) -> bool {
349 self.order_side() == OrderSide::Buy
350 }
351
352 fn is_sell(&self) -> bool {
353 self.order_side() == OrderSide::Sell
354 }
355
356 fn is_passive(&self) -> bool {
357 self.order_type() != OrderType::Market
358 }
359
360 fn is_aggressive(&self) -> bool {
361 self.order_type() == OrderType::Market
362 }
363
364 fn is_emulated(&self) -> bool {
365 self.status() == OrderStatus::Emulated
366 }
367
368 fn is_active_local(&self) -> bool {
369 matches!(
370 self.status(),
371 OrderStatus::Initialized | OrderStatus::Emulated | OrderStatus::Released
372 )
373 }
374
375 fn is_primary(&self) -> bool {
376 self.exec_algorithm_id().is_some()
377 && self
378 .exec_spawn_id()
379 .is_some_and(|spawn_id| self.client_order_id() == spawn_id)
380 }
381
382 fn is_secondary(&self) -> bool {
383 self.exec_algorithm_id().is_some()
384 && self
385 .exec_spawn_id()
386 .is_some_and(|spawn_id| self.client_order_id() != spawn_id)
387 }
388
389 fn is_contingency(&self) -> bool {
390 self.contingency_type().is_some()
391 }
392
393 fn is_parent_order(&self) -> bool {
394 match self.contingency_type() {
395 Some(c) => c == ContingencyType::Oto,
396 None => false,
397 }
398 }
399
400 fn is_child_order(&self) -> bool {
401 self.parent_order_id().is_some()
402 }
403
404 fn is_open(&self) -> bool {
405 if let Some(emulation_trigger) = self.emulation_trigger()
406 && emulation_trigger != TriggerType::NoTrigger
407 {
408 return false;
409 }
410
411 matches!(
412 self.status(),
413 OrderStatus::Accepted
414 | OrderStatus::Triggered
415 | OrderStatus::PendingCancel
416 | OrderStatus::PendingUpdate
417 | OrderStatus::PartiallyFilled
418 )
419 }
420
421 fn is_canceled(&self) -> bool {
422 self.status() == OrderStatus::Canceled
423 }
424
425 fn is_closed(&self) -> bool {
426 matches!(
427 self.status(),
428 OrderStatus::Denied
429 | OrderStatus::Rejected
430 | OrderStatus::Canceled
431 | OrderStatus::Expired
432 | OrderStatus::Filled
433 )
434 }
435
436 fn is_inflight(&self) -> bool {
437 if let Some(emulation_trigger) = self.emulation_trigger()
438 && emulation_trigger != TriggerType::NoTrigger
439 {
440 return false;
441 }
442
443 matches!(
444 self.status(),
445 OrderStatus::Submitted | OrderStatus::PendingCancel | OrderStatus::PendingUpdate
446 )
447 }
448
449 fn is_pending_update(&self) -> bool {
450 self.status() == OrderStatus::PendingUpdate
451 }
452
453 fn is_pending_cancel(&self) -> bool {
454 self.status() == OrderStatus::PendingCancel
455 }
456
457 fn is_spawned(&self) -> bool {
458 self.exec_spawn_id()
459 .is_some_and(|exec_spawn_id| exec_spawn_id != self.client_order_id())
460 }
461
462 fn to_own_book_order(&self) -> OwnBookOrder {
463 OwnBookOrder::new(
464 self.trader_id(),
465 self.client_order_id(),
466 self.venue_order_id(),
467 self.order_side().as_specified(),
468 self.price().expect("`OwnBookOrder` must have a price"), self.quantity(),
470 self.order_type(),
471 self.time_in_force(),
472 self.status(),
473 self.ts_last(),
474 self.ts_accepted().unwrap_or_default(),
475 self.ts_submitted().unwrap_or_default(),
476 self.ts_init(),
477 )
478 }
479
480 fn is_triggered(&self) -> Option<bool>; fn set_position_id(&mut self, position_id: Option<PositionId>);
482 fn set_quantity(&mut self, quantity: Quantity);
483 fn set_leaves_qty(&mut self, leaves_qty: Quantity);
484 fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>);
485 fn set_is_quote_quantity(&mut self, is_quote_quantity: bool);
486 fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide);
487 fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool;
488 fn previous_status(&self) -> Option<OrderStatus>;
489}
490
491impl<T> From<&T> for OrderInitialized
492where
493 T: Order,
494{
495 fn from(order: &T) -> Self {
496 Self {
497 trader_id: order.trader_id(),
498 strategy_id: order.strategy_id(),
499 instrument_id: order.instrument_id(),
500 client_order_id: order.client_order_id(),
501 order_side: order.order_side(),
502 order_type: order.order_type(),
503 quantity: order.quantity(),
504 price: order.price(),
505 trigger_price: order.trigger_price(),
506 trigger_type: order.trigger_type(),
507 time_in_force: order.time_in_force(),
508 expire_time: order.expire_time(),
509 post_only: order.is_post_only(),
510 reduce_only: order.is_reduce_only(),
511 quote_quantity: order.is_quote_quantity(),
512 display_qty: order.display_qty(),
513 limit_offset: order.limit_offset(),
514 trailing_offset: order.trailing_offset(),
515 trailing_offset_type: order.trailing_offset_type(),
516 emulation_trigger: order.emulation_trigger(),
517 trigger_instrument_id: order.trigger_instrument_id(),
518 contingency_type: order.contingency_type(),
519 order_list_id: order.order_list_id(),
520 linked_order_ids: order.linked_order_ids().map(|x| x.to_vec()),
521 parent_order_id: order.parent_order_id(),
522 exec_algorithm_id: order.exec_algorithm_id(),
523 exec_algorithm_params: order.exec_algorithm_params().map(|x| x.to_owned()),
524 exec_spawn_id: order.exec_spawn_id(),
525 tags: order.tags().map(|x| x.to_vec()),
526 event_id: order.init_id(),
527 ts_event: order.ts_init(),
528 ts_init: order.ts_init(),
529 reconciliation: false,
530 }
531 }
532}
533
534#[derive(Clone, Debug, Serialize, Deserialize)]
535pub struct OrderCore {
536 pub events: Vec<OrderEventAny>,
537 pub commissions: IndexMap<Currency, Money>,
538 pub venue_order_ids: Vec<VenueOrderId>,
539 pub trade_ids: Vec<TradeId>,
540 pub previous_status: Option<OrderStatus>,
541 pub status: OrderStatus,
542 pub trader_id: TraderId,
543 pub strategy_id: StrategyId,
544 pub instrument_id: InstrumentId,
545 pub client_order_id: ClientOrderId,
546 pub venue_order_id: Option<VenueOrderId>,
547 pub position_id: Option<PositionId>,
548 pub account_id: Option<AccountId>,
549 pub last_trade_id: Option<TradeId>,
550 pub side: OrderSide,
551 pub order_type: OrderType,
552 pub quantity: Quantity,
553 pub time_in_force: TimeInForce,
554 pub liquidity_side: Option<LiquiditySide>,
555 pub is_reduce_only: bool,
556 pub is_quote_quantity: bool,
557 pub emulation_trigger: Option<TriggerType>,
558 pub contingency_type: Option<ContingencyType>,
559 pub order_list_id: Option<OrderListId>,
560 pub linked_order_ids: Option<Vec<ClientOrderId>>,
561 pub parent_order_id: Option<ClientOrderId>,
562 pub exec_algorithm_id: Option<ExecAlgorithmId>,
563 pub exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
564 pub exec_spawn_id: Option<ClientOrderId>,
565 pub tags: Option<Vec<Ustr>>,
566 pub filled_qty: Quantity,
567 pub leaves_qty: Quantity,
568 pub avg_px: Option<f64>,
569 pub slippage: Option<f64>,
570 pub init_id: UUID4,
571 pub ts_init: UnixNanos,
572 pub ts_submitted: Option<UnixNanos>,
573 pub ts_accepted: Option<UnixNanos>,
574 pub ts_closed: Option<UnixNanos>,
575 pub ts_last: UnixNanos,
576}
577
578impl OrderCore {
579 pub fn new(init: OrderInitialized) -> Self {
581 let events: Vec<OrderEventAny> = vec![OrderEventAny::Initialized(init.clone())];
582 Self {
583 events,
584 commissions: IndexMap::new(),
585 venue_order_ids: Vec::new(),
586 trade_ids: Vec::new(),
587 previous_status: None,
588 status: OrderStatus::Initialized,
589 trader_id: init.trader_id,
590 strategy_id: init.strategy_id,
591 instrument_id: init.instrument_id,
592 client_order_id: init.client_order_id,
593 venue_order_id: None,
594 position_id: None,
595 account_id: None,
596 last_trade_id: None,
597 side: init.order_side,
598 order_type: init.order_type,
599 quantity: init.quantity,
600 time_in_force: init.time_in_force,
601 liquidity_side: Some(LiquiditySide::NoLiquiditySide),
602 is_reduce_only: init.reduce_only,
603 is_quote_quantity: init.quote_quantity,
604 emulation_trigger: init.emulation_trigger.or(Some(TriggerType::NoTrigger)),
605 contingency_type: init
606 .contingency_type
607 .or(Some(ContingencyType::NoContingency)),
608 order_list_id: init.order_list_id,
609 linked_order_ids: init.linked_order_ids,
610 parent_order_id: init.parent_order_id,
611 exec_algorithm_id: init.exec_algorithm_id,
612 exec_algorithm_params: init.exec_algorithm_params,
613 exec_spawn_id: init.exec_spawn_id,
614 tags: init.tags,
615 filled_qty: Quantity::zero(init.quantity.precision),
616 leaves_qty: init.quantity,
617 avg_px: None,
618 slippage: None,
619 init_id: init.event_id,
620 ts_init: init.ts_event,
621 ts_submitted: None,
622 ts_accepted: None,
623 ts_closed: None,
624 ts_last: init.ts_event,
625 }
626 }
627
628 pub fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
638 assert_eq!(self.client_order_id, event.client_order_id());
639 assert_eq!(self.strategy_id, event.strategy_id());
640
641 if !matches!(
646 event,
647 OrderEventAny::Initialized(_)
648 | OrderEventAny::ModifyRejected(_)
649 | OrderEventAny::CancelRejected(_)
650 ) && !matches!(
651 self.status,
652 OrderStatus::PendingUpdate | OrderStatus::PendingCancel
653 ) {
654 self.previous_status = Some(self.status);
655 }
656
657 let new_status = self.status.transition(&event)?;
658 self.status = new_status;
659
660 match &event {
661 OrderEventAny::Initialized(_) => return Err(OrderError::AlreadyInitialized),
662 OrderEventAny::Denied(event) => self.denied(event),
663 OrderEventAny::Emulated(event) => self.emulated(event),
664 OrderEventAny::Released(event) => self.released(event),
665 OrderEventAny::Submitted(event) => self.submitted(event),
666 OrderEventAny::Rejected(event) => self.rejected(event),
667 OrderEventAny::Accepted(event) => self.accepted(event),
668 OrderEventAny::PendingUpdate(event) => self.pending_update(event),
669 OrderEventAny::PendingCancel(event) => self.pending_cancel(event),
670 OrderEventAny::ModifyRejected(event) => self.modify_rejected(event),
671 OrderEventAny::CancelRejected(event) => self.cancel_rejected(event),
672 OrderEventAny::Updated(event) => self.updated(event),
673 OrderEventAny::Triggered(event) => self.triggered(event),
674 OrderEventAny::Canceled(event) => self.canceled(event),
675 OrderEventAny::Expired(event) => self.expired(event),
676 OrderEventAny::Filled(event) => self.filled(event),
677 }
678
679 self.ts_last = event.ts_event();
680 self.events.push(event);
681 Ok(())
682 }
683
684 fn denied(&mut self, event: &OrderDenied) {
685 self.ts_closed = Some(event.ts_event);
686 }
687
688 fn emulated(&self, _event: &OrderEmulated) {
689 }
691
692 fn released(&mut self, _event: &OrderReleased) {
693 self.emulation_trigger = None;
694 }
695
696 fn submitted(&mut self, event: &OrderSubmitted) {
697 self.account_id = Some(event.account_id);
698 self.ts_submitted = Some(event.ts_event);
699 }
700
701 fn accepted(&mut self, event: &OrderAccepted) {
702 self.account_id = Some(event.account_id);
703 self.venue_order_id = Some(event.venue_order_id);
704 self.venue_order_ids.push(event.venue_order_id);
705 self.ts_accepted = Some(event.ts_event);
706 }
707
708 fn rejected(&mut self, event: &OrderRejected) {
709 self.ts_closed = Some(event.ts_event);
710 }
711
712 fn pending_update(&self, _event: &OrderPendingUpdate) {
713 }
715
716 fn pending_cancel(&self, _event: &OrderPendingCancel) {
717 }
719
720 fn modify_rejected(&mut self, _event: &OrderModifyRejected) {
721 self.status = self
722 .previous_status
723 .unwrap_or_else(|| panic!("{}", OrderError::NoPreviousState));
724 }
725
726 fn cancel_rejected(&mut self, _event: &OrderCancelRejected) {
727 self.status = self
728 .previous_status
729 .unwrap_or_else(|| panic!("{}", OrderError::NoPreviousState));
730 }
731
732 fn triggered(&mut self, _event: &OrderTriggered) {}
733
734 fn canceled(&mut self, event: &OrderCanceled) {
735 self.ts_closed = Some(event.ts_event);
736 }
737
738 fn expired(&mut self, event: &OrderExpired) {
739 self.ts_closed = Some(event.ts_event);
740 }
741
742 fn updated(&mut self, event: &OrderUpdated) {
743 if let Some(venue_order_id) = &event.venue_order_id
744 && (self.venue_order_id.is_none()
745 || venue_order_id != self.venue_order_id.as_ref().unwrap())
746 {
747 self.venue_order_id = Some(*venue_order_id);
748 self.venue_order_ids.push(*venue_order_id);
749 }
750 }
751
752 fn filled(&mut self, event: &OrderFilled) {
753 let new_filled_qty = Quantity::from_raw(
755 self.filled_qty.raw.saturating_add(event.last_qty.raw),
756 self.filled_qty.precision,
757 );
758
759 if new_filled_qty < self.quantity {
760 self.status = OrderStatus::PartiallyFilled;
761 } else {
762 self.status = OrderStatus::Filled;
763 self.ts_closed = Some(event.ts_event);
764 }
765
766 self.venue_order_id = Some(event.venue_order_id);
767 self.position_id = event.position_id;
768 self.trade_ids.push(event.trade_id);
769 self.last_trade_id = Some(event.trade_id);
770 self.liquidity_side = Some(event.liquidity_side);
771 self.filled_qty = new_filled_qty;
772 self.leaves_qty = self.leaves_qty.saturating_sub(event.last_qty);
773 self.ts_last = event.ts_event;
774 if self.ts_accepted.is_none() {
775 self.ts_accepted = Some(event.ts_event);
777 }
778
779 self.set_avg_px(event.last_qty, event.last_px);
780 }
781
782 fn set_avg_px(&mut self, last_qty: Quantity, last_px: Price) {
783 if self.avg_px.is_none() {
784 self.avg_px = Some(last_px.as_f64());
785 return;
786 }
787
788 let prev_filled_qty = (self.filled_qty - last_qty).as_f64();
790 let last_qty_f64 = last_qty.as_f64();
791 let total_qty = prev_filled_qty + last_qty_f64;
792
793 let avg_px = self
794 .avg_px
795 .unwrap()
796 .mul_add(prev_filled_qty, last_px.as_f64() * last_qty_f64)
797 / total_qty;
798 self.avg_px = Some(avg_px);
799 }
800
801 pub fn set_slippage(&mut self, price: Price) {
802 self.slippage = self.avg_px.and_then(|avg_px| {
803 let current_price = price.as_f64();
804 match self.side {
805 OrderSide::Buy if avg_px > current_price => Some(avg_px - current_price),
806 OrderSide::Sell if avg_px < current_price => Some(current_price - avg_px),
807 _ => None,
808 }
809 });
810 }
811
812 #[must_use]
814 pub fn opposite_side(side: OrderSide) -> OrderSide {
815 match side {
816 OrderSide::Buy => OrderSide::Sell,
817 OrderSide::Sell => OrderSide::Buy,
818 OrderSide::NoOrderSide => OrderSide::NoOrderSide,
819 }
820 }
821
822 #[must_use]
824 pub fn closing_side(side: PositionSide) -> OrderSide {
825 match side {
826 PositionSide::Long => OrderSide::Sell,
827 PositionSide::Short => OrderSide::Buy,
828 PositionSide::Flat => OrderSide::NoOrderSide,
829 PositionSide::NoPositionSide => OrderSide::NoOrderSide,
830 }
831 }
832
833 #[must_use]
837 pub fn signed_decimal_qty(&self) -> Decimal {
838 match self.side {
839 OrderSide::Buy => self.quantity.as_decimal(),
840 OrderSide::Sell => -self.quantity.as_decimal(),
841 _ => panic!("Invalid order side"),
842 }
843 }
844
845 #[must_use]
846 pub fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
847 if side == PositionSide::Flat {
848 return false;
849 }
850
851 match (self.side, side) {
852 (OrderSide::Buy, PositionSide::Long) => false,
853 (OrderSide::Buy, PositionSide::Short) => self.leaves_qty <= position_qty,
854 (OrderSide::Sell, PositionSide::Short) => false,
855 (OrderSide::Sell, PositionSide::Long) => self.leaves_qty <= position_qty,
856 _ => true,
857 }
858 }
859
860 #[must_use]
861 pub fn commission(&self, currency: &Currency) -> Option<Money> {
862 self.commissions.get(currency).copied()
863 }
864
865 #[must_use]
866 pub fn commissions(&self) -> IndexMap<Currency, Money> {
867 self.commissions.clone()
868 }
869
870 #[must_use]
871 pub fn commissions_vec(&self) -> Vec<Money> {
872 self.commissions.values().cloned().collect()
873 }
874
875 #[must_use]
876 pub fn init_event(&self) -> Option<OrderEventAny> {
877 self.events.first().cloned()
878 }
879}
880
881#[cfg(test)]
885mod tests {
886 use rstest::rstest;
887 use rust_decimal_macros::dec;
888
889 use super::*;
890 use crate::{
891 enums::{OrderSide, OrderStatus, PositionSide},
892 events::order::{
893 accepted::OrderAcceptedBuilder, canceled::OrderCanceledBuilder,
894 denied::OrderDeniedBuilder, filled::OrderFilledBuilder,
895 initialized::OrderInitializedBuilder, submitted::OrderSubmittedBuilder,
896 },
897 orders::MarketOrder,
898 };
899
900 #[rstest]
911 #[case(OrderSide::Buy, OrderSide::Sell)]
912 #[case(OrderSide::Sell, OrderSide::Buy)]
913 #[case(OrderSide::NoOrderSide, OrderSide::NoOrderSide)]
914 fn test_order_opposite_side(#[case] order_side: OrderSide, #[case] expected_side: OrderSide) {
915 let result = OrderCore::opposite_side(order_side);
916 assert_eq!(result, expected_side);
917 }
918
919 #[rstest]
920 #[case(PositionSide::Long, OrderSide::Sell)]
921 #[case(PositionSide::Short, OrderSide::Buy)]
922 #[case(PositionSide::NoPositionSide, OrderSide::NoOrderSide)]
923 fn test_closing_side(#[case] position_side: PositionSide, #[case] expected_side: OrderSide) {
924 let result = OrderCore::closing_side(position_side);
925 assert_eq!(result, expected_side);
926 }
927
928 #[rstest]
929 #[case(OrderSide::Buy, dec!(10_000))]
930 #[case(OrderSide::Sell, dec!(-10_000))]
931 fn test_signed_decimal_qty(#[case] order_side: OrderSide, #[case] expected: Decimal) {
932 let order: MarketOrder = OrderInitializedBuilder::default()
933 .order_side(order_side)
934 .quantity(Quantity::from(10_000))
935 .build()
936 .unwrap()
937 .into();
938
939 let result = order.signed_decimal_qty();
940 assert_eq!(result, expected);
941 }
942
943 #[rustfmt::skip]
944 #[rstest]
945 #[case(OrderSide::Buy, Quantity::from(100), PositionSide::Long, Quantity::from(50), false)]
946 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(50), true)]
947 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Short, Quantity::from(100), true)]
948 #[case(OrderSide::Buy, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
949 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Flat, Quantity::from(0), false)]
950 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(50), true)]
951 #[case(OrderSide::Sell, Quantity::from(50), PositionSide::Long, Quantity::from(100), true)]
952 #[case(OrderSide::Sell, Quantity::from(100), PositionSide::Short, Quantity::from(50), false)]
953 fn test_would_reduce_only(
954 #[case] order_side: OrderSide,
955 #[case] order_qty: Quantity,
956 #[case] position_side: PositionSide,
957 #[case] position_qty: Quantity,
958 #[case] expected: bool,
959 ) {
960 let order: MarketOrder = OrderInitializedBuilder::default()
961 .order_side(order_side)
962 .quantity(order_qty)
963 .build()
964 .unwrap()
965 .into();
966
967 assert_eq!(
968 order.would_reduce_only(position_side, position_qty),
969 expected
970 );
971 }
972
973 #[rstest]
974 fn test_order_state_transition_denied() {
975 let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
976 let denied = OrderDeniedBuilder::default().build().unwrap();
977 let event = OrderEventAny::Denied(denied);
978
979 order.apply(event.clone()).unwrap();
980
981 assert_eq!(order.status, OrderStatus::Denied);
982 assert!(order.is_closed());
983 assert!(!order.is_open());
984 assert_eq!(order.event_count(), 2);
985 assert_eq!(order.last_event(), &event);
986 }
987
988 #[rstest]
989 fn test_order_life_cycle_to_filled() {
990 let init = OrderInitializedBuilder::default().build().unwrap();
991 let submitted = OrderSubmittedBuilder::default().build().unwrap();
992 let accepted = OrderAcceptedBuilder::default().build().unwrap();
993 let filled = OrderFilledBuilder::default().build().unwrap();
994
995 let mut order: MarketOrder = init.clone().into();
996 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
997 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
998 order.apply(OrderEventAny::Filled(filled)).unwrap();
999
1000 assert_eq!(order.client_order_id, init.client_order_id);
1001 assert_eq!(order.status(), OrderStatus::Filled);
1002 assert_eq!(order.filled_qty(), Quantity::from(100_000));
1003 assert_eq!(order.leaves_qty(), Quantity::from(0));
1004 assert_eq!(order.avg_px(), Some(1.0));
1005 assert!(!order.is_open());
1006 assert!(order.is_closed());
1007 assert_eq!(order.commission(&Currency::USD()), None);
1008 assert_eq!(order.commissions(), &IndexMap::new());
1009 }
1010
1011 #[rstest]
1012 fn test_order_state_transition_to_canceled() {
1013 let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
1014 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1015 let canceled = OrderCanceledBuilder::default().build().unwrap();
1016
1017 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1018 order.apply(OrderEventAny::Canceled(canceled)).unwrap();
1019
1020 assert_eq!(order.status(), OrderStatus::Canceled);
1021 assert!(order.is_closed());
1022 assert!(!order.is_open());
1023 }
1024
1025 #[rstest]
1026 fn test_order_life_cycle_to_partially_filled() {
1027 let init = OrderInitializedBuilder::default().build().unwrap();
1028 let submitted = OrderSubmittedBuilder::default().build().unwrap();
1029 let accepted = OrderAcceptedBuilder::default().build().unwrap();
1030 let filled = OrderFilledBuilder::default()
1031 .last_qty(Quantity::from(50_000))
1032 .build()
1033 .unwrap();
1034
1035 let mut order: MarketOrder = init.clone().into();
1036 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1037 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1038 order.apply(OrderEventAny::Filled(filled)).unwrap();
1039
1040 assert_eq!(order.client_order_id, init.client_order_id);
1041 assert_eq!(order.status(), OrderStatus::PartiallyFilled);
1042 assert_eq!(order.filled_qty(), Quantity::from(50_000));
1043 assert_eq!(order.leaves_qty(), Quantity::from(50_000));
1044 assert!(order.is_open());
1045 assert!(!order.is_closed());
1046 }
1047
1048 #[rstest]
1049 fn test_order_commission_calculation() {
1050 let mut order: MarketOrder = OrderInitializedBuilder::default().build().unwrap().into();
1051 order
1052 .commissions
1053 .insert(Currency::USD(), Money::new(10.0, Currency::USD()));
1054
1055 assert_eq!(
1056 order.commission(&Currency::USD()),
1057 Some(Money::new(10.0, Currency::USD()))
1058 );
1059 assert_eq!(
1060 order.commissions_vec(),
1061 vec![Money::new(10.0, Currency::USD())]
1062 );
1063 }
1064
1065 #[rstest]
1066 fn test_order_is_primary() {
1067 let order: MarketOrder = OrderInitializedBuilder::default()
1068 .exec_algorithm_id(Some(ExecAlgorithmId::from("ALGO-001")))
1069 .exec_spawn_id(Some(ClientOrderId::from("O-001")))
1070 .client_order_id(ClientOrderId::from("O-001"))
1071 .build()
1072 .unwrap()
1073 .into();
1074
1075 assert!(order.is_primary());
1076 assert!(!order.is_secondary());
1077 }
1078
1079 #[rstest]
1080 fn test_order_is_secondary() {
1081 let order: MarketOrder = OrderInitializedBuilder::default()
1082 .exec_algorithm_id(Some(ExecAlgorithmId::from("ALGO-001")))
1083 .exec_spawn_id(Some(ClientOrderId::from("O-002")))
1084 .client_order_id(ClientOrderId::from("O-001"))
1085 .build()
1086 .unwrap()
1087 .into();
1088
1089 assert!(!order.is_primary());
1090 assert!(order.is_secondary());
1091 }
1092
1093 #[rstest]
1094 fn test_order_is_contingency() {
1095 let order: MarketOrder = OrderInitializedBuilder::default()
1096 .contingency_type(Some(ContingencyType::Oto))
1097 .build()
1098 .unwrap()
1099 .into();
1100
1101 assert!(order.is_contingency());
1102 assert!(order.is_parent_order());
1103 assert!(!order.is_child_order());
1104 }
1105
1106 #[rstest]
1107 fn test_order_is_child_order() {
1108 let order: MarketOrder = OrderInitializedBuilder::default()
1109 .parent_order_id(Some(ClientOrderId::from("PARENT-001")))
1110 .build()
1111 .unwrap()
1112 .into();
1113
1114 assert!(order.is_child_order());
1115 assert!(!order.is_parent_order());
1116 }
1117
1118 #[rstest]
1119 fn test_to_own_book_order_timestamp_ordering() {
1120 use crate::orders::limit::LimitOrder;
1121
1122 let init = OrderInitializedBuilder::default()
1124 .price(Some(Price::from("100.00")))
1125 .build()
1126 .unwrap();
1127 let submitted = OrderSubmittedBuilder::default()
1128 .ts_event(UnixNanos::from(1_000_000))
1129 .build()
1130 .unwrap();
1131 let accepted = OrderAcceptedBuilder::default()
1132 .ts_event(UnixNanos::from(2_000_000))
1133 .build()
1134 .unwrap();
1135
1136 let mut order: LimitOrder = init.into();
1137 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1138 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1139
1140 let own_book_order = order.to_own_book_order();
1141
1142 assert_eq!(own_book_order.ts_submitted, UnixNanos::from(1_000_000));
1144 assert_eq!(own_book_order.ts_accepted, UnixNanos::from(2_000_000));
1145 assert_eq!(own_book_order.ts_last, UnixNanos::from(2_000_000));
1146 }
1147
1148 #[rstest]
1149 fn test_order_accepted_without_submitted_sets_account_id() {
1150 let init = OrderInitializedBuilder::default().build().unwrap();
1152 let accepted = OrderAcceptedBuilder::default()
1153 .account_id(AccountId::from("EXTERNAL-001"))
1154 .build()
1155 .unwrap();
1156
1157 let mut order: MarketOrder = init.into();
1158
1159 assert_eq!(order.account_id(), None);
1161
1162 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1164
1165 assert_eq!(order.account_id(), Some(AccountId::from("EXTERNAL-001")));
1167 assert_eq!(order.status(), OrderStatus::Accepted);
1168 }
1169
1170 #[rstest]
1171 fn test_order_accepted_after_submitted_preserves_account_id() {
1172 let init = OrderInitializedBuilder::default().build().unwrap();
1174 let submitted = OrderSubmittedBuilder::default()
1175 .account_id(AccountId::from("SUBMITTED-001"))
1176 .build()
1177 .unwrap();
1178 let accepted = OrderAcceptedBuilder::default()
1179 .account_id(AccountId::from("ACCEPTED-001"))
1180 .build()
1181 .unwrap();
1182
1183 let mut order: MarketOrder = init.into();
1184 order.apply(OrderEventAny::Submitted(submitted)).unwrap();
1185
1186 assert_eq!(order.account_id(), Some(AccountId::from("SUBMITTED-001")));
1188
1189 order.apply(OrderEventAny::Accepted(accepted)).unwrap();
1191
1192 assert_eq!(order.account_id(), Some(AccountId::from("ACCEPTED-001")));
1194 assert_eq!(order.status(), OrderStatus::Accepted);
1195 }
1196}