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