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