nautilus_model/python/orders/
mod.rs1use 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#[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
83pub 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);