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