1use std::{
17 fmt::Display,
18 ops::{Deref, DerefMut},
19};
20
21use indexmap::IndexMap;
22use nautilus_core::{
23 UUID4, UnixNanos,
24 correctness::{FAILED, check_predicate_false},
25};
26use rust_decimal::Decimal;
27use serde::{Deserialize, Serialize};
28use ustr::Ustr;
29
30use super::{Order, OrderAny, OrderCore};
31use crate::{
32 enums::{
33 ContingencyType, LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide,
34 TimeInForce, TrailingOffsetType, TriggerType,
35 },
36 events::{OrderEventAny, OrderInitialized, OrderUpdated},
37 identifiers::{
38 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
39 StrategyId, Symbol, TradeId, TraderId, Venue, VenueOrderId,
40 },
41 orders::OrderError,
42 types::{Currency, Money, Price, Quantity, quantity::check_positive_quantity},
43};
44
45#[derive(Clone, Debug, Serialize, Deserialize)]
46#[cfg_attr(
47 feature = "python",
48 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
49)]
50pub struct MarketOrder {
51 core: OrderCore,
52 pub protection_price: Option<Price>,
53}
54
55impl MarketOrder {
56 #[allow(clippy::too_many_arguments)]
64 pub fn new_checked(
65 trader_id: TraderId,
66 strategy_id: StrategyId,
67 instrument_id: InstrumentId,
68 client_order_id: ClientOrderId,
69 order_side: OrderSide,
70 quantity: Quantity,
71 time_in_force: TimeInForce,
72 init_id: UUID4,
73 ts_init: UnixNanos,
74 reduce_only: bool,
75 quote_quantity: bool,
76 contingency_type: Option<ContingencyType>,
77 order_list_id: Option<OrderListId>,
78 linked_order_ids: Option<Vec<ClientOrderId>>,
79 parent_order_id: Option<ClientOrderId>,
80 exec_algorithm_id: Option<ExecAlgorithmId>,
81 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
82 exec_spawn_id: Option<ClientOrderId>,
83 tags: Option<Vec<Ustr>>,
84 ) -> anyhow::Result<Self> {
85 check_positive_quantity(quantity, stringify!(quantity))?;
86 check_predicate_false(
87 time_in_force == TimeInForce::Gtd,
88 "GTD not supported for Market orders",
89 )?;
90
91 let init_order = OrderInitialized::new(
92 trader_id,
93 strategy_id,
94 instrument_id,
95 client_order_id,
96 order_side,
97 OrderType::Market,
98 quantity,
99 time_in_force,
100 false,
101 reduce_only,
102 quote_quantity,
103 false,
104 init_id,
105 ts_init,
106 ts_init,
107 None,
108 None,
109 Some(TriggerType::NoTrigger),
110 None,
111 None,
112 None,
113 None,
114 None,
115 None,
116 None,
117 contingency_type,
118 order_list_id,
119 linked_order_ids,
120 parent_order_id,
121 exec_algorithm_id,
122 exec_algorithm_params,
123 exec_spawn_id,
124 tags,
125 );
126
127 Ok(Self {
128 core: OrderCore::new(init_order),
129 protection_price: None,
130 })
131 }
132
133 #[allow(clippy::too_many_arguments)]
139 pub fn new(
140 trader_id: TraderId,
141 strategy_id: StrategyId,
142 instrument_id: InstrumentId,
143 client_order_id: ClientOrderId,
144 order_side: OrderSide,
145 quantity: Quantity,
146 time_in_force: TimeInForce,
147 init_id: UUID4,
148 ts_init: UnixNanos,
149 reduce_only: bool,
150 quote_quantity: bool,
151 contingency_type: Option<ContingencyType>,
152 order_list_id: Option<OrderListId>,
153 linked_order_ids: Option<Vec<ClientOrderId>>,
154 parent_order_id: Option<ClientOrderId>,
155 exec_algorithm_id: Option<ExecAlgorithmId>,
156 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
157 exec_spawn_id: Option<ClientOrderId>,
158 tags: Option<Vec<Ustr>>,
159 ) -> Self {
160 Self::new_checked(
161 trader_id,
162 strategy_id,
163 instrument_id,
164 client_order_id,
165 order_side,
166 quantity,
167 time_in_force,
168 init_id,
169 ts_init,
170 reduce_only,
171 quote_quantity,
172 contingency_type,
173 order_list_id,
174 linked_order_ids,
175 parent_order_id,
176 exec_algorithm_id,
177 exec_algorithm_params,
178 exec_spawn_id,
179 tags,
180 )
181 .expect(FAILED)
182 }
183}
184
185impl Deref for MarketOrder {
186 type Target = OrderCore;
187
188 fn deref(&self) -> &Self::Target {
189 &self.core
190 }
191}
192
193impl DerefMut for MarketOrder {
194 fn deref_mut(&mut self) -> &mut Self::Target {
195 &mut self.core
196 }
197}
198
199impl PartialEq for MarketOrder {
200 fn eq(&self, other: &Self) -> bool {
201 self.client_order_id == other.client_order_id
202 }
203}
204
205impl Order for MarketOrder {
206 fn into_any(self) -> OrderAny {
207 OrderAny::Market(self)
208 }
209
210 fn status(&self) -> OrderStatus {
211 self.status
212 }
213
214 fn trader_id(&self) -> TraderId {
215 self.trader_id
216 }
217
218 fn strategy_id(&self) -> StrategyId {
219 self.strategy_id
220 }
221
222 fn instrument_id(&self) -> InstrumentId {
223 self.instrument_id
224 }
225
226 fn symbol(&self) -> Symbol {
227 self.instrument_id.symbol
228 }
229
230 fn venue(&self) -> Venue {
231 self.instrument_id.venue
232 }
233
234 fn client_order_id(&self) -> ClientOrderId {
235 self.client_order_id
236 }
237
238 fn venue_order_id(&self) -> Option<VenueOrderId> {
239 self.venue_order_id
240 }
241
242 fn position_id(&self) -> Option<PositionId> {
243 self.position_id
244 }
245
246 fn account_id(&self) -> Option<AccountId> {
247 self.account_id
248 }
249
250 fn last_trade_id(&self) -> Option<TradeId> {
251 self.last_trade_id
252 }
253
254 fn order_side(&self) -> OrderSide {
255 self.side
256 }
257
258 fn order_type(&self) -> OrderType {
259 self.order_type
260 }
261
262 fn quantity(&self) -> Quantity {
263 self.quantity
264 }
265
266 fn time_in_force(&self) -> TimeInForce {
267 self.time_in_force
268 }
269
270 fn expire_time(&self) -> Option<UnixNanos> {
271 None
272 }
273
274 fn price(&self) -> Option<Price> {
275 self.protection_price
276 }
277
278 fn trigger_price(&self) -> Option<Price> {
279 None
280 }
281
282 fn trigger_type(&self) -> Option<TriggerType> {
283 None
284 }
285
286 fn liquidity_side(&self) -> Option<LiquiditySide> {
287 self.liquidity_side
288 }
289
290 fn is_post_only(&self) -> bool {
291 false
292 }
293
294 fn is_reduce_only(&self) -> bool {
295 self.is_reduce_only
296 }
297
298 fn is_quote_quantity(&self) -> bool {
299 self.is_quote_quantity
300 }
301
302 fn has_price(&self) -> bool {
303 self.protection_price.is_some()
304 }
305
306 fn display_qty(&self) -> Option<Quantity> {
307 None
308 }
309
310 fn limit_offset(&self) -> Option<Decimal> {
311 None
312 }
313
314 fn trailing_offset(&self) -> Option<Decimal> {
315 None
316 }
317
318 fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
319 None
320 }
321
322 fn emulation_trigger(&self) -> Option<TriggerType> {
323 None
324 }
325
326 fn trigger_instrument_id(&self) -> Option<InstrumentId> {
327 None
328 }
329
330 fn contingency_type(&self) -> Option<ContingencyType> {
331 self.contingency_type
332 }
333
334 fn order_list_id(&self) -> Option<OrderListId> {
335 self.order_list_id
336 }
337
338 fn linked_order_ids(&self) -> Option<&[ClientOrderId]> {
339 self.linked_order_ids.as_deref()
340 }
341
342 fn parent_order_id(&self) -> Option<ClientOrderId> {
343 self.parent_order_id
344 }
345
346 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
347 self.exec_algorithm_id
348 }
349
350 fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>> {
351 self.exec_algorithm_params.as_ref()
352 }
353
354 fn exec_spawn_id(&self) -> Option<ClientOrderId> {
355 self.exec_spawn_id
356 }
357
358 fn tags(&self) -> Option<&[Ustr]> {
359 self.tags.as_deref()
360 }
361
362 fn filled_qty(&self) -> Quantity {
363 self.filled_qty
364 }
365
366 fn leaves_qty(&self) -> Quantity {
367 self.leaves_qty
368 }
369
370 fn overfill_qty(&self) -> Quantity {
371 self.overfill_qty
372 }
373
374 fn avg_px(&self) -> Option<f64> {
375 self.avg_px
376 }
377
378 fn slippage(&self) -> Option<f64> {
379 self.slippage
380 }
381
382 fn init_id(&self) -> UUID4 {
383 self.init_id
384 }
385
386 fn ts_init(&self) -> UnixNanos {
387 self.ts_init
388 }
389
390 fn ts_submitted(&self) -> Option<UnixNanos> {
391 self.ts_submitted
392 }
393
394 fn ts_accepted(&self) -> Option<UnixNanos> {
395 self.ts_accepted
396 }
397
398 fn ts_closed(&self) -> Option<UnixNanos> {
399 self.ts_closed
400 }
401
402 fn ts_last(&self) -> UnixNanos {
403 self.ts_last
404 }
405
406 fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
407 if let OrderEventAny::Updated(ref event) = event {
408 self.update(event);
409 };
410
411 self.core.apply(event)?;
412
413 Ok(())
414 }
415
416 fn update(&mut self, event: &OrderUpdated) {
417 assert!(event.price.is_none(), "{}", OrderError::InvalidOrderEvent);
418 assert!(
419 event.trigger_price.is_none(),
420 "{}",
421 OrderError::InvalidOrderEvent
422 );
423
424 self.protection_price = event.protection_price;
425 self.quantity = event.quantity;
426 self.leaves_qty = self.quantity.saturating_sub(self.filled_qty);
427 }
428
429 fn events(&self) -> Vec<&OrderEventAny> {
430 self.events.iter().collect()
431 }
432
433 fn venue_order_ids(&self) -> Vec<&VenueOrderId> {
434 self.venue_order_ids.iter().collect()
435 }
436
437 fn trade_ids(&self) -> Vec<&TradeId> {
438 self.trade_ids.iter().collect()
439 }
440
441 fn commissions(&self) -> &IndexMap<Currency, Money> {
442 &self.commissions
443 }
444
445 fn is_triggered(&self) -> Option<bool> {
446 None
447 }
448
449 fn set_position_id(&mut self, position_id: Option<PositionId>) {
450 self.position_id = position_id;
451 }
452
453 fn set_quantity(&mut self, quantity: Quantity) {
454 self.quantity = quantity;
455 }
456
457 fn set_leaves_qty(&mut self, leaves_qty: Quantity) {
458 self.leaves_qty = leaves_qty;
459 }
460
461 fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>) {
462 self.emulation_trigger = emulation_trigger;
463 }
464
465 fn set_is_quote_quantity(&mut self, is_quote_quantity: bool) {
466 self.is_quote_quantity = is_quote_quantity;
467 }
468
469 fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide) {
470 self.liquidity_side = Some(liquidity_side);
471 }
472
473 fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
474 self.core.would_reduce_only(side, position_qty)
475 }
476
477 fn previous_status(&self) -> Option<OrderStatus> {
478 self.core.previous_status
479 }
480}
481
482impl Display for MarketOrder {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484 write!(
485 f,
486 "MarketOrder(\
487 {} {} {} @ {} {}, \
488 status={}, \
489 client_order_id={}, \
490 venue_order_id={}, \
491 position_id={}, \
492 exec_algorithm_id={}, \
493 exec_spawn_id={}, \
494 tags={:?}\
495 )",
496 self.side,
497 self.quantity.to_formatted_string(),
498 self.instrument_id,
499 self.order_type,
500 self.time_in_force,
501 self.status,
502 self.client_order_id,
503 self.venue_order_id.map_or_else(
504 || "None".to_string(),
505 |venue_order_id| format!("{venue_order_id}")
506 ),
507 self.position_id.map_or_else(
508 || "None".to_string(),
509 |position_id| format!("{position_id}")
510 ),
511 self.exec_algorithm_id
512 .map_or_else(|| "None".to_string(), |id| format!("{id}")),
513 self.exec_spawn_id
514 .map_or_else(|| "None".to_string(), |id| format!("{id}")),
515 self.tags
516 )
517 }
518}
519
520impl From<OrderInitialized> for MarketOrder {
521 fn from(event: OrderInitialized) -> Self {
522 Self::new(
523 event.trader_id,
524 event.strategy_id,
525 event.instrument_id,
526 event.client_order_id,
527 event.order_side,
528 event.quantity,
529 event.time_in_force,
530 event.event_id,
531 event.ts_event,
532 event.reduce_only,
533 event.quote_quantity,
534 event.contingency_type,
535 event.order_list_id,
536 event.linked_order_ids,
537 event.parent_order_id,
538 event.exec_algorithm_id,
539 event.exec_algorithm_params,
540 event.exec_spawn_id,
541 event.tags,
542 )
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use rstest::rstest;
549
550 use crate::{
551 enums::{OrderSide, OrderType, TimeInForce},
552 events::{OrderEventAny, OrderUpdated, order::initialized::OrderInitializedBuilder},
553 instruments::{CurrencyPair, stubs::*},
554 orders::{MarketOrder, Order, builder::OrderTestBuilder, stubs::TestOrderStubs},
555 types::{Price, Quantity},
556 };
557
558 #[rstest]
559 #[should_panic(
560 expected = "Condition failed: invalid `Quantity` for 'quantity' not positive, was 0"
561 )]
562 fn test_positive_quantity_condition(audusd_sim: CurrencyPair) {
563 let _ = OrderTestBuilder::new(OrderType::Market)
564 .instrument_id(audusd_sim.id)
565 .side(OrderSide::Buy)
566 .quantity(Quantity::from(0))
567 .build();
568 }
569
570 #[rstest]
571 #[should_panic(expected = "GTD not supported for Market orders")]
572 fn test_gtd_condition(audusd_sim: CurrencyPair) {
573 let _ = OrderTestBuilder::new(OrderType::Market)
574 .instrument_id(audusd_sim.id)
575 .side(OrderSide::Buy)
576 .quantity(Quantity::from(100))
577 .time_in_force(TimeInForce::Gtd)
578 .build();
579 }
580 #[rstest]
581 fn test_market_order_creation(audusd_sim: CurrencyPair) {
582 let order = OrderTestBuilder::new(OrderType::Market)
584 .instrument_id(audusd_sim.id)
585 .quantity(Quantity::from(10))
586 .side(OrderSide::Buy)
587 .time_in_force(TimeInForce::Ioc)
588 .build();
589
590 assert_eq!(order.time_in_force(), TimeInForce::Ioc);
592 assert_eq!(order.order_type(), OrderType::Market);
593 assert!(order.price().is_none());
594 }
595
596 #[rstest]
597 fn test_market_order_update(audusd_sim: CurrencyPair) {
598 let order = OrderTestBuilder::new(OrderType::Market)
600 .instrument_id(audusd_sim.id)
601 .quantity(Quantity::from(10))
602 .side(OrderSide::Buy)
603 .build();
604
605 let mut accepted_order = TestOrderStubs::make_accepted_order(&order);
606
607 let updated_quantity = Quantity::from(5);
609
610 let event = OrderUpdated {
611 client_order_id: accepted_order.client_order_id(),
612 strategy_id: accepted_order.strategy_id(),
613 quantity: updated_quantity,
614 ..Default::default()
615 };
616
617 accepted_order.apply(OrderEventAny::Updated(event)).unwrap();
618
619 assert_eq!(accepted_order.quantity(), updated_quantity);
621 }
622
623 #[rstest]
624 fn test_market_order_from_order_initialized(audusd_sim: CurrencyPair) {
625 let order_initialized = OrderInitializedBuilder::default()
627 .order_type(OrderType::Market)
628 .instrument_id(audusd_sim.id)
629 .quantity(Quantity::from(10))
630 .order_side(OrderSide::Buy)
631 .build()
632 .unwrap();
633
634 let order: MarketOrder = order_initialized.clone().into();
636
637 assert_eq!(order.trader_id(), order_initialized.trader_id);
639 assert_eq!(order.strategy_id(), order_initialized.strategy_id);
640 assert_eq!(order.instrument_id(), order_initialized.instrument_id);
641 assert_eq!(order.client_order_id(), order_initialized.client_order_id);
642 assert_eq!(order.order_side(), order_initialized.order_side);
643 assert_eq!(order.quantity(), order_initialized.quantity);
644 }
645
646 #[rstest]
647 #[should_panic(
648 expected = "Condition failed: invalid `Quantity` for 'quantity' not positive, was 0"
649 )]
650 fn test_market_order_invalid_quantity(audusd_sim: CurrencyPair) {
651 let _ = OrderTestBuilder::new(OrderType::Market)
652 .instrument_id(audusd_sim.id)
653 .quantity(Quantity::from(0))
654 .side(OrderSide::Buy)
655 .build();
656 }
657
658 #[rstest]
659 fn test_display(audusd_sim: CurrencyPair) {
660 let order = OrderTestBuilder::new(OrderType::Market)
661 .instrument_id(audusd_sim.id)
662 .quantity(Quantity::from(10))
663 .side(OrderSide::Buy)
664 .build();
665
666 assert_eq!(
668 order.to_string(),
669 format!(
670 "MarketOrder({} {} {} @ {} {}, status=INITIALIZED, client_order_id={}, venue_order_id=None, position_id=None, exec_algorithm_id=None, exec_spawn_id=None, tags=None)",
671 order.order_side(),
672 order.quantity().to_formatted_string(),
673 order.instrument_id(),
674 order.order_type(),
675 order.time_in_force(),
676 order.client_order_id()
677 )
678 );
679 }
680
681 #[rstest]
682 fn test_stop_market_order_protection_price_update(audusd_sim: CurrencyPair) {
683 let order = OrderTestBuilder::new(OrderType::Market)
685 .instrument_id(audusd_sim.id)
686 .quantity(Quantity::from(10))
687 .side(OrderSide::Buy)
688 .build();
689
690 let mut accepted_order = TestOrderStubs::make_accepted_order(&order);
691
692 let calculated_protection_price = Price::new(95.0, 2);
694
695 let event = OrderUpdated {
696 client_order_id: accepted_order.client_order_id(),
697 strategy_id: accepted_order.strategy_id(),
698 protection_price: Some(calculated_protection_price),
699 ..Default::default()
700 };
701
702 assert_eq!(accepted_order.price(), None);
703 assert!(!accepted_order.has_price());
704
705 accepted_order.apply(OrderEventAny::Updated(event)).unwrap();
706
707 assert_eq!(accepted_order.price(), Some(calculated_protection_price));
709 assert!(accepted_order.has_price());
710 }
711}