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