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