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
83#[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);