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}
53
54impl MarketOrder {
55 #[allow(clippy::too_many_arguments)]
57 pub fn new(
58 trader_id: TraderId,
59 strategy_id: StrategyId,
60 instrument_id: InstrumentId,
61 client_order_id: ClientOrderId,
62 order_side: OrderSide,
63 quantity: Quantity,
64 time_in_force: TimeInForce,
65 init_id: UUID4,
66 ts_init: UnixNanos,
67 reduce_only: bool,
68 quote_quantity: bool,
69 contingency_type: Option<ContingencyType>,
70 order_list_id: Option<OrderListId>,
71 linked_order_ids: Option<Vec<ClientOrderId>>,
72 parent_order_id: Option<ClientOrderId>,
73 exec_algorithm_id: Option<ExecAlgorithmId>,
74 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
75 exec_spawn_id: Option<ClientOrderId>,
76 tags: Option<Vec<Ustr>>,
77 ) -> Self {
78 Self::new_checked(
79 trader_id,
80 strategy_id,
81 instrument_id,
82 client_order_id,
83 order_side,
84 quantity,
85 time_in_force,
86 init_id,
87 ts_init,
88 reduce_only,
89 quote_quantity,
90 contingency_type,
91 order_list_id,
92 linked_order_ids,
93 parent_order_id,
94 exec_algorithm_id,
95 exec_algorithm_params,
96 exec_spawn_id,
97 tags,
98 )
99 .expect(FAILED)
100 }
101
102 #[allow(clippy::too_many_arguments)]
104 pub fn new_checked(
105 trader_id: TraderId,
106 strategy_id: StrategyId,
107 instrument_id: InstrumentId,
108 client_order_id: ClientOrderId,
109 order_side: OrderSide,
110 quantity: Quantity,
111 time_in_force: TimeInForce,
112 init_id: UUID4,
113 ts_init: UnixNanos,
114 reduce_only: bool,
115 quote_quantity: bool,
116 contingency_type: Option<ContingencyType>,
117 order_list_id: Option<OrderListId>,
118 linked_order_ids: Option<Vec<ClientOrderId>>,
119 parent_order_id: Option<ClientOrderId>,
120 exec_algorithm_id: Option<ExecAlgorithmId>,
121 exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
122 exec_spawn_id: Option<ClientOrderId>,
123 tags: Option<Vec<Ustr>>,
124 ) -> anyhow::Result<Self> {
125 check_positive_quantity(quantity, stringify!(quantity))?;
126 check_predicate_false(
127 time_in_force == TimeInForce::Gtd,
128 "GTD not supported for Market orders",
129 )?;
130 let init_order = OrderInitialized::new(
131 trader_id,
132 strategy_id,
133 instrument_id,
134 client_order_id,
135 order_side,
136 OrderType::Market,
137 quantity,
138 time_in_force,
139 false,
140 reduce_only,
141 quote_quantity,
142 false,
143 init_id,
144 ts_init,
145 ts_init,
146 None,
147 None,
148 Some(TriggerType::NoTrigger),
149 None,
150 None,
151 None,
152 None,
153 None,
154 None,
155 None,
156 contingency_type,
157 order_list_id,
158 linked_order_ids,
159 parent_order_id,
160 exec_algorithm_id,
161 exec_algorithm_params,
162 exec_spawn_id,
163 tags,
164 );
165
166 Ok(Self {
167 core: OrderCore::new(init_order),
168 })
169 }
170}
171
172impl Deref for MarketOrder {
173 type Target = OrderCore;
174
175 fn deref(&self) -> &Self::Target {
176 &self.core
177 }
178}
179
180impl DerefMut for MarketOrder {
181 fn deref_mut(&mut self) -> &mut Self::Target {
182 &mut self.core
183 }
184}
185
186impl PartialEq for MarketOrder {
187 fn eq(&self, other: &Self) -> bool {
188 self.client_order_id == other.client_order_id
189 }
190}
191
192impl Order for MarketOrder {
193 fn into_any(self) -> OrderAny {
194 OrderAny::Market(self)
195 }
196
197 fn status(&self) -> OrderStatus {
198 self.status
199 }
200
201 fn trader_id(&self) -> TraderId {
202 self.trader_id
203 }
204
205 fn strategy_id(&self) -> StrategyId {
206 self.strategy_id
207 }
208
209 fn instrument_id(&self) -> InstrumentId {
210 self.instrument_id
211 }
212
213 fn symbol(&self) -> Symbol {
214 self.instrument_id.symbol
215 }
216
217 fn venue(&self) -> Venue {
218 self.instrument_id.venue
219 }
220
221 fn client_order_id(&self) -> ClientOrderId {
222 self.client_order_id
223 }
224
225 fn venue_order_id(&self) -> Option<VenueOrderId> {
226 self.venue_order_id
227 }
228
229 fn position_id(&self) -> Option<PositionId> {
230 self.position_id
231 }
232
233 fn account_id(&self) -> Option<AccountId> {
234 self.account_id
235 }
236
237 fn last_trade_id(&self) -> Option<TradeId> {
238 self.last_trade_id
239 }
240
241 fn order_side(&self) -> OrderSide {
242 self.side
243 }
244
245 fn order_type(&self) -> OrderType {
246 self.order_type
247 }
248
249 fn quantity(&self) -> Quantity {
250 self.quantity
251 }
252
253 fn time_in_force(&self) -> TimeInForce {
254 self.time_in_force
255 }
256
257 fn expire_time(&self) -> Option<UnixNanos> {
258 None
259 }
260
261 fn price(&self) -> Option<Price> {
262 None
263 }
264
265 fn trigger_price(&self) -> Option<Price> {
266 None
267 }
268
269 fn trigger_type(&self) -> Option<TriggerType> {
270 None
271 }
272
273 fn liquidity_side(&self) -> Option<LiquiditySide> {
274 self.liquidity_side
275 }
276
277 fn is_post_only(&self) -> bool {
278 false
279 }
280
281 fn is_reduce_only(&self) -> bool {
282 self.is_reduce_only
283 }
284
285 fn is_quote_quantity(&self) -> bool {
286 self.is_quote_quantity
287 }
288
289 fn has_price(&self) -> bool {
290 false
291 }
292
293 fn display_qty(&self) -> Option<Quantity> {
294 None
295 }
296
297 fn limit_offset(&self) -> Option<Decimal> {
298 None
299 }
300
301 fn trailing_offset(&self) -> Option<Decimal> {
302 None
303 }
304
305 fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
306 None
307 }
308
309 fn emulation_trigger(&self) -> Option<TriggerType> {
310 None
311 }
312
313 fn trigger_instrument_id(&self) -> Option<InstrumentId> {
314 None
315 }
316
317 fn contingency_type(&self) -> Option<ContingencyType> {
318 self.contingency_type
319 }
320
321 fn order_list_id(&self) -> Option<OrderListId> {
322 self.order_list_id
323 }
324
325 fn linked_order_ids(&self) -> Option<&[ClientOrderId]> {
326 self.linked_order_ids.as_deref()
327 }
328
329 fn parent_order_id(&self) -> Option<ClientOrderId> {
330 self.parent_order_id
331 }
332
333 fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
334 self.exec_algorithm_id
335 }
336
337 fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>> {
338 self.exec_algorithm_params.as_ref()
339 }
340
341 fn exec_spawn_id(&self) -> Option<ClientOrderId> {
342 self.exec_spawn_id
343 }
344
345 fn tags(&self) -> Option<&[Ustr]> {
346 self.tags.as_deref()
347 }
348
349 fn filled_qty(&self) -> Quantity {
350 self.filled_qty
351 }
352
353 fn leaves_qty(&self) -> Quantity {
354 self.leaves_qty
355 }
356
357 fn avg_px(&self) -> Option<f64> {
358 self.avg_px
359 }
360
361 fn slippage(&self) -> Option<f64> {
362 self.slippage
363 }
364
365 fn init_id(&self) -> UUID4 {
366 self.init_id
367 }
368
369 fn ts_init(&self) -> UnixNanos {
370 self.ts_init
371 }
372
373 fn ts_submitted(&self) -> Option<UnixNanos> {
374 self.ts_submitted
375 }
376
377 fn ts_accepted(&self) -> Option<UnixNanos> {
378 self.ts_accepted
379 }
380
381 fn ts_closed(&self) -> Option<UnixNanos> {
382 self.ts_closed
383 }
384
385 fn ts_last(&self) -> UnixNanos {
386 self.ts_last
387 }
388
389 fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
390 if let OrderEventAny::Updated(ref event) = event {
391 self.update(event);
392 };
393
394 self.core.apply(event)?;
395
396 Ok(())
397 }
398
399 fn update(&mut self, event: &OrderUpdated) {
400 assert!(event.price.is_none(), "{}", OrderError::InvalidOrderEvent);
401 assert!(
402 event.trigger_price.is_none(),
403 "{}",
404 OrderError::InvalidOrderEvent
405 );
406
407 self.quantity = event.quantity;
408 self.leaves_qty = self.quantity - self.filled_qty;
409 }
410
411 fn events(&self) -> Vec<&OrderEventAny> {
412 self.events.iter().collect()
413 }
414
415 fn venue_order_ids(&self) -> Vec<&VenueOrderId> {
416 self.venue_order_ids.iter().collect()
417 }
418
419 fn trade_ids(&self) -> Vec<&TradeId> {
420 self.trade_ids.iter().collect()
421 }
422
423 fn commissions(&self) -> &IndexMap<Currency, Money> {
424 &self.commissions
425 }
426
427 fn is_triggered(&self) -> Option<bool> {
428 None
429 }
430
431 fn set_position_id(&mut self, position_id: Option<PositionId>) {
432 self.position_id = position_id;
433 }
434
435 fn set_quantity(&mut self, quantity: Quantity) {
436 self.quantity = quantity;
437 }
438
439 fn set_leaves_qty(&mut self, leaves_qty: Quantity) {
440 self.leaves_qty = leaves_qty;
441 }
442
443 fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>) {
444 self.emulation_trigger = emulation_trigger;
445 }
446
447 fn set_is_quote_quantity(&mut self, is_quote_quantity: bool) {
448 self.is_quote_quantity = is_quote_quantity;
449 }
450
451 fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide) {
452 self.liquidity_side = Some(liquidity_side)
453 }
454
455 fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
456 self.core.would_reduce_only(side, position_qty)
457 }
458
459 fn previous_status(&self) -> Option<OrderStatus> {
460 self.core.previous_status
461 }
462}
463
464impl Display for MarketOrder {
465 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466 write!(
467 f,
468 "MarketOrder(\
469 {} {} {} @ {} {}, \
470 status={}, \
471 client_order_id={}, \
472 venue_order_id={}, \
473 position_id={}, \
474 exec_algorithm_id={}, \
475 exec_spawn_id={}, \
476 tags={:?}\
477 )",
478 self.side,
479 self.quantity.to_formatted_string(),
480 self.instrument_id,
481 self.order_type,
482 self.time_in_force,
483 self.status,
484 self.client_order_id,
485 self.venue_order_id.map_or_else(
486 || "None".to_string(),
487 |venue_order_id| format!("{venue_order_id}")
488 ),
489 self.position_id.map_or_else(
490 || "None".to_string(),
491 |position_id| format!("{position_id}")
492 ),
493 self.exec_algorithm_id
494 .map_or_else(|| "None".to_string(), |id| format!("{id}")),
495 self.exec_spawn_id
496 .map_or_else(|| "None".to_string(), |id| format!("{id}")),
497 self.tags
498 )
499 }
500}
501
502impl From<OrderInitialized> for MarketOrder {
503 fn from(event: OrderInitialized) -> Self {
504 Self::new(
505 event.trader_id,
506 event.strategy_id,
507 event.instrument_id,
508 event.client_order_id,
509 event.order_side,
510 event.quantity,
511 event.time_in_force,
512 event.event_id,
513 event.ts_event,
514 event.reduce_only,
515 event.quote_quantity,
516 event.contingency_type,
517 event.order_list_id,
518 event.linked_order_ids,
519 event.parent_order_id,
520 event.exec_algorithm_id,
521 event.exec_algorithm_params,
522 event.exec_spawn_id,
523 event.tags,
524 )
525 }
526}
527
528#[cfg(test)]
532mod tests {
533 use rstest::rstest;
534
535 use crate::{
536 enums::{OrderSide, OrderType, TimeInForce},
537 instruments::{CurrencyPair, stubs::*},
538 orders::builder::OrderTestBuilder,
539 types::Quantity,
540 };
541
542 #[rstest]
543 #[should_panic(
544 expected = "Condition failed: invalid `Quantity` for 'quantity' not positive, was 0"
545 )]
546 fn test_positive_quantity_condition(audusd_sim: CurrencyPair) {
547 let _ = OrderTestBuilder::new(OrderType::Market)
548 .instrument_id(audusd_sim.id)
549 .side(OrderSide::Buy)
550 .quantity(Quantity::from(0))
551 .build();
552 }
553
554 #[rstest]
555 #[should_panic(expected = "GTD not supported for Market orders")]
556 fn test_gtd_condition(audusd_sim: CurrencyPair) {
557 let _ = OrderTestBuilder::new(OrderType::Market)
558 .instrument_id(audusd_sim.id)
559 .side(OrderSide::Buy)
560 .quantity(Quantity::from(100))
561 .time_in_force(TimeInForce::Gtd)
562 .build();
563 }
564}