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.
88pub fn order_any_to_pyobject(py: Python, order: OrderAny) -> PyResult<Py<PyAny>> {
89    match order {
90        OrderAny::Limit(limit_order) => limit_order.into_py_any(py),
91        OrderAny::LimitIfTouched(limit_if_touched_order) => limit_if_touched_order.into_py_any(py),
92        OrderAny::Market(market_order) => market_order.into_py_any(py),
93        OrderAny::MarketIfTouched(market_if_touched_order) => {
94            market_if_touched_order.into_py_any(py)
95        }
96        OrderAny::MarketToLimit(market_to_limit_order) => market_to_limit_order.into_py_any(py),
97        OrderAny::StopLimit(stop_limit_order) => stop_limit_order.into_py_any(py),
98        OrderAny::StopMarket(stop_market_order) => stop_market_order.into_py_any(py),
99        OrderAny::TrailingStopLimit(trailing_stop_limit_order) => {
100            trailing_stop_limit_order.into_py_any(py)
101        }
102        OrderAny::TrailingStopMarket(trailing_stop_market_order) => {
103            trailing_stop_market_order.into_py_any(py)
104        }
105    }
106}
107
108macro_rules! impl_order_common_pymethods {
109    ($type:ty) => {
110        #[pyo3_stub_gen::derive::gen_stub_pymethods]
111        #[pyo3::pymethods]
112        impl $type {
113            #[getter]
114            #[pyo3(name = "avg_px")]
115            fn py_avg_px(&self) -> Option<Decimal> {
116                self.avg_px()
117            }
118
119            #[getter]
120            #[pyo3(name = "event_count")]
121            fn py_event_count(&self) -> usize {
122                self.event_count()
123            }
124
125            #[getter]
126            #[pyo3(name = "is_buy")]
127            fn py_is_buy(&self) -> bool {
128                self.is_buy()
129            }
130
131            #[getter]
132            #[pyo3(name = "is_canceled")]
133            fn py_is_canceled(&self) -> bool {
134                self.is_canceled()
135            }
136
137            #[getter]
138            #[pyo3(name = "is_inflight")]
139            fn py_is_inflight(&self) -> bool {
140                self.is_inflight()
141            }
142
143            #[getter]
144            #[pyo3(name = "is_pending_cancel")]
145            fn py_is_pending_cancel(&self) -> bool {
146                self.is_pending_cancel()
147            }
148
149            #[getter]
150            #[pyo3(name = "is_pending_update")]
151            fn py_is_pending_update(&self) -> bool {
152                self.is_pending_update()
153            }
154
155            #[getter]
156            #[pyo3(name = "is_sell")]
157            fn py_is_sell(&self) -> bool {
158                self.is_sell()
159            }
160
161            #[getter]
162            #[pyo3(name = "last_event")]
163            fn py_last_event(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
164                order_event_to_pyobject(py, self.last_event().clone())
165            }
166
167            #[getter]
168            #[pyo3(name = "leaves_qty")]
169            fn py_leaves_qty(&self) -> Quantity {
170                self.leaves_qty()
171            }
172
173            #[getter]
174            #[pyo3(name = "overfill_qty")]
175            fn py_overfill_qty(&self) -> Quantity {
176                self.overfill_qty()
177            }
178
179            #[getter]
180            #[pyo3(name = "slippage")]
181            fn py_slippage(&self) -> Option<Decimal> {
182                self.slippage()
183            }
184
185            #[getter]
186            #[pyo3(name = "trade_ids")]
187            fn py_trade_ids(&self) -> Vec<TradeId> {
188                self.trade_ids().into_iter().copied().collect()
189            }
190
191            #[getter]
192            #[pyo3(name = "ts_accepted")]
193            fn py_ts_accepted(&self) -> Option<u64> {
194                self.ts_accepted().map(|ts| ts.as_u64())
195            }
196
197            #[getter]
198            #[pyo3(name = "ts_closed")]
199            fn py_ts_closed(&self) -> Option<u64> {
200                self.ts_closed().map(|ts| ts.as_u64())
201            }
202
203            #[getter]
204            #[pyo3(name = "ts_submitted")]
205            fn py_ts_submitted(&self) -> Option<u64> {
206                self.ts_submitted().map(|ts| ts.as_u64())
207            }
208
209            #[getter]
210            #[pyo3(name = "venue_order_ids")]
211            fn py_venue_order_ids(&self) -> Vec<VenueOrderId> {
212                self.venue_order_ids().into_iter().copied().collect()
213            }
214        }
215    };
216}
217
218macro_rules! impl_order_runtime_state_pymethods {
219    ($type:ty) => {
220        #[pyo3_stub_gen::derive::gen_stub_pymethods]
221        #[pyo3::pymethods]
222        impl $type {
223            #[getter]
224            #[pyo3(name = "filled_qty")]
225            fn py_filled_qty(&self) -> Quantity {
226                self.filled_qty()
227            }
228
229            #[getter]
230            #[pyo3(name = "is_active_local")]
231            fn py_is_active_local(&self) -> bool {
232                self.is_active_local()
233            }
234
235            #[getter]
236            #[pyo3(name = "is_emulated")]
237            fn py_is_emulated(&self) -> bool {
238                self.is_emulated()
239            }
240
241            #[getter]
242            #[pyo3(name = "is_primary")]
243            fn py_is_primary(&self) -> bool {
244                self.is_primary()
245            }
246
247            #[getter]
248            #[pyo3(name = "is_spawned")]
249            fn py_is_spawned(&self) -> bool {
250                self.is_spawned()
251            }
252
253            #[getter]
254            #[pyo3(name = "last_trade_id")]
255            fn py_last_trade_id(&self) -> Option<TradeId> {
256                self.last_trade_id()
257            }
258
259            #[getter]
260            #[pyo3(name = "liquidity_side")]
261            fn py_liquidity_side(&self) -> Option<LiquiditySide> {
262                self.liquidity_side()
263            }
264
265            #[getter]
266            #[pyo3(name = "position_id")]
267            fn py_position_id(&self) -> Option<PositionId> {
268                self.position_id()
269            }
270
271            #[getter]
272            #[pyo3(name = "ts_last")]
273            fn py_ts_last(&self) -> u64 {
274                self.ts_last().as_u64()
275            }
276
277            #[getter]
278            #[pyo3(name = "venue_order_id")]
279            fn py_venue_order_id(&self) -> Option<VenueOrderId> {
280                self.venue_order_id()
281            }
282        }
283    };
284}
285
286macro_rules! impl_order_account_id_pymethods {
287    ($type:ty) => {
288        #[pyo3_stub_gen::derive::gen_stub_pymethods]
289        #[pyo3::pymethods]
290        impl $type {
291            #[getter]
292            #[pyo3(name = "account_id")]
293            fn py_account_id(&self) -> Option<AccountId> {
294                self.account_id()
295            }
296        }
297    };
298}
299
300macro_rules! impl_order_init_event_pymethods {
301    ($type:ty) => {
302        #[pyo3_stub_gen::derive::gen_stub_pymethods]
303        #[pyo3::pymethods]
304        impl $type {
305            #[getter]
306            #[pyo3(name = "init_event")]
307            fn py_init_event(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
308                match self.init_event() {
309                    Some(event) => order_event_to_pyobject(py, event),
310                    None => Ok(py.None()),
311                }
312            }
313        }
314    };
315}
316
317macro_rules! impl_order_open_state_pymethods {
318    ($type:ty) => {
319        #[pyo3_stub_gen::derive::gen_stub_pymethods]
320        #[pyo3::pymethods]
321        impl $type {
322            #[getter]
323            #[pyo3(name = "is_open")]
324            fn py_is_open(&self) -> bool {
325                self.is_open()
326            }
327
328            #[getter]
329            #[pyo3(name = "is_closed")]
330            fn py_is_closed(&self) -> bool {
331                self.is_closed()
332            }
333        }
334    };
335}
336
337macro_rules! impl_trigger_order_state_pymethods {
338    ($type:ty) => {
339        #[pyo3_stub_gen::derive::gen_stub_pymethods]
340        #[pyo3::pymethods]
341        impl $type {
342            #[getter]
343            #[pyo3(name = "is_triggered")]
344            fn py_is_triggered(&self) -> bool {
345                self.is_triggered
346            }
347
348            #[getter]
349            #[pyo3(name = "ts_triggered")]
350            fn py_ts_triggered(&self) -> Option<u64> {
351                self.ts_triggered.map(|ts| ts.as_u64())
352            }
353        }
354    };
355}
356
357macro_rules! impl_trailing_order_activation_pymethods {
358    ($type:ty) => {
359        #[pyo3_stub_gen::derive::gen_stub_pymethods]
360        #[pyo3::pymethods]
361        impl $type {
362            #[getter]
363            #[pyo3(name = "is_activated")]
364            fn py_is_activated(&self) -> bool {
365                self.is_activated
366            }
367        }
368    };
369}
370
371impl_order_common_pymethods!(LimitOrder);
372impl_order_common_pymethods!(LimitIfTouchedOrder);
373impl_order_common_pymethods!(MarketOrder);
374impl_order_common_pymethods!(MarketIfTouchedOrder);
375impl_order_common_pymethods!(MarketToLimitOrder);
376impl_order_common_pymethods!(StopLimitOrder);
377impl_order_common_pymethods!(StopMarketOrder);
378impl_order_common_pymethods!(TrailingStopLimitOrder);
379impl_order_common_pymethods!(TrailingStopMarketOrder);
380
381impl_order_runtime_state_pymethods!(LimitIfTouchedOrder);
382impl_order_runtime_state_pymethods!(MarketOrder);
383impl_order_runtime_state_pymethods!(MarketIfTouchedOrder);
384impl_order_runtime_state_pymethods!(MarketToLimitOrder);
385impl_order_runtime_state_pymethods!(StopLimitOrder);
386impl_order_runtime_state_pymethods!(StopMarketOrder);
387impl_order_runtime_state_pymethods!(TrailingStopLimitOrder);
388impl_order_runtime_state_pymethods!(TrailingStopMarketOrder);
389
390impl_order_account_id_pymethods!(MarketIfTouchedOrder);
391impl_order_account_id_pymethods!(StopLimitOrder);
392
393impl_order_init_event_pymethods!(LimitOrder);
394impl_order_init_event_pymethods!(MarketOrder);
395impl_order_open_state_pymethods!(MarketOrder);
396
397impl_trigger_order_state_pymethods!(LimitIfTouchedOrder);
398impl_trigger_order_state_pymethods!(MarketIfTouchedOrder);
399impl_trigger_order_state_pymethods!(StopLimitOrder);
400impl_trigger_order_state_pymethods!(StopMarketOrder);
401impl_trigger_order_state_pymethods!(TrailingStopLimitOrder);
402impl_trigger_order_state_pymethods!(TrailingStopMarketOrder);
403
404impl_trailing_order_activation_pymethods!(TrailingStopLimitOrder);
405impl_trailing_order_activation_pymethods!(TrailingStopMarketOrder);