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