nautilus_model/orders/
stop_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::ops::{Deref, DerefMut};
17
18use indexmap::IndexMap;
19use nautilus_core::{UUID4, UnixNanos};
20use rust_decimal::Decimal;
21use serde::{Deserialize, Serialize};
22use ustr::Ustr;
23
24use super::{Order, OrderAny, OrderCore};
25use crate::{
26    enums::{
27        ContingencyType, LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide,
28        TimeInForce, TrailingOffsetType, TriggerType,
29    },
30    events::{OrderEventAny, OrderInitialized, OrderUpdated},
31    identifiers::{
32        AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
33        StrategyId, Symbol, TradeId, TraderId, Venue, VenueOrderId,
34    },
35    orders::OrderError,
36    types::{Currency, Money, Price, Quantity},
37};
38
39#[derive(Clone, Debug, Serialize, Deserialize)]
40#[cfg_attr(
41    feature = "python",
42    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
43)]
44pub struct StopMarketOrder {
45    pub trigger_price: Price,
46    pub trigger_type: TriggerType,
47    pub expire_time: Option<UnixNanos>,
48    pub display_qty: Option<Quantity>,
49    pub trigger_instrument_id: Option<InstrumentId>,
50    pub is_triggered: bool,
51    pub ts_triggered: Option<UnixNanos>,
52    core: OrderCore,
53}
54
55impl StopMarketOrder {
56    /// Creates a new [`StopMarketOrder`] instance.
57    #[allow(clippy::too_many_arguments)]
58    pub fn new(
59        trader_id: TraderId,
60        strategy_id: StrategyId,
61        instrument_id: InstrumentId,
62        client_order_id: ClientOrderId,
63        order_side: OrderSide,
64        quantity: Quantity,
65        trigger_price: Price,
66        trigger_type: TriggerType,
67        time_in_force: TimeInForce,
68        expire_time: Option<UnixNanos>,
69        reduce_only: bool,
70        quote_quantity: bool,
71        display_qty: Option<Quantity>,
72        emulation_trigger: Option<TriggerType>,
73        trigger_instrument_id: Option<InstrumentId>,
74        contingency_type: Option<ContingencyType>,
75        order_list_id: Option<OrderListId>,
76        linked_order_ids: Option<Vec<ClientOrderId>>,
77        parent_order_id: Option<ClientOrderId>,
78        exec_algorithm_id: Option<ExecAlgorithmId>,
79        exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
80        exec_spawn_id: Option<ClientOrderId>,
81        tags: Option<Vec<Ustr>>,
82        init_id: UUID4,
83        ts_init: UnixNanos,
84    ) -> Self {
85        let init_order = OrderInitialized::new(
86            trader_id,
87            strategy_id,
88            instrument_id,
89            client_order_id,
90            order_side,
91            OrderType::StopMarket,
92            quantity,
93            time_in_force,
94            false,
95            reduce_only,
96            quote_quantity,
97            false,
98            init_id,
99            ts_init,
100            ts_init,
101            None,
102            Some(trigger_price),
103            Some(trigger_type),
104            None,
105            None,
106            None,
107            expire_time,
108            display_qty,
109            emulation_trigger,
110            trigger_instrument_id,
111            contingency_type,
112            order_list_id,
113            linked_order_ids,
114            parent_order_id,
115            exec_algorithm_id,
116            exec_algorithm_params,
117            exec_spawn_id,
118            tags,
119        );
120        Self {
121            core: OrderCore::new(init_order),
122            trigger_price,
123            trigger_type,
124            expire_time,
125            display_qty,
126            trigger_instrument_id,
127            is_triggered: false,
128            ts_triggered: None,
129        }
130    }
131}
132
133impl Deref for StopMarketOrder {
134    type Target = OrderCore;
135
136    fn deref(&self) -> &Self::Target {
137        &self.core
138    }
139}
140
141impl DerefMut for StopMarketOrder {
142    fn deref_mut(&mut self) -> &mut Self::Target {
143        &mut self.core
144    }
145}
146
147impl Order for StopMarketOrder {
148    fn into_any(self) -> OrderAny {
149        OrderAny::StopMarket(self)
150    }
151
152    fn status(&self) -> OrderStatus {
153        self.status
154    }
155
156    fn trader_id(&self) -> TraderId {
157        self.trader_id
158    }
159
160    fn strategy_id(&self) -> StrategyId {
161        self.strategy_id
162    }
163
164    fn instrument_id(&self) -> InstrumentId {
165        self.instrument_id
166    }
167
168    fn symbol(&self) -> Symbol {
169        self.instrument_id.symbol
170    }
171
172    fn venue(&self) -> Venue {
173        self.instrument_id.venue
174    }
175
176    fn client_order_id(&self) -> ClientOrderId {
177        self.client_order_id
178    }
179
180    fn venue_order_id(&self) -> Option<VenueOrderId> {
181        self.venue_order_id
182    }
183
184    fn position_id(&self) -> Option<PositionId> {
185        self.position_id
186    }
187
188    fn account_id(&self) -> Option<AccountId> {
189        self.account_id
190    }
191
192    fn last_trade_id(&self) -> Option<TradeId> {
193        self.last_trade_id
194    }
195
196    fn order_side(&self) -> OrderSide {
197        self.side
198    }
199
200    fn order_type(&self) -> OrderType {
201        self.order_type
202    }
203
204    fn quantity(&self) -> Quantity {
205        self.quantity
206    }
207
208    fn time_in_force(&self) -> TimeInForce {
209        self.time_in_force
210    }
211
212    fn expire_time(&self) -> Option<UnixNanos> {
213        self.expire_time
214    }
215
216    fn price(&self) -> Option<Price> {
217        None
218    }
219
220    fn trigger_price(&self) -> Option<Price> {
221        Some(self.trigger_price)
222    }
223
224    fn trigger_type(&self) -> Option<TriggerType> {
225        Some(self.trigger_type)
226    }
227
228    fn liquidity_side(&self) -> Option<LiquiditySide> {
229        self.liquidity_side
230    }
231
232    fn is_post_only(&self) -> bool {
233        false
234    }
235
236    fn is_reduce_only(&self) -> bool {
237        self.is_reduce_only
238    }
239
240    fn is_quote_quantity(&self) -> bool {
241        self.is_quote_quantity
242    }
243
244    fn has_price(&self) -> bool {
245        false
246    }
247
248    fn display_qty(&self) -> Option<Quantity> {
249        self.display_qty
250    }
251
252    fn limit_offset(&self) -> Option<Decimal> {
253        None
254    }
255
256    fn trailing_offset(&self) -> Option<Decimal> {
257        None
258    }
259
260    fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
261        None
262    }
263
264    fn emulation_trigger(&self) -> Option<TriggerType> {
265        self.emulation_trigger
266    }
267
268    fn trigger_instrument_id(&self) -> Option<InstrumentId> {
269        self.trigger_instrument_id
270    }
271
272    fn contingency_type(&self) -> Option<ContingencyType> {
273        self.contingency_type
274    }
275
276    fn order_list_id(&self) -> Option<OrderListId> {
277        self.order_list_id
278    }
279
280    fn linked_order_ids(&self) -> Option<&[ClientOrderId]> {
281        self.linked_order_ids.as_deref()
282    }
283
284    fn parent_order_id(&self) -> Option<ClientOrderId> {
285        self.parent_order_id
286    }
287
288    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
289        self.exec_algorithm_id
290    }
291
292    fn exec_algorithm_params(&self) -> Option<&IndexMap<Ustr, Ustr>> {
293        self.exec_algorithm_params.as_ref()
294    }
295
296    fn exec_spawn_id(&self) -> Option<ClientOrderId> {
297        self.exec_spawn_id
298    }
299
300    fn tags(&self) -> Option<&[Ustr]> {
301        self.tags.as_deref()
302    }
303
304    fn filled_qty(&self) -> Quantity {
305        self.filled_qty
306    }
307
308    fn leaves_qty(&self) -> Quantity {
309        self.leaves_qty
310    }
311
312    fn avg_px(&self) -> Option<f64> {
313        self.avg_px
314    }
315
316    fn slippage(&self) -> Option<f64> {
317        self.slippage
318    }
319
320    fn init_id(&self) -> UUID4 {
321        self.init_id
322    }
323
324    fn ts_init(&self) -> UnixNanos {
325        self.ts_init
326    }
327
328    fn ts_submitted(&self) -> Option<UnixNanos> {
329        self.ts_submitted
330    }
331
332    fn ts_accepted(&self) -> Option<UnixNanos> {
333        self.ts_accepted
334    }
335
336    fn ts_closed(&self) -> Option<UnixNanos> {
337        self.ts_closed
338    }
339
340    fn ts_last(&self) -> UnixNanos {
341        self.ts_last
342    }
343
344    fn events(&self) -> Vec<&OrderEventAny> {
345        self.events.iter().collect()
346    }
347
348    fn venue_order_ids(&self) -> Vec<&VenueOrderId> {
349        self.venue_order_ids.iter().collect()
350    }
351
352    fn trade_ids(&self) -> Vec<&TradeId> {
353        self.trade_ids.iter().collect()
354    }
355
356    fn commissions(&self) -> &IndexMap<Currency, Money> {
357        &self.commissions
358    }
359
360    fn apply(&mut self, event: OrderEventAny) -> Result<(), OrderError> {
361        if let OrderEventAny::Updated(ref event) = event {
362            self.update(event);
363        };
364        let is_order_filled = matches!(event, OrderEventAny::Filled(_));
365
366        self.core.apply(event)?;
367
368        if is_order_filled {
369            self.core.set_slippage(self.trigger_price);
370        };
371
372        Ok(())
373    }
374
375    fn update(&mut self, event: &OrderUpdated) {
376        assert!(event.price.is_none(), "{}", OrderError::InvalidOrderEvent);
377
378        if let Some(trigger_price) = event.trigger_price {
379            self.trigger_price = trigger_price;
380        }
381
382        self.quantity = event.quantity;
383        self.leaves_qty = self.quantity - self.filled_qty;
384    }
385
386    fn is_triggered(&self) -> Option<bool> {
387        Some(self.is_triggered)
388    }
389
390    fn set_position_id(&mut self, position_id: Option<PositionId>) {
391        self.position_id = position_id;
392    }
393
394    fn set_quantity(&mut self, quantity: Quantity) {
395        self.quantity = quantity;
396    }
397
398    fn set_leaves_qty(&mut self, leaves_qty: Quantity) {
399        self.leaves_qty = leaves_qty;
400    }
401
402    fn set_emulation_trigger(&mut self, emulation_trigger: Option<TriggerType>) {
403        self.emulation_trigger = emulation_trigger;
404    }
405
406    fn set_is_quote_quantity(&mut self, is_quote_quantity: bool) {
407        self.is_quote_quantity = is_quote_quantity;
408    }
409
410    fn set_liquidity_side(&mut self, liquidity_side: LiquiditySide) {
411        self.liquidity_side = Some(liquidity_side)
412    }
413
414    fn would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
415        self.core.would_reduce_only(side, position_qty)
416    }
417
418    fn previous_status(&self) -> Option<OrderStatus> {
419        self.core.previous_status
420    }
421}
422
423impl From<OrderInitialized> for StopMarketOrder {
424    fn from(event: OrderInitialized) -> Self {
425        Self::new(
426            event.trader_id,
427            event.strategy_id,
428            event.instrument_id,
429            event.client_order_id,
430            event.order_side,
431            event.quantity,
432            event
433                .trigger_price // TODO: Improve this error, model order domain errors
434                .expect(
435                    "Error initializing order: `trigger_price` was `None` for `StopMarketOrder`",
436                ),
437            event.trigger_type.expect(
438                "Error initializing order: `trigger_type` was `None` for `StopMarketOrder`",
439            ),
440            event.time_in_force,
441            event.expire_time,
442            event.reduce_only,
443            event.quote_quantity,
444            event.display_qty,
445            event.emulation_trigger,
446            event.trigger_instrument_id,
447            event.contingency_type,
448            event.order_list_id,
449            event.linked_order_ids,
450            event.parent_order_id,
451            event.exec_algorithm_id,
452            event.exec_algorithm_params,
453            event.exec_spawn_id,
454            event.tags,
455            event.event_id,
456            event.ts_event,
457        )
458    }
459}