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