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