Skip to main content

nautilus_model/python/orders/
mod.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 nautilus_core::python::to_pyvalue_err;
17use pyo3::{IntoPyObjectExt, Py, PyAny, PyResult, Python};
18use rust_decimal::Decimal;
19
20use crate::{
21    enums::{LiquiditySide, OrderType},
22    identifiers::{AccountId, PositionId, TradeId, VenueOrderId},
23    orders::{
24        LimitIfTouchedOrder, LimitOrder, MarketIfTouchedOrder, MarketOrder, MarketToLimitOrder,
25        Order, OrderAny, StopLimitOrder, StopMarketOrder, TrailingStopLimitOrder,
26        TrailingStopMarketOrder,
27    },
28    python::events::order::order_event_to_pyobject,
29    types::Quantity,
30};
31
32pub mod limit;
33pub mod limit_if_touched;
34pub mod list;
35pub mod market;
36pub mod market_if_touched;
37pub mod market_to_limit;
38pub mod stop_limit;
39pub mod stop_market;
40pub mod trailing_stop_limit;
41pub mod trailing_stop_market;
42
43/// Converts a Python order object into an [`OrderAny`] enum.
44///
45/// # Errors
46///
47/// Returns a `PyErr` if extraction fails or the order type is unsupported.
48#[expect(clippy::needless_pass_by_value)]
49pub fn pyobject_to_order_any(py: Python, order: Py<PyAny>) -> PyResult<OrderAny> {
50    let order_type = order.getattr(py, "order_type")?.extract::<OrderType>(py)?;
51    if order_type == OrderType::Limit {
52        let limit = order.extract::<LimitOrder>(py)?;
53        Ok(OrderAny::Limit(limit))
54    } else if order_type == OrderType::Market {
55        let market = order.extract::<MarketOrder>(py)?;
56        Ok(OrderAny::Market(market))
57    } else if order_type == OrderType::StopLimit {
58        let stop_limit = order.extract::<StopLimitOrder>(py)?;
59        Ok(OrderAny::StopLimit(stop_limit))
60    } else if order_type == OrderType::LimitIfTouched {
61        let limit_if_touched = order.extract::<LimitIfTouchedOrder>(py)?;
62        Ok(OrderAny::LimitIfTouched(limit_if_touched))
63    } else if order_type == OrderType::MarketIfTouched {
64        let market_if_touched = order.extract::<MarketIfTouchedOrder>(py)?;
65        Ok(OrderAny::MarketIfTouched(market_if_touched))
66    } else if order_type == OrderType::MarketToLimit {
67        let market_to_limit = order.extract::<MarketToLimitOrder>(py)?;
68        Ok(OrderAny::MarketToLimit(market_to_limit))
69    } else if order_type == OrderType::StopMarket {
70        let stop_market = order.extract::<StopMarketOrder>(py)?;
71        Ok(OrderAny::StopMarket(stop_market))
72    } else if order_type == OrderType::TrailingStopMarket {
73        let trailing_stop_market = order.extract::<TrailingStopMarketOrder>(py)?;
74        Ok(OrderAny::TrailingStopMarket(trailing_stop_market))
75    } else if order_type == OrderType::TrailingStopLimit {
76        let trailing_stop_limit = order.extract::<TrailingStopLimitOrder>(py)?;
77        Ok(OrderAny::TrailingStopLimit(trailing_stop_limit))
78    } else {
79        Err(to_pyvalue_err("Unsupported order type"))
80    }
81}
82
83/// Converts an [`OrderAny`] enum into a Python object.
84///
85/// # Errors
86///
87/// Returns a `PyErr` if conversion to a Python object fails.
88#[rustfmt::skip]
89pub fn order_any_to_pyobject(py: Python, order: OrderAny) -> PyResult<Py<PyAny>> {
90    match order {
91        OrderAny::Limit(limit_order) => limit_order.into_py_any(py),
92        OrderAny::LimitIfTouched(limit_if_touched_order) => limit_if_touched_order.into_py_any(py),
93        OrderAny::Market(market_order) => market_order.into_py_any(py),
94        OrderAny::MarketIfTouched(market_if_touched_order) => market_if_touched_order.into_py_any(py),
95        OrderAny::MarketToLimit(market_to_limit_order) => market_to_limit_order.into_py_any(py),
96        OrderAny::StopLimit(stop_limit_order) => stop_limit_order.into_py_any(py),
97        OrderAny::StopMarket(stop_market_order) => stop_market_order.into_py_any(py),
98        OrderAny::TrailingStopLimit(trailing_stop_limit_order) => trailing_stop_limit_order.into_py_any(py),
99        OrderAny::TrailingStopMarket(trailing_stop_market_order) => trailing_stop_market_order.into_py_any(py),
100    }
101}
102
103macro_rules! impl_order_common_pymethods {
104    ($type:ty) => {
105        #[pyo3_stub_gen::derive::gen_stub_pymethods]
106        #[pyo3::pymethods]
107        impl $type {
108            #[getter]
109            #[pyo3(name = "avg_px")]
110            fn py_avg_px(&self) -> Option<Decimal> {
111                self.avg_px()
112            }
113
114            #[getter]
115            #[pyo3(name = "event_count")]
116            fn py_event_count(&self) -> usize {
117                self.event_count()
118            }
119
120            #[getter]
121            #[pyo3(name = "is_buy")]
122            fn py_is_buy(&self) -> bool {
123                self.is_buy()
124            }
125
126            #[getter]
127            #[pyo3(name = "is_canceled")]
128            fn py_is_canceled(&self) -> bool {
129                self.is_canceled()
130            }
131
132            #[getter]
133            #[pyo3(name = "is_inflight")]
134            fn py_is_inflight(&self) -> bool {
135                self.is_inflight()
136            }
137
138            #[getter]
139            #[pyo3(name = "is_pending_cancel")]
140            fn py_is_pending_cancel(&self) -> bool {
141                self.is_pending_cancel()
142            }
143
144            #[getter]
145            #[pyo3(name = "is_pending_update")]
146            fn py_is_pending_update(&self) -> bool {
147                self.is_pending_update()
148            }
149
150            #[getter]
151            #[pyo3(name = "is_sell")]
152            fn py_is_sell(&self) -> bool {
153                self.is_sell()
154            }
155
156            #[getter]
157            #[pyo3(name = "last_event")]
158            fn py_last_event(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
159                order_event_to_pyobject(py, self.last_event().clone())
160            }
161
162            #[getter]
163            #[pyo3(name = "leaves_qty")]
164            fn py_leaves_qty(&self) -> Quantity {
165                self.leaves_qty()
166            }
167
168            #[getter]
169            #[pyo3(name = "overfill_qty")]
170            fn py_overfill_qty(&self) -> Quantity {
171                self.overfill_qty()
172            }
173
174            #[getter]
175            #[pyo3(name = "slippage")]
176            fn py_slippage(&self) -> Option<Decimal> {
177                self.slippage()
178            }
179
180            #[getter]
181            #[pyo3(name = "trade_ids")]
182            fn py_trade_ids(&self) -> Vec<TradeId> {
183                self.trade_ids().into_iter().copied().collect()
184            }
185
186            #[getter]
187            #[pyo3(name = "ts_accepted")]
188            fn py_ts_accepted(&self) -> Option<u64> {
189                self.ts_accepted().map(|ts| ts.as_u64())
190            }
191
192            #[getter]
193            #[pyo3(name = "ts_closed")]
194            fn py_ts_closed(&self) -> Option<u64> {
195                self.ts_closed().map(|ts| ts.as_u64())
196            }
197
198            #[getter]
199            #[pyo3(name = "ts_submitted")]
200            fn py_ts_submitted(&self) -> Option<u64> {
201                self.ts_submitted().map(|ts| ts.as_u64())
202            }
203
204            #[getter]
205            #[pyo3(name = "venue_order_ids")]
206            fn py_venue_order_ids(&self) -> Vec<VenueOrderId> {
207                self.venue_order_ids().into_iter().copied().collect()
208            }
209        }
210    };
211}
212
213macro_rules! impl_order_runtime_state_pymethods {
214    ($type:ty) => {
215        #[pyo3_stub_gen::derive::gen_stub_pymethods]
216        #[pyo3::pymethods]
217        impl $type {
218            #[getter]
219            #[pyo3(name = "filled_qty")]
220            fn py_filled_qty(&self) -> Quantity {
221                self.filled_qty()
222            }
223
224            #[getter]
225            #[pyo3(name = "is_active_local")]
226            fn py_is_active_local(&self) -> bool {
227                self.is_active_local()
228            }
229
230            #[getter]
231            #[pyo3(name = "is_emulated")]
232            fn py_is_emulated(&self) -> bool {
233                self.is_emulated()
234            }
235
236            #[getter]
237            #[pyo3(name = "is_primary")]
238            fn py_is_primary(&self) -> bool {
239                self.is_primary()
240            }
241
242            #[getter]
243            #[pyo3(name = "is_spawned")]
244            fn py_is_spawned(&self) -> bool {
245                self.is_spawned()
246            }
247
248            #[getter]
249            #[pyo3(name = "last_trade_id")]
250            fn py_last_trade_id(&self) -> Option<TradeId> {
251                self.last_trade_id()
252            }
253
254            #[getter]
255            #[pyo3(name = "liquidity_side")]
256            fn py_liquidity_side(&self) -> Option<LiquiditySide> {
257                self.liquidity_side()
258            }
259
260            #[getter]
261            #[pyo3(name = "position_id")]
262            fn py_position_id(&self) -> Option<PositionId> {
263                self.position_id()
264            }
265
266            #[getter]
267            #[pyo3(name = "ts_last")]
268            fn py_ts_last(&self) -> u64 {
269                self.ts_last().as_u64()
270            }
271
272            #[getter]
273            #[pyo3(name = "venue_order_id")]
274            fn py_venue_order_id(&self) -> Option<VenueOrderId> {
275                self.venue_order_id()
276            }
277        }
278    };
279}
280
281macro_rules! impl_order_account_id_pymethods {
282    ($type:ty) => {
283        #[pyo3_stub_gen::derive::gen_stub_pymethods]
284        #[pyo3::pymethods]
285        impl $type {
286            #[getter]
287            #[pyo3(name = "account_id")]
288            fn py_account_id(&self) -> Option<AccountId> {
289                self.account_id()
290            }
291        }
292    };
293}
294
295macro_rules! impl_order_init_event_pymethods {
296    ($type:ty) => {
297        #[pyo3_stub_gen::derive::gen_stub_pymethods]
298        #[pyo3::pymethods]
299        impl $type {
300            #[getter]
301            #[pyo3(name = "init_event")]
302            fn py_init_event(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
303                match self.init_event() {
304                    Some(event) => order_event_to_pyobject(py, event),
305                    None => Ok(py.None()),
306                }
307            }
308        }
309    };
310}
311
312macro_rules! impl_order_open_state_pymethods {
313    ($type:ty) => {
314        #[pyo3_stub_gen::derive::gen_stub_pymethods]
315        #[pyo3::pymethods]
316        impl $type {
317            #[getter]
318            #[pyo3(name = "is_open")]
319            fn py_is_open(&self) -> bool {
320                self.is_open()
321            }
322
323            #[getter]
324            #[pyo3(name = "is_closed")]
325            fn py_is_closed(&self) -> bool {
326                self.is_closed()
327            }
328        }
329    };
330}
331
332macro_rules! impl_trigger_order_state_pymethods {
333    ($type:ty) => {
334        #[pyo3_stub_gen::derive::gen_stub_pymethods]
335        #[pyo3::pymethods]
336        impl $type {
337            #[getter]
338            #[pyo3(name = "is_triggered")]
339            fn py_is_triggered(&self) -> bool {
340                self.is_triggered
341            }
342
343            #[getter]
344            #[pyo3(name = "ts_triggered")]
345            fn py_ts_triggered(&self) -> Option<u64> {
346                self.ts_triggered.map(|ts| ts.as_u64())
347            }
348        }
349    };
350}
351
352macro_rules! impl_trailing_order_activation_pymethods {
353    ($type:ty) => {
354        #[pyo3_stub_gen::derive::gen_stub_pymethods]
355        #[pyo3::pymethods]
356        impl $type {
357            #[getter]
358            #[pyo3(name = "is_activated")]
359            fn py_is_activated(&self) -> bool {
360                self.is_activated
361            }
362        }
363    };
364}
365
366impl_order_common_pymethods!(LimitOrder);
367impl_order_common_pymethods!(LimitIfTouchedOrder);
368impl_order_common_pymethods!(MarketOrder);
369impl_order_common_pymethods!(MarketIfTouchedOrder);
370impl_order_common_pymethods!(MarketToLimitOrder);
371impl_order_common_pymethods!(StopLimitOrder);
372impl_order_common_pymethods!(StopMarketOrder);
373impl_order_common_pymethods!(TrailingStopLimitOrder);
374impl_order_common_pymethods!(TrailingStopMarketOrder);
375
376impl_order_runtime_state_pymethods!(LimitIfTouchedOrder);
377impl_order_runtime_state_pymethods!(MarketOrder);
378impl_order_runtime_state_pymethods!(MarketIfTouchedOrder);
379impl_order_runtime_state_pymethods!(MarketToLimitOrder);
380impl_order_runtime_state_pymethods!(StopLimitOrder);
381impl_order_runtime_state_pymethods!(StopMarketOrder);
382impl_order_runtime_state_pymethods!(TrailingStopLimitOrder);
383impl_order_runtime_state_pymethods!(TrailingStopMarketOrder);
384
385impl_order_account_id_pymethods!(MarketIfTouchedOrder);
386impl_order_account_id_pymethods!(StopLimitOrder);
387
388impl_order_init_event_pymethods!(LimitOrder);
389impl_order_init_event_pymethods!(MarketOrder);
390impl_order_open_state_pymethods!(MarketOrder);
391
392impl_trigger_order_state_pymethods!(LimitIfTouchedOrder);
393impl_trigger_order_state_pymethods!(MarketIfTouchedOrder);
394impl_trigger_order_state_pymethods!(StopLimitOrder);
395impl_trigger_order_state_pymethods!(StopMarketOrder);
396impl_trigger_order_state_pymethods!(TrailingStopLimitOrder);
397impl_trigger_order_state_pymethods!(TrailingStopMarketOrder);
398
399impl_trailing_order_activation_pymethods!(TrailingStopLimitOrder);
400impl_trailing_order_activation_pymethods!(TrailingStopMarketOrder);