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