nautilus_model/python/orders/
market.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use 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}