1use indexmap::IndexMap;
17use nautilus_core::{
18 UUID4, UnixNanos,
19 python::{
20 IntoPyObjectNautilusExt,
21 parsing::{
22 get_optional, get_optional_parsed, get_required, get_required_parsed,
23 get_required_string,
24 },
25 to_pyruntime_err, to_pyvalue_err,
26 },
27};
28use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
29use rust_decimal::Decimal;
30use ustr::Ustr;
31
32use crate::{
33 enums::{
34 ContingencyType, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce, TriggerType,
35 },
36 events::order::initialized::OrderInitialized,
37 identifiers::{
38 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
39 },
40 orders::{Order, OrderCore, StopMarketOrder, str_indexmap_to_ustr},
41 python::{
42 common::commissions_from_indexmap,
43 events::order::{order_event_to_pyobject, pyobject_to_order_event},
44 },
45 types::{Currency, Money, Price, Quantity},
46};
47
48#[pymethods]
49#[pyo3_stub_gen::derive::gen_stub_pymethods]
50impl StopMarketOrder {
51 #[new]
53 #[expect(clippy::too_many_arguments)]
54 #[pyo3(signature = (trader_id, strategy_id, instrument_id, client_order_id, order_side, quantity, trigger_price, trigger_type, time_in_force, reduce_only, quote_quantity, init_id, ts_init, expire_time=None, display_qty=None, emulation_trigger=None, trigger_instrument_id=None, contingency_type=None, order_list_id=None, linked_order_ids=None, parent_order_id=None, exec_algorithm_id=None, exec_algorithm_params=None, exec_spawn_id=None, tags=None))]
55 fn py_new(
56 trader_id: TraderId,
57 strategy_id: StrategyId,
58 instrument_id: InstrumentId,
59 client_order_id: ClientOrderId,
60 order_side: OrderSide,
61 quantity: Quantity,
62 trigger_price: Price,
63 trigger_type: TriggerType,
64 time_in_force: TimeInForce,
65 reduce_only: bool,
66 quote_quantity: bool,
67 init_id: UUID4,
68 ts_init: u64,
69 expire_time: Option<u64>,
70 display_qty: Option<Quantity>,
71 emulation_trigger: Option<TriggerType>,
72 trigger_instrument_id: Option<InstrumentId>,
73 contingency_type: Option<ContingencyType>,
74 order_list_id: Option<OrderListId>,
75 linked_order_ids: Option<Vec<ClientOrderId>>,
76 parent_order_id: Option<ClientOrderId>,
77 exec_algorithm_id: Option<ExecAlgorithmId>,
78 exec_algorithm_params: Option<IndexMap<String, String>>,
79 exec_spawn_id: Option<ClientOrderId>,
80 tags: Option<Vec<String>>,
81 ) -> PyResult<Self> {
82 Self::new_checked(
83 trader_id,
84 strategy_id,
85 instrument_id,
86 client_order_id,
87 order_side,
88 quantity,
89 trigger_price,
90 trigger_type,
91 time_in_force,
92 expire_time.map(std::convert::Into::into),
93 reduce_only,
94 quote_quantity,
95 display_qty,
96 emulation_trigger,
97 trigger_instrument_id,
98 contingency_type,
99 order_list_id,
100 linked_order_ids,
101 parent_order_id,
102 exec_algorithm_id,
103 exec_algorithm_params.map(str_indexmap_to_ustr),
104 exec_spawn_id,
105 tags.map(|vec| vec.into_iter().map(|s| Ustr::from(s.as_str())).collect()),
106 init_id,
107 ts_init.into(),
108 )
109 .map_err(to_pyvalue_err)
110 }
111
112 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
113 match op {
114 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
115 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
116 _ => py.NotImplemented(),
117 }
118 }
119
120 fn __repr__(&self) -> String {
121 self.to_string()
122 }
123
124 fn __str__(&self) -> String {
125 self.to_string()
126 }
127
128 #[staticmethod]
129 #[pyo3(name = "create")]
130 fn py_create(init: OrderInitialized) -> Self {
131 Self::from(init)
132 }
133
134 #[staticmethod]
135 #[pyo3(name = "opposite_side")]
136 fn py_opposite_side(side: OrderSide) -> OrderSide {
137 OrderCore::opposite_side(side)
138 }
139
140 #[staticmethod]
141 #[pyo3(name = "closing_side")]
142 fn py_closing_side(side: PositionSide) -> OrderSide {
143 OrderCore::closing_side(side)
144 }
145
146 #[getter]
147 #[pyo3(name = "status")]
148 fn py_status(&self) -> OrderStatus {
149 self.status
150 }
151
152 #[getter]
153 #[pyo3(name = "trader_id")]
154 fn py_trader_id(&self) -> TraderId {
155 self.trader_id
156 }
157
158 #[getter]
159 #[pyo3(name = "strategy_id")]
160 fn py_strategy_id(&self) -> StrategyId {
161 self.strategy_id
162 }
163
164 #[getter]
165 #[pyo3(name = "instrument_id")]
166 fn py_instrument_id(&self) -> InstrumentId {
167 self.instrument_id
168 }
169
170 #[getter]
171 #[pyo3(name = "client_order_id")]
172 fn py_client_order_id(&self) -> ClientOrderId {
173 self.client_order_id
174 }
175
176 #[getter]
177 #[pyo3(name = "side")]
178 fn py_order_side(&self) -> OrderSide {
179 self.side
180 }
181
182 #[getter]
183 #[pyo3(name = "quantity")]
184 fn py_quantity(&self) -> Quantity {
185 self.quantity
186 }
187
188 #[getter]
189 #[pyo3(name = "trigger_price")]
190 fn py_trigger_price(&self) -> Price {
191 self.trigger_price
192 }
193
194 #[getter]
195 #[pyo3(name = "trigger_type")]
196 fn py_trigger_type(&self) -> TriggerType {
197 self.trigger_type
198 }
199
200 #[getter]
201 #[pyo3(name = "order_type")]
202 fn py_order_type(&self) -> OrderType {
203 self.order_type
204 }
205
206 #[getter]
207 #[pyo3(name = "time_in_force")]
208 fn py_time_in_force(&self) -> TimeInForce {
209 self.time_in_force
210 }
211
212 #[getter]
213 #[pyo3(name = "expire_time")]
214 fn py_expire_time(&self) -> Option<u64> {
215 self.expire_time.map(std::convert::Into::into)
216 }
217
218 #[getter]
219 #[pyo3(name = "init_id")]
220 fn py_init_id(&self) -> UUID4 {
221 self.init_id
222 }
223
224 #[getter]
225 #[pyo3(name = "ts_init")]
226 fn py_ts_init(&self) -> u64 {
227 self.ts_init.as_u64()
228 }
229
230 #[getter]
231 #[pyo3(name = "init_event")]
232 fn py_init_event(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
233 match self.init_event() {
234 Some(event) => order_event_to_pyobject(py, event),
235 None => Ok(py.None()),
236 }
237 }
238
239 #[getter]
240 #[pyo3(name = "has_price")]
241 fn py_has_price(&self) -> bool {
242 self.protection_price.is_some()
243 }
244
245 #[getter]
246 #[pyo3(name = "has_trigger_price")]
247 fn py_has_trigger_price(&self) -> bool {
248 true
249 }
250
251 #[getter]
252 #[pyo3(name = "is_passive")]
253 fn py_is_passive(&self) -> bool {
254 self.is_passive()
255 }
256
257 #[getter]
258 #[pyo3(name = "is_aggressive")]
259 fn py_is_aggressive(&self) -> bool {
260 self.is_aggressive()
261 }
262
263 #[getter]
264 #[pyo3(name = "is_closed")]
265 fn py_is_closed(&self) -> bool {
266 self.is_closed()
267 }
268
269 #[getter]
270 #[pyo3(name = "is_open")]
271 fn py_is_open(&self) -> bool {
272 self.is_open()
273 }
274
275 #[getter]
276 #[pyo3(name = "is_reduce_only")]
277 fn py_reduce_only(&self) -> bool {
278 self.is_reduce_only
279 }
280
281 #[getter]
282 #[pyo3(name = "is_quote_quantity")]
283 fn py_quote_quantity(&self) -> bool {
284 self.is_quote_quantity
285 }
286
287 #[getter]
288 #[pyo3(name = "display_qty")]
289 fn py_display_qty(&self) -> Option<Quantity> {
290 self.display_qty
291 }
292
293 #[getter]
294 #[pyo3(name = "emulation_trigger")]
295 fn py_emulation_trigger(&self) -> Option<TriggerType> {
296 self.emulation_trigger
297 }
298
299 #[getter]
300 #[pyo3(name = "trigger_instrument_id")]
301 fn py_trigger_instrument_id(&self) -> Option<InstrumentId> {
302 self.trigger_instrument_id
303 }
304
305 #[getter]
306 #[pyo3(name = "contingency_type")]
307 fn py_contingency_type(&self) -> Option<ContingencyType> {
308 self.contingency_type
309 }
310
311 #[getter]
312 #[pyo3(name = "order_list_id")]
313 fn py_order_list_id(&self) -> Option<OrderListId> {
314 self.order_list_id
315 }
316
317 #[getter]
318 #[pyo3(name = "linked_order_ids")]
319 fn py_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
320 self.linked_order_ids.clone()
321 }
322
323 #[getter]
324 #[pyo3(name = "parent_order_id")]
325 fn py_parent_order_id(&self) -> Option<ClientOrderId> {
326 self.parent_order_id
327 }
328
329 #[getter]
330 #[pyo3(name = "exec_algorithm_id")]
331 fn py_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
332 self.exec_algorithm_id
333 }
334
335 #[getter]
336 #[pyo3(name = "exec_algorithm_params")]
337 fn py_exec_algorithm_params(&self) -> Option<IndexMap<&str, &str>> {
338 self.exec_algorithm_params
339 .as_ref()
340 .map(|x| x.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect())
341 }
342
343 #[getter]
344 #[pyo3(name = "exec_spawn_id")]
345 fn py_exec_spawn_id(&self) -> Option<ClientOrderId> {
346 self.exec_spawn_id
347 }
348
349 #[getter]
350 #[pyo3(name = "tags")]
351 fn py_tags(&self) -> Option<Vec<&str>> {
352 self.tags
353 .as_ref()
354 .map(|vec| vec.iter().map(|s| s.as_str()).collect())
355 }
356
357 #[getter]
358 #[pyo3(name = "account_id")]
359 fn py_account_id(&self) -> Option<AccountId> {
360 self.account_id
361 }
362
363 #[pyo3(name = "commission")]
364 fn py_commission(&self, currency: &Currency) -> Option<Money> {
365 self.commission(currency)
366 }
367
368 #[pyo3(name = "commissions")]
369 fn py_commissions(&self) -> IndexMap<Currency, Money> {
370 self.commissions().clone()
371 }
372
373 #[pyo3(name = "events")]
374 fn py_events(&self, py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
375 self.events()
376 .into_iter()
377 .map(|event| order_event_to_pyobject(py, event.clone()))
378 .collect()
379 }
380
381 #[pyo3(name = "signed_decimal_qty")]
382 fn py_signed_decimal_qty(&self) -> Decimal {
383 self.signed_decimal_qty()
384 }
385
386 #[pyo3(name = "would_reduce_only")]
387 fn py_would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
388 self.would_reduce_only(side, position_qty)
389 }
390
391 #[pyo3(name = "apply")]
392 fn py_apply(&mut self, event: Py<PyAny>, py: Python<'_>) -> PyResult<()> {
393 let event_any = pyobject_to_order_event(py, event).unwrap();
394 self.apply(event_any).map_err(to_pyruntime_err)
395 }
396
397 #[staticmethod]
398 #[pyo3(name = "from_dict")]
399 fn py_from_dict(values: &Bound<'_, PyDict>) -> PyResult<Self> {
400 let trader_id = TraderId::from(get_required_string(values, "trader_id")?.as_str());
401 let strategy_id = StrategyId::from(get_required_string(values, "strategy_id")?.as_str());
402 let instrument_id = InstrumentId::from(get_required_string(values, "instrument_id")?);
403 let client_order_id =
404 ClientOrderId::from(get_required_string(values, "client_order_id")?.as_str());
405 let order_side = get_required_parsed(values, "side", |s| {
406 s.parse::<OrderSide>().map_err(|e| e.to_string())
407 })?;
408 let quantity = Quantity::from(get_required_string(values, "quantity")?.as_str());
409 let trigger_price = Price::from(get_required_string(values, "trigger_price")?.as_str());
410 let trigger_type = get_required_parsed(values, "trigger_type", |s| {
411 s.parse::<TriggerType>().map_err(|e| e.to_string())
412 })?;
413 let time_in_force = get_required_parsed(values, "time_in_force", |s| {
414 s.parse::<TimeInForce>().map_err(|e| e.to_string())
415 })?;
416 let reduce_only = get_required::<bool>(values, "is_reduce_only")?;
417 let quote_quantity = get_required::<bool>(values, "is_quote_quantity")?;
418 let expire_time = get_optional::<u64>(values, "expire_time_ns")?.map(UnixNanos::from);
419 let display_quantity =
420 get_optional_parsed(values, "display_qty", |s| Ok(Quantity::from(s.as_str())))?;
421 let emulation_trigger = get_optional_parsed(values, "emulation_trigger", |s| {
422 s.parse::<TriggerType>().map_err(|e| e.to_string())
423 })?;
424 let trigger_instrument_id = get_optional_parsed(values, "trigger_instrument_id", |s| {
425 s.parse::<InstrumentId>().map_err(|e| e.to_string())
426 })?;
427 let contingency_type = get_optional_parsed(values, "contingency_type", |s| {
428 s.parse::<ContingencyType>().map_err(|e| e.to_string())
429 })?;
430 let order_list_id = get_optional_parsed(values, "order_list_id", |s| {
431 Ok(OrderListId::from(s.as_str()))
432 })?;
433 let linked_order_ids =
434 get_optional::<Vec<String>>(values, "linked_order_ids")?.map(|vec| {
435 vec.iter()
436 .map(|s| ClientOrderId::from(s.as_str()))
437 .collect()
438 });
439 let parent_order_id = get_optional_parsed(values, "parent_order_id", |s| {
440 Ok(ClientOrderId::from(s.as_str()))
441 })?;
442 let exec_algorithm_id = get_optional_parsed(values, "exec_algorithm_id", |s| {
443 Ok(ExecAlgorithmId::from(s.as_str()))
444 })?;
445 let exec_algorithm_params =
446 get_optional::<IndexMap<String, String>>(values, "exec_algorithm_params")?
447 .map(str_indexmap_to_ustr);
448 let exec_spawn_id = get_optional_parsed(values, "exec_spawn_id", |s| {
449 Ok(ClientOrderId::from(s.as_str()))
450 })?;
451 let tags = get_optional::<Vec<String>>(values, "tags")?
452 .map(|vec| vec.iter().map(|s| Ustr::from(s)).collect());
453 let init_id = get_required_parsed(values, "init_id", |s| s.parse::<UUID4>())?;
454 let ts_init = get_required::<u64>(values, "ts_init")?;
455 let stop_market_order = Self::new(
456 trader_id,
457 strategy_id,
458 instrument_id,
459 client_order_id,
460 order_side,
461 quantity,
462 trigger_price,
463 trigger_type,
464 time_in_force,
465 expire_time,
466 reduce_only,
467 quote_quantity,
468 display_quantity,
469 emulation_trigger,
470 trigger_instrument_id,
471 contingency_type,
472 order_list_id,
473 linked_order_ids,
474 parent_order_id,
475 exec_algorithm_id,
476 exec_algorithm_params,
477 exec_spawn_id,
478 tags,
479 init_id,
480 ts_init.into(),
481 );
482 Ok(stop_market_order)
483 }
484
485 #[pyo3(name = "to_dict")]
486 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
487 let dict = PyDict::new(py);
488 dict.set_item("trader_id", self.trader_id.to_string())?;
489 dict.set_item("strategy_id", self.strategy_id.to_string())?;
490 dict.set_item("instrument_id", self.instrument_id.to_string())?;
491 dict.set_item("client_order_id", self.client_order_id.to_string())?;
492 dict.set_item("side", self.side.to_string())?;
493 dict.set_item("type", self.order_type.to_string())?;
494 dict.set_item("quantity", self.quantity.to_string())?;
495 dict.set_item("status", self.status.to_string())?;
496 dict.set_item("trigger_price", self.trigger_price.to_string())?;
497 dict.set_item("trigger_type", self.trigger_type.to_string())?;
498 dict.set_item("filled_qty", self.filled_qty.to_string())?;
499 dict.set_item("time_in_force", self.time_in_force.to_string())?;
500 dict.set_item("is_reduce_only", self.is_reduce_only)?;
501 dict.set_item("is_quote_quantity", self.is_quote_quantity)?;
502 dict.set_item("init_id", self.init_id.to_string())?;
503 dict.set_item(
504 "expire_time_ns",
505 self.expire_time.filter(|&t| t != 0).map(|t| t.as_u64()),
506 )?;
507 dict.set_item("ts_init", self.ts_init.as_u64())?;
508 dict.set_item("ts_last", self.ts_last.as_u64())?;
509 dict.set_item(
510 "commissions",
511 commissions_from_indexmap(py, &self.commissions)?,
512 )?;
513 self.last_trade_id.map_or_else(
514 || dict.set_item("last_trade_id", py.None()),
515 |x| dict.set_item("last_trade_id", x.to_string()),
516 )?;
517 self.avg_px.map_or_else(
518 || dict.set_item("avg_px", py.None()),
519 |x| dict.set_item("avg_px", x),
520 )?;
521 self.position_id.map_or_else(
522 || dict.set_item("position_id", py.None()),
523 |x| dict.set_item("position_id", x.to_string()),
524 )?;
525 self.liquidity_side.map_or_else(
526 || dict.set_item("liquidity_side", py.None()),
527 |x| dict.set_item("liquidity_side", x.to_string()),
528 )?;
529 self.slippage.map_or_else(
530 || dict.set_item("slippage", py.None()),
531 |x| dict.set_item("slippage", x),
532 )?;
533 self.account_id.map_or_else(
534 || dict.set_item("account_id", py.None()),
535 |x| dict.set_item("account_id", x.to_string()),
536 )?;
537 self.venue_order_id.map_or_else(
538 || dict.set_item("venue_order_id", py.None()),
539 |x| dict.set_item("venue_order_id", x.to_string()),
540 )?;
541 self.display_qty.map_or_else(
542 || dict.set_item("display_qty", py.None()),
543 |x| dict.set_item("display_qty", x.to_string()),
544 )?;
545 self.emulation_trigger.map_or_else(
546 || dict.set_item("emulation_trigger", py.None()),
547 |x| dict.set_item("emulation_trigger", x.to_string()),
548 )?;
549 self.trigger_instrument_id.map_or_else(
550 || dict.set_item("trigger_instrument_id", py.None()),
551 |x| dict.set_item("trigger_instrument_id", x.to_string()),
552 )?;
553 self.contingency_type.map_or_else(
554 || dict.set_item("contingency_type", py.None()),
555 |x| dict.set_item("contingency_type", x.to_string()),
556 )?;
557 self.order_list_id.map_or_else(
558 || dict.set_item("order_list_id", py.None()),
559 |x| dict.set_item("order_list_id", x.to_string()),
560 )?;
561 dict.set_item(
562 "linked_order_ids",
563 self.linked_order_ids
564 .as_ref()
565 .map(|x| x.iter().map(ToString::to_string).collect::<Vec<String>>()),
566 )?;
567 self.parent_order_id.map_or_else(
568 || dict.set_item("parent_order_id", py.None()),
569 |x| dict.set_item("parent_order_id", x.to_string()),
570 )?;
571 self.exec_algorithm_id.map_or_else(
572 || dict.set_item("exec_algorithm_id", py.None()),
573 |x| dict.set_item("exec_algorithm_id", x.to_string()),
574 )?;
575 dict.set_item(
576 "exec_algorithm_params",
577 self.exec_algorithm_params.as_ref().map(|x| {
578 x.iter()
579 .map(|(k, v)| (k.to_string(), v.to_string()))
580 .collect::<IndexMap<String, String>>()
581 }),
582 )?;
583 self.exec_spawn_id.map_or_else(
584 || dict.set_item("exec_spawn_id", py.None()),
585 |x| dict.set_item("exec_spawn_id", x.to_string()),
586 )?;
587 dict.set_item(
588 "tags",
589 self.tags
590 .as_ref()
591 .map(|vec| vec.iter().map(|s| s.to_string()).collect::<Vec<String>>()),
592 )?;
593 Ok(dict.into())
594 }
595}