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