nautilus_model/python/events/order/
fill_voided.rs1use indexmap::IndexMap;
10use nautilus_core::{
11 UUID4,
12 python::{
13 IntoPyObjectNautilusExt,
14 serialization::{from_dict_pyo3, to_dict_pyo3},
15 },
16};
17use pyo3::{basic::CompareOp, prelude::*, types::PyDict};
18use ustr::Ustr;
19
20use crate::{
21 enums::{LiquiditySide, OrderSide, OrderType},
22 events::OrderFillVoided,
23 identifiers::{
24 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId,
25 VenueOrderId,
26 },
27 orders::str_indexmap_to_ustr,
28 types::{Currency, Money, Price, Quantity},
29};
30
31#[pymethods]
32#[pyo3_stub_gen::derive::gen_stub_pymethods]
33impl OrderFillVoided {
34 #[expect(clippy::too_many_arguments)]
41 #[new]
42 #[pyo3(signature = (trader_id, strategy_id, instrument_id, client_order_id, venue_order_id, account_id, correction_id, trade_id, voided_qty, order_side, order_type, last_px, currency, liquidity_side, event_id, ts_event, ts_init, reconciliation, is_reopened=false, commission_voided=None, position_id=None, reason=None, info=None))]
43 fn py_new(
44 trader_id: TraderId,
45 strategy_id: StrategyId,
46 instrument_id: InstrumentId,
47 client_order_id: ClientOrderId,
48 venue_order_id: VenueOrderId,
49 account_id: AccountId,
50 correction_id: &str,
51 trade_id: TradeId,
52 voided_qty: Quantity,
53 order_side: OrderSide,
54 order_type: OrderType,
55 last_px: Price,
56 currency: Currency,
57 liquidity_side: LiquiditySide,
58 event_id: UUID4,
59 ts_event: u64,
60 ts_init: u64,
61 reconciliation: bool,
62 is_reopened: bool,
63 commission_voided: Option<Money>,
64 position_id: Option<PositionId>,
65 reason: Option<&str>,
66 info: Option<IndexMap<String, String>>,
67 ) -> Self {
68 Self::new(
69 trader_id,
70 strategy_id,
71 instrument_id,
72 client_order_id,
73 venue_order_id,
74 account_id,
75 Ustr::from(correction_id),
76 trade_id,
77 voided_qty,
78 commission_voided,
79 order_side,
80 order_type,
81 last_px,
82 currency,
83 liquidity_side,
84 position_id,
85 reason.map(Ustr::from),
86 info.map(str_indexmap_to_ustr),
87 event_id,
88 ts_event.into(),
89 ts_init.into(),
90 reconciliation,
91 is_reopened,
92 )
93 }
94
95 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
96 match op {
97 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
98 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
99 _ => py.NotImplemented(),
100 }
101 }
102
103 fn __repr__(&self) -> String {
104 format!("{self:?}")
105 }
106 fn __str__(&self) -> String {
107 self.to_string()
108 }
109
110 #[getter]
111 fn trader_id(&self) -> TraderId {
112 self.trader_id
113 }
114 #[getter]
115 fn strategy_id(&self) -> StrategyId {
116 self.strategy_id
117 }
118 #[getter]
119 fn instrument_id(&self) -> InstrumentId {
120 self.instrument_id
121 }
122 #[getter]
123 fn client_order_id(&self) -> ClientOrderId {
124 self.client_order_id
125 }
126 #[getter]
127 fn venue_order_id(&self) -> VenueOrderId {
128 self.venue_order_id
129 }
130 #[getter]
131 fn account_id(&self) -> AccountId {
132 self.account_id
133 }
134 #[getter]
135 fn correction_id(&self) -> &str {
136 self.correction_id.as_str()
137 }
138 #[getter]
139 fn trade_id(&self) -> TradeId {
140 self.trade_id
141 }
142 #[getter]
143 fn voided_qty(&self) -> Quantity {
144 self.voided_qty
145 }
146 #[getter]
147 fn commission_voided(&self) -> Option<Money> {
148 self.commission_voided
149 }
150 #[getter]
151 fn order_side(&self) -> OrderSide {
152 self.order_side
153 }
154 #[getter]
155 fn order_type(&self) -> OrderType {
156 self.order_type
157 }
158 #[getter]
159 fn last_px(&self) -> Price {
160 self.last_px
161 }
162 #[getter]
163 fn currency(&self) -> Currency {
164 self.currency
165 }
166 #[getter]
167 fn liquidity_side(&self) -> LiquiditySide {
168 self.liquidity_side
169 }
170 #[getter]
171 fn position_id(&self) -> Option<PositionId> {
172 self.position_id
173 }
174 #[getter]
175 fn reason(&self) -> Option<&str> {
176 self.reason.map(|value| value.as_str())
177 }
178 #[getter]
179 fn event_id(&self) -> UUID4 {
180 self.event_id
181 }
182 #[getter]
183 fn ts_event(&self) -> u64 {
184 self.ts_event.as_u64()
185 }
186 #[getter]
187 fn ts_init(&self) -> u64 {
188 self.ts_init.as_u64()
189 }
190 #[getter]
191 fn reconciliation(&self) -> bool {
192 self.reconciliation
193 }
194 #[getter]
195 fn is_reopened(&self) -> bool {
196 self.is_reopened
197 }
198 #[getter]
199 fn causation_id(&self) -> Option<UUID4> {
200 self.causation_id
201 }
202
203 #[getter]
204 fn info(&self) -> Option<IndexMap<&str, &str>> {
205 self.info.as_ref().map(|info| {
206 info.iter()
207 .map(|(key, value)| (key.as_str(), value.as_str()))
208 .collect()
209 })
210 }
211
212 #[staticmethod]
213 #[pyo3(name = "from_dict")]
214 fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
215 from_dict_pyo3(py, values)
216 }
217
218 #[pyo3(name = "to_dict")]
219 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
220 to_dict_pyo3(py, self)
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use nautilus_core::UUID4;
227 use rstest::rstest;
228
229 use super::*;
230 use crate::events::order::spec::OrderFillVoidedSpec;
231
232 #[rstest]
233 fn test_order_fill_voided_python_dict_round_trip() {
234 let mut event = OrderFillVoidedSpec::builder()
235 .correction_id(Ustr::from("CORRECTION-PYTHON"))
236 .voided_qty(Quantity::from("0.561000"))
237 .commission_voided(Money::from("12.20000000 USDT"))
238 .position_id(PositionId::from("P-001"))
239 .reason(Ustr::from("VENUE_VOID"))
240 .info(IndexMap::from([(Ustr::from("source"), Ustr::from("test"))]))
241 .reconciliation(true)
242 .is_reopened(true)
243 .build();
244 event.causation_id = Some(UUID4::new());
245
246 Python::initialize();
247 Python::attach(|py| {
248 let values = event.py_to_dict(py).unwrap();
249 let restored = OrderFillVoided::py_from_dict(py, values).unwrap();
250
251 assert_eq!(restored, event);
252 });
253 }
254}