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