nautilus_execution/python/
order.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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::{
17    UUID4,
18    python::{IntoPyObjectNautilusExt, serialization::from_dict_pyo3},
19};
20use nautilus_model::{
21    enums::{
22        ContingencyType, OrderSide, OrderStatus, OrderType, TimeInForce, TrailingOffsetType,
23        TriggerType,
24    },
25    identifiers::{AccountId, ClientOrderId, InstrumentId, OrderListId, VenueOrderId},
26    types::{Price, Quantity},
27};
28use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
29
30use crate::reports::order::OrderStatusReport;
31
32#[pymethods]
33impl OrderStatusReport {
34    #[new]
35    #[allow(clippy::too_many_arguments)]
36    #[pyo3(signature = (
37        account_id,
38        instrument_id,
39        venue_order_id,
40        order_side,
41        order_type,
42        time_in_force,
43        order_status,
44        quantity,
45        filled_qty,
46        ts_accepted,
47        ts_last,
48        ts_init,
49        client_order_id=None,
50        report_id=None,
51        order_list_id=None,
52        contingency_type=None,
53        expire_time=None,
54        price=None,
55        trigger_price=None,
56        trigger_type=None,
57        limit_offset=None,
58        trailing_offset=None,
59        trailing_offset_type=None,
60        avg_px=None,
61        display_qty=None,
62        post_only=false,
63        reduce_only=false,
64        cancel_reason=None,
65        ts_triggered=None,
66    ))]
67    fn py_new(
68        account_id: AccountId,
69        instrument_id: InstrumentId,
70        venue_order_id: VenueOrderId,
71        order_side: OrderSide,
72        order_type: OrderType,
73        time_in_force: TimeInForce,
74        order_status: OrderStatus,
75        quantity: Quantity,
76        filled_qty: Quantity,
77        ts_accepted: u64,
78        ts_last: u64,
79        ts_init: u64,
80        client_order_id: Option<ClientOrderId>,
81        report_id: Option<UUID4>,
82        order_list_id: Option<OrderListId>,
83        contingency_type: Option<ContingencyType>,
84        expire_time: Option<u64>,
85        price: Option<Price>,
86        trigger_price: Option<Price>,
87        trigger_type: Option<TriggerType>,
88        limit_offset: Option<Price>,
89        trailing_offset: Option<Price>,
90        trailing_offset_type: Option<TrailingOffsetType>,
91        avg_px: Option<f64>,
92        display_qty: Option<Quantity>,
93        post_only: bool,
94        reduce_only: bool,
95        cancel_reason: Option<String>,
96        ts_triggered: Option<u64>,
97    ) -> PyResult<Self> {
98        let mut report = Self::new(
99            account_id,
100            instrument_id,
101            client_order_id,
102            venue_order_id,
103            order_side,
104            order_type,
105            time_in_force,
106            order_status,
107            quantity,
108            filled_qty,
109            ts_accepted.into(),
110            ts_last.into(),
111            ts_init.into(),
112            report_id,
113        );
114
115        if let Some(id) = order_list_id {
116            report = report.with_order_list_id(id);
117        }
118        if let Some(ct) = contingency_type {
119            report = report.with_contingency_type(ct);
120        }
121        if let Some(et) = expire_time {
122            report = report.with_expire_time(et.into());
123        }
124        if let Some(p) = price {
125            report = report.with_price(p);
126        }
127        if let Some(tp) = trigger_price {
128            report = report.with_trigger_price(tp);
129        }
130        if let Some(tt) = trigger_type {
131            report = report.with_trigger_type(tt);
132        }
133        if let Some(lo) = limit_offset {
134            report = report.with_limit_offset(lo);
135        }
136        if let Some(to) = trailing_offset {
137            report = report.with_trailing_offset(to);
138        }
139        if let Some(tot) = trailing_offset_type {
140            report = report.with_trailing_offset_type(tot);
141        }
142        if let Some(ap) = avg_px {
143            report = report.with_avg_px(ap);
144        }
145        if let Some(dq) = display_qty {
146            report = report.with_display_qty(dq);
147        }
148        if post_only {
149            report = report.with_post_only(true);
150        }
151        if reduce_only {
152            report = report.with_reduce_only(true);
153        }
154        if let Some(cr) = cancel_reason {
155            report = report.with_cancel_reason(&cr);
156        }
157        if let Some(tt) = ts_triggered {
158            report = report.with_ts_triggered(tt.into());
159        }
160
161        Ok(report)
162    }
163
164    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
165        match op {
166            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
167            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
168            _ => py.NotImplemented(),
169        }
170    }
171
172    fn __repr__(&self) -> String {
173        self.to_string()
174    }
175
176    fn __str__(&self) -> String {
177        self.to_string()
178    }
179
180    #[getter]
181    #[pyo3(name = "account_id")]
182    const fn py_account_id(&self) -> AccountId {
183        self.account_id
184    }
185
186    #[getter]
187    #[pyo3(name = "instrument_id")]
188    const fn py_instrument_id(&self) -> InstrumentId {
189        self.instrument_id
190    }
191
192    #[getter]
193    #[pyo3(name = "venue_order_id")]
194    const fn py_venue_order_id(&self) -> VenueOrderId {
195        self.venue_order_id
196    }
197
198    #[getter]
199    #[pyo3(name = "order_side")]
200    const fn py_order_side(&self) -> OrderSide {
201        self.order_side
202    }
203
204    #[getter]
205    #[pyo3(name = "order_type")]
206    const fn py_order_type(&self) -> OrderType {
207        self.order_type
208    }
209
210    #[getter]
211    #[pyo3(name = "time_in_force")]
212    const fn py_time_in_force(&self) -> TimeInForce {
213        self.time_in_force
214    }
215
216    #[getter]
217    #[pyo3(name = "order_status")]
218    const fn py_order_status(&self) -> OrderStatus {
219        self.order_status
220    }
221
222    #[getter]
223    #[pyo3(name = "quantity")]
224    const fn py_quantity(&self) -> Quantity {
225        self.quantity
226    }
227
228    #[getter]
229    #[pyo3(name = "filled_qty")]
230    const fn py_filled_qty(&self) -> Quantity {
231        self.filled_qty
232    }
233
234    #[getter]
235    #[pyo3(name = "report_id")]
236    const fn py_report_id(&self) -> UUID4 {
237        self.report_id
238    }
239
240    #[getter]
241    #[pyo3(name = "ts_accepted")]
242    const fn py_ts_accepted(&self) -> u64 {
243        self.ts_accepted.as_u64()
244    }
245
246    #[getter]
247    #[pyo3(name = "ts_last")]
248    const fn py_ts_last(&self) -> u64 {
249        self.ts_last.as_u64()
250    }
251
252    #[getter]
253    #[pyo3(name = "ts_init")]
254    const fn py_ts_init(&self) -> u64 {
255        self.ts_init.as_u64()
256    }
257
258    #[getter]
259    #[pyo3(name = "client_order_id")]
260    const fn py_client_order_id(&self) -> Option<ClientOrderId> {
261        self.client_order_id
262    }
263
264    #[getter]
265    #[pyo3(name = "order_list_id")]
266    const fn py_order_list_id(&self) -> Option<OrderListId> {
267        self.order_list_id
268    }
269
270    #[getter]
271    #[pyo3(name = "contingency_type")]
272    const fn py_contingency_type(&self) -> ContingencyType {
273        self.contingency_type
274    }
275
276    #[getter]
277    #[pyo3(name = "expire_time")]
278    fn py_expire_time(&self) -> Option<u64> {
279        self.expire_time.map(|t| t.as_u64())
280    }
281
282    #[getter]
283    #[pyo3(name = "price")]
284    const fn py_price(&self) -> Option<Price> {
285        self.price
286    }
287
288    #[getter]
289    #[pyo3(name = "trigger_price")]
290    const fn py_trigger_price(&self) -> Option<Price> {
291        self.trigger_price
292    }
293
294    #[getter]
295    #[pyo3(name = "trigger_type")]
296    const fn py_trigger_type(&self) -> Option<TriggerType> {
297        self.trigger_type
298    }
299
300    #[getter]
301    #[pyo3(name = "limit_offset")]
302    const fn py_limit_offset(&self) -> Option<Price> {
303        self.limit_offset
304    }
305
306    #[getter]
307    #[pyo3(name = "trailing_offset")]
308    const fn py_trailing_offset(&self) -> Option<Price> {
309        self.trailing_offset
310    }
311
312    #[getter]
313    #[pyo3(name = "trailing_offset_type")]
314    const fn py_trailing_offset_type(&self) -> TrailingOffsetType {
315        self.trailing_offset_type
316    }
317
318    #[getter]
319    #[pyo3(name = "avg_px")]
320    const fn py_avg_px(&self) -> Option<f64> {
321        self.avg_px
322    }
323
324    #[getter]
325    #[pyo3(name = "display_qty")]
326    const fn py_display_qty(&self) -> Option<Quantity> {
327        self.display_qty
328    }
329
330    #[getter]
331    #[pyo3(name = "post_only")]
332    const fn py_post_only(&self) -> bool {
333        self.post_only
334    }
335
336    #[getter]
337    #[pyo3(name = "reduce_only")]
338    const fn py_reduce_only(&self) -> bool {
339        self.reduce_only
340    }
341
342    #[getter]
343    #[pyo3(name = "cancel_reason")]
344    fn py_cancel_reason(&self) -> Option<String> {
345        self.cancel_reason.clone()
346    }
347
348    #[getter]
349    #[pyo3(name = "ts_triggered")]
350    fn py_ts_triggered(&self) -> Option<u64> {
351        self.ts_triggered.map(|t| t.as_u64())
352    }
353
354    #[staticmethod]
355    #[pyo3(name = "from_dict")]
356    pub fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
357        from_dict_pyo3(py, values)
358    }
359
360    #[pyo3(name = "to_dict")]
361    pub fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
362        let dict = PyDict::new(py);
363        dict.set_item("type", stringify!(OrderStatusReport))?;
364        dict.set_item("account_id", self.account_id.to_string())?;
365        dict.set_item("instrument_id", self.instrument_id.to_string())?;
366        dict.set_item("venue_order_id", self.venue_order_id.to_string())?;
367        dict.set_item("order_side", self.order_side.to_string())?;
368        dict.set_item("order_type", self.order_type.to_string())?;
369        dict.set_item("time_in_force", self.time_in_force.to_string())?;
370        dict.set_item("order_status", self.order_status.to_string())?;
371        dict.set_item("quantity", self.quantity.to_string())?;
372        dict.set_item("filled_qty", self.filled_qty.to_string())?;
373        dict.set_item("report_id", self.report_id.to_string())?;
374        dict.set_item("ts_accepted", self.ts_accepted.as_u64())?;
375        dict.set_item("ts_last", self.ts_last.as_u64())?;
376        dict.set_item("ts_init", self.ts_init.as_u64())?;
377        dict.set_item("contingency_type", self.contingency_type.to_string())?;
378        dict.set_item(
379            "trailing_offset_type",
380            self.trailing_offset_type.to_string(),
381        )?;
382        dict.set_item("post_only", self.post_only)?;
383        dict.set_item("reduce_only", self.reduce_only)?;
384
385        match &self.client_order_id {
386            Some(id) => dict.set_item("client_order_id", id.to_string())?,
387            None => dict.set_item("client_order_id", py.None())?,
388        }
389        match &self.order_list_id {
390            Some(id) => dict.set_item("order_list_id", id.to_string())?,
391            None => dict.set_item("order_list_id", py.None())?,
392        }
393        match &self.expire_time {
394            Some(t) => dict.set_item("expire_time", t.as_u64())?,
395            None => dict.set_item("expire_time", py.None())?,
396        }
397        match &self.price {
398            Some(p) => dict.set_item("price", p.to_string())?,
399            None => dict.set_item("price", py.None())?,
400        }
401        match &self.trigger_price {
402            Some(p) => dict.set_item("trigger_price", p.to_string())?,
403            None => dict.set_item("trigger_price", py.None())?,
404        }
405        match &self.trigger_type {
406            Some(t) => dict.set_item("trigger_type", t.to_string())?,
407            None => dict.set_item("trigger_type", py.None())?,
408        }
409        match &self.limit_offset {
410            Some(o) => dict.set_item("limit_offset", o.to_string())?,
411            None => dict.set_item("limit_offset", py.None())?,
412        }
413        match &self.trailing_offset {
414            Some(o) => dict.set_item("trailing_offset", o.to_string())?,
415            None => dict.set_item("trailing_offset", py.None())?,
416        }
417        match &self.avg_px {
418            Some(p) => dict.set_item("avg_px", p)?,
419            None => dict.set_item("avg_px", py.None())?,
420        }
421        match &self.display_qty {
422            Some(q) => dict.set_item("display_qty", q.to_string())?,
423            None => dict.set_item("display_qty", py.None())?,
424        }
425        match &self.cancel_reason {
426            Some(r) => dict.set_item("cancel_reason", r)?,
427            None => dict.set_item("cancel_reason", py.None())?,
428        }
429        match &self.ts_triggered {
430            Some(t) => dict.set_item("ts_triggered", t.as_u64())?,
431            None => dict.set_item("ts_triggered", py.None())?,
432        }
433
434        Ok(dict.into())
435    }
436}