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