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