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