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