1use indexmap::IndexMap;
17use nautilus_core::{
18 UUID4,
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::{
26 Bound, Py, PyAny, PyResult, Python,
27 basic::CompareOp,
28 pymethods,
29 types::{PyAnyMethods, PyDict, PyList},
30};
31use rust_decimal::Decimal;
32use ustr::Ustr;
33
34use crate::{
35 enums::{ContingencyType, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce},
36 events::OrderInitialized,
37 identifiers::{
38 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
39 },
40 orders::{MarketOrder, 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, Quantity},
46};
47
48#[pymethods]
49#[pyo3_stub_gen::derive::gen_stub_pymethods]
50impl MarketOrder {
51 #[new]
53 #[expect(clippy::too_many_arguments)]
54 #[pyo3(signature = (trader_id, strategy_id, instrument_id, client_order_id, order_side, quantity, init_id, ts_init, time_in_force, reduce_only, quote_quantity, 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 init_id: UUID4,
63 ts_init: u64,
64 time_in_force: TimeInForce,
65 reduce_only: bool,
66 quote_quantity: bool,
67 contingency_type: Option<ContingencyType>,
68 order_list_id: Option<OrderListId>,
69 linked_order_ids: Option<Vec<ClientOrderId>>,
70 parent_order_id: Option<ClientOrderId>,
71 exec_algorithm_id: Option<ExecAlgorithmId>,
72 exec_algorithm_params: Option<IndexMap<String, String>>,
73 exec_spawn_id: Option<ClientOrderId>,
74 tags: Option<Vec<String>>,
75 ) -> PyResult<Self> {
76 Self::new_checked(
77 trader_id,
78 strategy_id,
79 instrument_id,
80 client_order_id,
81 order_side,
82 quantity,
83 time_in_force,
84 init_id,
85 ts_init.into(),
86 reduce_only,
87 quote_quantity,
88 contingency_type,
89 order_list_id,
90 linked_order_ids,
91 parent_order_id,
92 exec_algorithm_id,
93 exec_algorithm_params.map(str_indexmap_to_ustr),
94 exec_spawn_id,
95 tags.map(|vec| vec.into_iter().map(|s| Ustr::from(s.as_str())).collect()),
96 )
97 .map_err(to_pyvalue_err)
98 }
99
100 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
101 match op {
102 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
103 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
104 _ => py.NotImplemented(),
105 }
106 }
107
108 fn __repr__(&self) -> String {
109 self.to_string()
110 }
111
112 fn __str__(&self) -> String {
113 self.to_string()
114 }
115
116 #[staticmethod]
117 #[pyo3(name = "create")]
118 fn py_create(init: OrderInitialized) -> PyResult<Self> {
119 Self::try_from(init).map_err(to_pyvalue_err)
120 }
121
122 #[staticmethod]
123 #[pyo3(name = "opposite_side")]
124 fn py_opposite_side(side: OrderSide) -> OrderSide {
125 OrderCore::opposite_side(side)
126 }
127
128 #[staticmethod]
129 #[pyo3(name = "closing_side")]
130 fn py_closing_side(side: PositionSide) -> Option<OrderSide> {
131 OrderCore::closing_side(side)
132 }
133
134 #[getter]
135 #[pyo3(name = "status")]
136 fn py_status(&self) -> OrderStatus {
137 self.status
138 }
139
140 #[pyo3(name = "commission")]
141 fn py_commission(&self, currency: &Currency) -> Option<Money> {
142 self.commission(currency)
143 }
144
145 #[pyo3(name = "commissions")]
146 fn py_commissions(&self) -> IndexMap<Currency, Money> {
147 self.commissions().clone()
148 }
149
150 #[getter]
151 #[pyo3(name = "account_id")]
152 fn py_account_id(&self) -> Option<AccountId> {
153 self.account_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 = "trader_id")]
164 fn py_trader_id(&self) -> TraderId {
165 self.trader_id
166 }
167
168 #[getter]
169 #[pyo3(name = "strategy_id")]
170 fn py_strategy_id(&self) -> StrategyId {
171 self.strategy_id
172 }
173
174 #[getter]
175 #[pyo3(name = "init_id")]
176 fn py_init_id(&self) -> UUID4 {
177 self.init_id
178 }
179
180 #[getter]
181 #[pyo3(name = "ts_init")]
182 fn py_ts_init(&self) -> u64 {
183 self.ts_init.as_u64()
184 }
185
186 #[getter]
187 #[pyo3(name = "client_order_id")]
188 fn py_client_order_id(&self) -> ClientOrderId {
189 self.client_order_id
190 }
191
192 #[getter]
193 #[pyo3(name = "order_list_id")]
194 fn py_order_list_id(&self) -> Option<OrderListId> {
195 self.order_list_id
196 }
197
198 #[getter]
199 #[pyo3(name = "linked_order_ids")]
200 fn py_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
201 self.linked_order_ids.clone()
202 }
203
204 #[getter]
205 #[pyo3(name = "parent_order_id")]
206 fn py_parent_order_id(&self) -> Option<ClientOrderId> {
207 self.parent_order_id
208 }
209
210 #[getter]
211 #[pyo3(name = "exec_algorithm_id")]
212 fn py_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
213 self.exec_algorithm_id
214 }
215
216 #[getter]
217 #[pyo3(name = "exec_algorithm_params")]
218 fn py_exec_algorithm_params(&self) -> Option<IndexMap<&str, &str>> {
219 self.exec_algorithm_params
220 .as_ref()
221 .map(|x| x.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect())
222 }
223
224 #[getter]
225 #[pyo3(name = "exec_spawn_id")]
226 fn py_exec_spawn_id(&self) -> Option<ClientOrderId> {
227 self.exec_spawn_id
228 }
229
230 #[getter]
231 #[pyo3(name = "is_reduce_only")]
232 fn py_is_reduce_only(&self) -> bool {
233 self.is_reduce_only
234 }
235
236 #[getter]
237 #[pyo3(name = "is_quote_quantity")]
238 fn py_is_quote_quantity(&self) -> bool {
239 self.is_quote_quantity
240 }
241
242 #[getter]
243 #[pyo3(name = "contingency_type")]
244 fn py_contingency_type(&self) -> Option<ContingencyType> {
245 self.contingency_type
246 }
247
248 #[getter]
249 #[pyo3(name = "quantity")]
250 fn py_quantity(&self) -> Quantity {
251 self.quantity
252 }
253
254 #[getter]
255 #[pyo3(name = "side")]
256 fn py_side(&self) -> OrderSide {
257 self.side
258 }
259
260 #[getter]
261 #[pyo3(name = "order_type")]
262 fn py_order_type(&self) -> OrderType {
263 self.order_type
264 }
265
266 #[getter]
267 #[pyo3(name = "emulation_trigger")]
268 fn py_emulation_trigger(&self) -> Option<String> {
269 self.emulation_trigger.map(|x| x.to_string())
270 }
271
272 #[getter]
273 #[pyo3(name = "time_in_force")]
274 fn py_time_in_force(&self) -> TimeInForce {
275 self.time_in_force
276 }
277
278 #[getter]
279 #[pyo3(name = "tags")]
280 fn py_tags(&self) -> Option<Vec<&str>> {
281 self.tags
282 .as_ref()
283 .map(|vec| vec.iter().map(Ustr::as_str).collect())
284 }
285
286 #[pyo3(name = "events")]
287 fn py_events(&self, py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
288 self.events()
289 .into_iter()
290 .map(|event| order_event_to_pyobject(py, event.clone()))
291 .collect()
292 }
293
294 #[pyo3(name = "signed_decimal_qty")]
295 fn py_signed_decimal_qty(&self) -> Decimal {
296 self.signed_decimal_qty()
297 }
298
299 #[pyo3(name = "would_reduce_only")]
300 fn py_would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
301 self.would_reduce_only(side, position_qty)
302 }
303
304 #[pyo3(name = "apply")]
305 fn py_apply(&mut self, event: Py<PyAny>, py: Python<'_>) -> PyResult<()> {
306 let event_any = pyobject_to_order_event(py, event)?;
307 self.apply(event_any).map_err(to_pyruntime_err)
308 }
309
310 #[staticmethod]
311 #[pyo3(name = "from_dict")]
312 fn py_from_dict(values: &Bound<'_, PyDict>) -> PyResult<Self> {
313 let trader_id = get_required_parsed(values, "trader_id", |s| {
314 TraderId::new_checked(s).map_err(|e| e.to_string())
315 })?;
316
317 let strategy_id = get_required_parsed(values, "strategy_id", |s| {
318 StrategyId::new_checked(s).map_err(|e| e.to_string())
319 })?;
320
321 let instrument_id = get_required_parsed(values, "instrument_id", |s| {
322 s.parse::<InstrumentId>().map_err(|e| e.to_string())
323 })?;
324
325 let client_order_id = get_required_parsed(values, "client_order_id", |s| {
326 ClientOrderId::new_checked(s).map_err(|e| e.to_string())
327 })?;
328
329 let order_side = get_required_parsed(values, "side", |s| {
330 s.parse::<OrderSide>().map_err(|e| e.to_string())
331 })?;
332
333 let quantity = get_required_parsed(values, "quantity", |s| s.parse::<Quantity>())?;
334
335 let time_in_force = get_required_parsed(values, "time_in_force", |s| {
336 s.parse::<TimeInForce>().map_err(|e| e.to_string())
337 })?;
338 let init_id = get_required_parsed(values, "init_id", |s| s.parse::<UUID4>())?;
339 let ts_init = get_required::<u64>(values, "ts_init")?;
340 let is_reduce_only = get_required::<bool>(values, "is_reduce_only")?;
341 let is_quote_quantity = get_required::<bool>(values, "is_quote_quantity")?;
342 let contingency_type = get_optional_parsed(values, "contingency_type", |s| {
343 s.parse::<ContingencyType>().map_err(|e| e.to_string())
344 })?;
345
346 let order_list_id = get_optional_parsed(values, "order_list_id", |s| {
347 OrderListId::new_checked(s).map_err(|e| e.to_string())
348 })?;
349
350 let linked_order_ids = get_optional::<Vec<String>>(values, "linked_order_ids")?
351 .map(|vec| {
352 vec.into_iter()
353 .map(ClientOrderId::new_checked)
354 .collect::<Result<Vec<_>, _>>()
355 })
356 .transpose()
357 .map_err(|e| to_pyvalue_err(format!("Failed to parse 'linked_order_ids': {e}")))?;
358
359 let parent_order_id = get_optional_parsed(values, "parent_order_id", |s| {
360 ClientOrderId::new_checked(s).map_err(|e| e.to_string())
361 })?;
362
363 let exec_algorithm_id = get_optional_parsed(values, "exec_algorithm_id", |s| {
364 ExecAlgorithmId::new_checked(s).map_err(|e| e.to_string())
365 })?;
366 let exec_algorithm_params =
367 get_optional::<IndexMap<String, String>>(values, "exec_algorithm_params")?
368 .map(str_indexmap_to_ustr);
369
370 let exec_spawn_id = get_optional_parsed(values, "exec_spawn_id", |s| {
371 ClientOrderId::new_checked(s).map_err(|e| e.to_string())
372 })?;
373 let tags = get_optional::<Vec<String>>(values, "tags")?
374 .map(|vec| vec.iter().map(|s| Ustr::from(s)).collect());
375 Self::new_checked(
376 trader_id,
377 strategy_id,
378 instrument_id,
379 client_order_id,
380 order_side,
381 quantity,
382 time_in_force,
383 init_id,
384 ts_init.into(),
385 is_reduce_only,
386 is_quote_quantity,
387 contingency_type,
388 order_list_id,
389 linked_order_ids,
390 parent_order_id,
391 exec_algorithm_id,
392 exec_algorithm_params,
393 exec_spawn_id,
394 tags,
395 )
396 .map_err(to_pyvalue_err)
397 }
398
399 #[pyo3(name = "to_dict")]
400 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
401 let dict = PyDict::new(py);
402 dict.set_item("trader_id", self.trader_id.to_string())?;
403 dict.set_item("strategy_id", self.strategy_id.to_string())?;
404 dict.set_item("instrument_id", self.instrument_id.to_string())?;
405 dict.set_item("client_order_id", self.client_order_id.to_string())?;
406 dict.set_item("side", self.side.to_string())?;
407 dict.set_item("type", self.order_type.to_string())?;
408 dict.set_item("quantity", self.quantity.to_string())?;
409 dict.set_item("status", self.status.to_string())?;
410 dict.set_item("time_in_force", self.time_in_force.to_string())?;
411 dict.set_item("is_reduce_only", self.is_reduce_only)?;
412 dict.set_item("is_quote_quantity", self.is_quote_quantity)?;
413 dict.set_item("filled_qty", self.filled_qty.to_string())?;
414 dict.set_item("init_id", self.init_id.to_string())?;
415 dict.set_item("ts_init", self.ts_init.as_u64())?;
416 dict.set_item("ts_last", self.ts_last.as_u64())?;
417 dict.set_item(
418 "commissions",
419 commissions_from_indexmap(py, self.commissions())?,
420 )?;
421 self.venue_order_id.map_or_else(
422 || dict.set_item("venue_order_id", py.None()),
423 |x| dict.set_item("venue_order_id", x.to_string()),
424 )?;
425 self.emulation_trigger.map_or_else(
426 || dict.set_item("emulation_trigger", py.None()),
427 |x| dict.set_item("emulation_trigger", x.to_string()),
428 )?;
429 self.contingency_type.map_or_else(
430 || dict.set_item("contingency_type", py.None()),
431 |x| dict.set_item("contingency_type", x.to_string()),
432 )?;
433 self.order_list_id.map_or_else(
434 || dict.set_item("order_list_id", py.None()),
435 |x| dict.set_item("order_list_id", x.to_string()),
436 )?;
437 self.linked_order_ids.clone().map_or_else(
438 || dict.set_item("linked_order_ids", py.None()),
439 |linked_order_ids| {
440 let linked_order_ids_list =
441 PyList::new(py, linked_order_ids.iter().map(ToString::to_string))?;
442 dict.set_item("linked_order_ids", linked_order_ids_list)
443 },
444 )?;
445 self.parent_order_id.map_or_else(
446 || dict.set_item("parent_order_id", py.None()),
447 |x| dict.set_item("parent_order_id", x.to_string()),
448 )?;
449 self.exec_algorithm_id.map_or_else(
450 || dict.set_item("exec_algorithm_id", py.None()),
451 |x| dict.set_item("exec_algorithm_id", x.to_string()),
452 )?;
453
454 match &self.exec_algorithm_params {
455 Some(exec_algorithm_params) => {
456 let py_exec_algorithm_params = PyDict::new(py);
457 for (key, value) in exec_algorithm_params {
458 py_exec_algorithm_params.set_item(key.to_string(), value.to_string())?;
459 }
460 dict.set_item("exec_algorithm_params", py_exec_algorithm_params)?;
461 }
462 None => dict.set_item("exec_algorithm_params", py.None())?,
463 }
464 self.exec_spawn_id.map_or_else(
465 || dict.set_item("exec_spawn_id", py.None()),
466 |x| dict.set_item("exec_spawn_id", x.to_string()),
467 )?;
468 self.tags.clone().map_or_else(
469 || dict.set_item("tags", py.None()),
470 |x| {
471 dict.set_item(
472 "tags",
473 x.iter().map(ToString::to_string).collect::<Vec<String>>(),
474 )
475 },
476 )?;
477 self.account_id.map_or_else(
478 || dict.set_item("account_id", py.None()),
479 |x| dict.set_item("account_id", x.to_string()),
480 )?;
481 self.slippage.map_or_else(
482 || dict.set_item("slippage", py.None()),
483 |x| dict.set_item("slippage", x.to_string()),
484 )?;
485 self.position_id.map_or_else(
486 || dict.set_item("position_id", py.None()),
487 |x| dict.set_item("position_id", x.to_string()),
488 )?;
489 self.liquidity_side.map_or_else(
490 || dict.set_item("liquidity_side", py.None()),
491 |x| dict.set_item("liquidity_side", x.to_string()),
492 )?;
493 self.last_trade_id.map_or_else(
494 || dict.set_item("last_trade_id", py.None()),
495 |x| dict.set_item("last_trade_id", x.to_string()),
496 )?;
497 self.avg_px.map_or_else(
498 || dict.set_item("avg_px", py.None()),
499 |x| dict.set_item("avg_px", x.to_string()),
500 )?;
501 Ok(dict.into())
502 }
503}