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