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