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::{
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    /// Creates a new `MarketOrder` instance.
55    #[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}