nautilus_model/python/orders/
market.rs

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