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