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