Skip to main content

nautilus_model/python/orders/
market.rs

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