1use indexmap::IndexMap;
17use nautilus_core::{
18 UUID4,
19 python::{
20 IntoPyObjectPoseiExt,
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::{
29 Bound, Py, PyAny, PyObject, PyResult, Python,
30 basic::CompareOp,
31 pymethods,
32 types::{PyAnyMethods, PyDict, PyList},
33};
34use rust_decimal::Decimal;
35use ustr::Ustr;
36
37use crate::{
38 enums::{ContingencyType, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce},
39 events::OrderInitialized,
40 identifiers::{
41 AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
42 },
43 orders::{MarketOrder, Order, OrderCore, str_indexmap_to_ustr},
44 python::{
45 common::commissions_from_indexmap,
46 events::order::{order_event_to_pyobject, pyobject_to_order_event},
47 },
48 types::{Currency, Money, Quantity},
49};
50
51#[pymethods]
52impl MarketOrder {
53 #[new]
54 #[allow(clippy::too_many_arguments)]
55 #[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))]
56 fn py_new(
57 trader_id: TraderId,
58 strategy_id: StrategyId,
59 instrument_id: InstrumentId,
60 client_order_id: ClientOrderId,
61 order_side: OrderSide,
62 quantity: Quantity,
63 init_id: UUID4,
64 ts_init: u64,
65 time_in_force: TimeInForce,
66 reduce_only: bool,
67 quote_quantity: bool,
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 init_id,
86 ts_init.into(),
87 reduce_only,
88 quote_quantity,
89 contingency_type,
90 order_list_id,
91 linked_order_ids,
92 parent_order_id,
93 exec_algorithm_id,
94 exec_algorithm_params.map(str_indexmap_to_ustr),
95 exec_spawn_id,
96 tags.map(|vec| vec.into_iter().map(|s| Ustr::from(s.as_str())).collect()),
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 Ok(MarketOrder::from(init))
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) -> 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 #[pyo3(name = "commission")]
142 fn py_commission(&self, currency: &Currency) -> Option<Money> {
143 self.commission(currency)
144 }
145
146 #[pyo3(name = "commissions")]
147 fn py_commissions(&self) -> IndexMap<Currency, Money> {
148 self.commissions().clone()
149 }
150
151 #[getter]
152 #[pyo3(name = "account_id")]
153 fn py_account_id(&self) -> Option<AccountId> {
154 self.account_id
155 }
156
157 #[getter]
158 #[pyo3(name = "instrument_id")]
159 fn py_instrument_id(&self) -> InstrumentId {
160 self.instrument_id
161 }
162
163 #[getter]
164 #[pyo3(name = "trader_id")]
165 fn py_trader_id(&self) -> TraderId {
166 self.trader_id
167 }
168
169 #[getter]
170 #[pyo3(name = "strategy_id")]
171 fn py_strategy_id(&self) -> StrategyId {
172 self.strategy_id
173 }
174
175 #[getter]
176 #[pyo3(name = "init_id")]
177 fn py_init_id(&self) -> UUID4 {
178 self.init_id
179 }
180
181 #[getter]
182 #[pyo3(name = "ts_init")]
183 fn py_ts_init(&self) -> u64 {
184 self.ts_init.as_u64()
185 }
186
187 #[getter]
188 #[pyo3(name = "client_order_id")]
189 fn py_client_order_id(&self) -> ClientOrderId {
190 self.client_order_id
191 }
192
193 #[getter]
194 #[pyo3(name = "order_list_id")]
195 fn py_order_list_id(&self) -> Option<OrderListId> {
196 self.order_list_id
197 }
198
199 #[getter]
200 #[pyo3(name = "linked_order_ids")]
201 fn py_linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
202 self.linked_order_ids.clone()
203 }
204
205 #[getter]
206 #[pyo3(name = "parent_order_id")]
207 fn py_parent_order_id(&self) -> Option<ClientOrderId> {
208 self.parent_order_id
209 }
210
211 #[getter]
212 #[pyo3(name = "exec_algorithm_id")]
213 fn py_exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
214 self.exec_algorithm_id
215 }
216
217 #[getter]
218 #[pyo3(name = "exec_algorithm_params")]
219 fn py_exec_algorithm_params(&self) -> Option<IndexMap<&str, &str>> {
220 self.exec_algorithm_params
221 .as_ref()
222 .map(|x| x.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect())
223 }
224
225 #[getter]
226 #[pyo3(name = "exec_spawn_id")]
227 fn py_exec_spawn_id(&self) -> Option<ClientOrderId> {
228 self.exec_spawn_id
229 }
230
231 #[getter]
232 #[pyo3(name = "is_reduce_only")]
233 fn py_is_reduce_only(&self) -> bool {
234 self.is_reduce_only
235 }
236
237 #[getter]
238 #[pyo3(name = "is_quote_quantity")]
239 fn py_is_quote_quantity(&self) -> bool {
240 self.is_quote_quantity
241 }
242
243 #[getter]
244 #[pyo3(name = "contingency_type")]
245 fn py_contingency_type(&self) -> Option<ContingencyType> {
246 self.contingency_type
247 }
248
249 #[getter]
250 #[pyo3(name = "quantity")]
251 fn py_quantity(&self) -> Quantity {
252 self.quantity
253 }
254
255 #[getter]
256 #[pyo3(name = "side")]
257 fn py_side(&self) -> OrderSide {
258 self.side
259 }
260
261 #[getter]
262 #[pyo3(name = "order_type")]
263 fn py_order_type(&self) -> OrderType {
264 self.order_type
265 }
266
267 #[getter]
268 #[pyo3(name = "emulation_trigger")]
269 fn py_emulation_trigger(&self) -> Option<String> {
270 self.emulation_trigger.map(|x| x.to_string())
271 }
272
273 #[getter]
274 #[pyo3(name = "time_in_force")]
275 fn py_time_in_force(&self) -> TimeInForce {
276 self.time_in_force
277 }
278
279 #[getter]
280 #[pyo3(name = "tags")]
281 fn py_tags(&self) -> Option<Vec<&str>> {
282 self.tags
283 .as_ref()
284 .map(|vec| vec.iter().map(|s| s.as_str()).collect())
285 }
286
287 #[getter]
288 #[pyo3(name = "events")]
289 fn py_events(&self, py: Python<'_>) -> PyResult<Vec<PyObject>> {
290 self.events()
291 .into_iter()
292 .map(|event| order_event_to_pyobject(py, event.clone()))
293 .collect()
294 }
295
296 #[pyo3(name = "signed_decimal_qty")]
297 fn py_signed_decimal_qty(&self) -> Decimal {
298 self.signed_decimal_qty()
299 }
300
301 #[pyo3(name = "would_reduce_only")]
302 fn py_would_reduce_only(&self, side: PositionSide, position_qty: Quantity) -> bool {
303 self.would_reduce_only(side, position_qty)
304 }
305
306 #[pyo3(name = "apply")]
307 fn py_apply(&mut self, event: PyObject, py: Python<'_>) -> PyResult<()> {
308 let event_any = pyobject_to_order_event(py, event).unwrap();
309 self.apply(event_any).map(|_| ()).map_err(to_pyruntime_err)
310 }
311
312 #[staticmethod]
313 #[pyo3(name = "from_dict")]
314 fn py_from_dict(values: &Bound<'_, PyDict>) -> PyResult<Self> {
315 let trader_id = TraderId::from(get_required_string(values, "trader_id")?.as_str());
316 let strategy_id = StrategyId::from(get_required_string(values, "strategy_id")?.as_str());
317 let instrument_id =
318 InstrumentId::from(get_required_string(values, "instrument_id")?.as_str());
319 let client_order_id =
320 ClientOrderId::from(get_required_string(values, "client_order_id")?.as_str());
321 let order_side = get_required_parsed(values, "side", |s| {
322 s.parse::<OrderSide>().map_err(|e| e.to_string())
323 })?;
324 let quantity = Quantity::from(get_required_string(values, "quantity")?.as_str());
325 let time_in_force = get_required_parsed(values, "time_in_force", |s| {
326 s.parse::<TimeInForce>().map_err(|e| e.to_string())
327 })?;
328 let init_id = get_required_parsed(values, "init_id", |s| {
329 s.parse::<UUID4>().map_err(|e| e.to_string())
330 })?;
331 let ts_init = get_required::<u64>(values, "ts_init")?;
332 let is_reduce_only = get_required::<bool>(values, "is_reduce_only")?;
333 let is_quote_quantity = get_required::<bool>(values, "is_quote_quantity")?;
334 let contingency_type = get_optional_parsed(values, "contingency_type", |s| {
335 s.parse::<ContingencyType>().map_err(|e| e.to_string())
336 })?;
337 let order_list_id = get_optional_parsed(values, "order_list_id", |s| {
338 Ok(OrderListId::from(s.as_str()))
339 })?;
340 let linked_order_ids =
341 get_optional::<Vec<String>>(values, "linked_order_ids")?.map(|vec| {
342 vec.iter()
343 .map(|s| ClientOrderId::from(s.as_str()))
344 .collect()
345 });
346 let parent_order_id = get_optional_parsed(values, "parent_order_id", |s| {
347 Ok(ClientOrderId::from(s.as_str()))
348 })?;
349 let exec_algorithm_id = get_optional_parsed(values, "exec_algorithm_id", |s| {
350 Ok(ExecAlgorithmId::from(s.as_str()))
351 })?;
352 let exec_algorithm_params =
353 get_optional::<IndexMap<String, String>>(values, "exec_algorithm_params")?
354 .map(str_indexmap_to_ustr);
355 let exec_spawn_id = get_optional_parsed(values, "exec_spawn_id", |s| {
356 Ok(ClientOrderId::from(s.as_str()))
357 })?;
358 let tags = get_optional::<Vec<String>>(values, "tags")?
359 .map(|vec| vec.iter().map(|s| Ustr::from(s)).collect());
360 Self::new_checked(
361 trader_id,
362 strategy_id,
363 instrument_id,
364 client_order_id,
365 order_side,
366 quantity,
367 time_in_force,
368 init_id,
369 ts_init.into(),
370 is_reduce_only,
371 is_quote_quantity,
372 contingency_type,
373 order_list_id,
374 linked_order_ids,
375 parent_order_id,
376 exec_algorithm_id,
377 exec_algorithm_params,
378 exec_spawn_id,
379 tags,
380 )
381 .map_err(to_pyvalue_err)
382 }
383
384 #[pyo3(name = "to_dict")]
385 fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
386 let dict = PyDict::new(py);
387 dict.set_item("trader_id", self.trader_id.to_string())?;
388 dict.set_item("strategy_id", self.strategy_id.to_string())?;
389 dict.set_item("instrument_id", self.instrument_id.to_string())?;
390 dict.set_item("client_order_id", self.client_order_id.to_string())?;
391 dict.set_item("side", self.side.to_string())?;
392 dict.set_item("type", self.order_type.to_string())?;
393 dict.set_item("quantity", self.quantity.to_string())?;
394 dict.set_item("status", self.status.to_string())?;
395 dict.set_item("time_in_force", self.time_in_force.to_string())?;
396 dict.set_item("is_reduce_only", self.is_reduce_only)?;
397 dict.set_item("is_quote_quantity", self.is_quote_quantity)?;
398 dict.set_item("filled_qty", self.filled_qty.to_string())?;
399 dict.set_item("init_id", self.init_id.to_string())?;
400 dict.set_item("ts_init", self.ts_init.as_u64())?;
401 dict.set_item("ts_last", self.ts_last.as_u64())?;
402 dict.set_item(
403 "commissions",
404 commissions_from_indexmap(py, self.commissions().clone())?,
405 )?;
406 self.venue_order_id.map_or_else(
407 || dict.set_item("venue_order_id", py.None()),
408 |x| dict.set_item("venue_order_id", x.to_string()),
409 )?;
410 self.emulation_trigger.map_or_else(
411 || dict.set_item("emulation_trigger", py.None()),
412 |x| dict.set_item("emulation_trigger", x.to_string()),
413 )?;
414 self.contingency_type.map_or_else(
415 || dict.set_item("contingency_type", py.None()),
416 |x| dict.set_item("contingency_type", x.to_string()),
417 )?;
418 self.order_list_id.map_or_else(
419 || dict.set_item("order_list_id", py.None()),
420 |x| dict.set_item("order_list_id", x.to_string()),
421 )?;
422 self.linked_order_ids.clone().map_or_else(
423 || dict.set_item("linked_order_ids", py.None()),
424 |linked_order_ids| {
425 let linked_order_ids_list =
426 PyList::new(py, linked_order_ids.iter().map(ToString::to_string))
427 .expect("Invalid `ExactSizeIterator`");
428 dict.set_item("linked_order_ids", linked_order_ids_list)
429 },
430 )?;
431 self.parent_order_id.map_or_else(
432 || dict.set_item("parent_order_id", py.None()),
433 |x| dict.set_item("parent_order_id", x.to_string()),
434 )?;
435 self.exec_algorithm_id.map_or_else(
436 || dict.set_item("exec_algorithm_id", py.None()),
437 |x| dict.set_item("exec_algorithm_id", x.to_string()),
438 )?;
439 match &self.exec_algorithm_params {
440 Some(exec_algorithm_params) => {
441 let py_exec_algorithm_params = PyDict::new(py);
442 for (key, value) in exec_algorithm_params {
443 py_exec_algorithm_params.set_item(key.to_string(), value.to_string())?;
444 }
445 dict.set_item("exec_algorithm_params", py_exec_algorithm_params)?;
446 }
447 None => dict.set_item("exec_algorithm_params", py.None())?,
448 }
449 self.exec_spawn_id.map_or_else(
450 || dict.set_item("exec_spawn_id", py.None()),
451 |x| dict.set_item("exec_spawn_id", x.to_string()),
452 )?;
453 self.tags.clone().map_or_else(
454 || dict.set_item("tags", py.None()),
455 |x| {
456 dict.set_item(
457 "tags",
458 x.iter().map(|x| x.to_string()).collect::<Vec<String>>(),
459 )
460 },
461 )?;
462 self.account_id.map_or_else(
463 || dict.set_item("account_id", py.None()),
464 |x| dict.set_item("account_id", x.to_string()),
465 )?;
466 self.slippage.map_or_else(
467 || dict.set_item("slippage", py.None()),
468 |x| dict.set_item("slippage", x.to_string()),
469 )?;
470 self.position_id.map_or_else(
471 || dict.set_item("position_id", py.None()),
472 |x| dict.set_item("position_id", x.to_string()),
473 )?;
474 self.liquidity_side.map_or_else(
475 || dict.set_item("liquidity_side", py.None()),
476 |x| dict.set_item("liquidity_side", x.to_string()),
477 )?;
478 self.last_trade_id.map_or_else(
479 || dict.set_item("last_trade_id", py.None()),
480 |x| dict.set_item("last_trade_id", x.to_string()),
481 )?;
482 self.avg_px.map_or_else(
483 || dict.set_item("avg_px", py.None()),
484 |x| dict.set_item("avg_px", x.to_string()),
485 )?;
486 Ok(dict.into())
487 }
488}