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