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