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