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