nautilus_model/orders/
market.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use 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    /// Creates a new [`MarketOrder`] instance.
56    #[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    /// Creates a new [`MarketOrder`] instance.
103    #[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////////////////////////////////////////////////////////////////////////////////
529// Tests
530////////////////////////////////////////////////////////////////////////////////
531#[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}