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