Skip to main content

nautilus_model/events/order/
fill_voided.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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
10use std::fmt::{Debug, Display};
11
12use indexmap::IndexMap;
13use nautilus_core::{UUID4, UnixNanos};
14use rust_decimal::Decimal;
15use serde::{Deserialize, Serialize};
16use ustr::Ustr;
17
18use crate::{
19    enums::{
20        ContingencyType, LiquiditySide, OrderSide, OrderType, TimeInForce, TrailingOffsetType,
21        TriggerType,
22    },
23    events::OrderEvent,
24    identifiers::{
25        AccountId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
26        StrategyId, TradeId, TraderId, VenueOrderId,
27    },
28    types::{Currency, Money, Price, Quantity},
29};
30
31/// Records that a cumulative fill quantity no longer has economic effect.
32///
33/// The correction identity, voided quantity, and commission are cumulative for the referenced
34/// trade. `is_reopened` records positive evidence that the corrected order is executable again
35/// and therefore requires the referenced fill to have been applied locally. Without a local fill,
36/// a non-reopened correction is an authoritative terminal order void.
37#[repr(C)]
38#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(tag = "type")]
40#[cfg_attr(
41    feature = "python",
42    pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
43)]
44#[cfg_attr(
45    feature = "python",
46    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
47)]
48pub struct OrderFillVoided {
49    pub trader_id: TraderId,
50    pub strategy_id: StrategyId,
51    pub instrument_id: InstrumentId,
52    pub client_order_id: ClientOrderId,
53    pub venue_order_id: VenueOrderId,
54    pub account_id: AccountId,
55    pub correction_id: Ustr,
56    pub trade_id: TradeId,
57    pub voided_qty: Quantity,
58    pub commission_voided: Option<Money>,
59    pub order_side: OrderSide,
60    pub order_type: OrderType,
61    pub last_px: Price,
62    pub currency: Currency,
63    pub liquidity_side: LiquiditySide,
64    pub position_id: Option<PositionId>,
65    pub reason: Option<Ustr>,
66    pub info: Option<IndexMap<Ustr, Ustr>>,
67    pub event_id: UUID4,
68    pub ts_event: UnixNanos,
69    pub ts_init: UnixNanos,
70    pub reconciliation: bool,
71    #[serde(default)]
72    pub is_reopened: bool,
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub causation_id: Option<UUID4>,
75}
76
77impl OrderFillVoided {
78    #[expect(clippy::too_many_arguments)]
79    #[must_use]
80    pub fn new(
81        trader_id: TraderId,
82        strategy_id: StrategyId,
83        instrument_id: InstrumentId,
84        client_order_id: ClientOrderId,
85        venue_order_id: VenueOrderId,
86        account_id: AccountId,
87        correction_id: Ustr,
88        trade_id: TradeId,
89        voided_qty: Quantity,
90        commission_voided: Option<Money>,
91        order_side: OrderSide,
92        order_type: OrderType,
93        last_px: Price,
94        currency: Currency,
95        liquidity_side: LiquiditySide,
96        position_id: Option<PositionId>,
97        reason: Option<Ustr>,
98        info: Option<IndexMap<Ustr, Ustr>>,
99        event_id: UUID4,
100        ts_event: UnixNanos,
101        ts_init: UnixNanos,
102        reconciliation: bool,
103        is_reopened: bool,
104    ) -> Self {
105        Self {
106            trader_id,
107            strategy_id,
108            instrument_id,
109            client_order_id,
110            venue_order_id,
111            account_id,
112            correction_id,
113            trade_id,
114            voided_qty,
115            commission_voided,
116            order_side,
117            order_type,
118            last_px,
119            currency,
120            liquidity_side,
121            position_id,
122            reason,
123            info,
124            event_id,
125            ts_event,
126            ts_init,
127            reconciliation,
128            is_reopened,
129            causation_id: None,
130        }
131    }
132}
133
134impl Debug for OrderFillVoided {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        write!(
137            f,
138            "{}(trader_id={}, strategy_id={}, instrument_id={}, client_order_id={}, venue_order_id={}, account_id={}, correction_id={}, trade_id={}, voided_qty={}, commission_voided={:?}, is_reopened={}, event_id={}, ts_event={}, ts_init={})",
139            stringify!(OrderFillVoided),
140            self.trader_id,
141            self.strategy_id,
142            self.instrument_id,
143            self.client_order_id,
144            self.venue_order_id,
145            self.account_id,
146            self.correction_id,
147            self.trade_id,
148            self.voided_qty,
149            self.commission_voided,
150            self.is_reopened,
151            self.event_id,
152            self.ts_event,
153            self.ts_init,
154        )
155    }
156}
157
158impl Display for OrderFillVoided {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        write!(
161            f,
162            "{}(instrument_id={}, client_order_id={}, venue_order_id={}, correction_id={}, trade_id={}, voided_qty={}, is_reopened={}, reason={}, ts_event={})",
163            stringify!(OrderFillVoided),
164            self.instrument_id,
165            self.client_order_id,
166            self.venue_order_id,
167            self.correction_id,
168            self.trade_id,
169            self.voided_qty,
170            self.is_reopened,
171            self.reason.map_or("None", |reason| reason.as_str()),
172            self.ts_event,
173        )
174    }
175}
176
177impl OrderEvent for OrderFillVoided {
178    fn id(&self) -> UUID4 {
179        self.event_id
180    }
181
182    fn type_name(&self) -> &'static str {
183        stringify!(OrderFillVoided)
184    }
185
186    fn order_type(&self) -> Option<OrderType> {
187        Some(self.order_type)
188    }
189
190    fn order_side(&self) -> Option<OrderSide> {
191        Some(self.order_side)
192    }
193
194    fn trader_id(&self) -> TraderId {
195        self.trader_id
196    }
197
198    fn strategy_id(&self) -> StrategyId {
199        self.strategy_id
200    }
201
202    fn instrument_id(&self) -> InstrumentId {
203        self.instrument_id
204    }
205
206    fn trade_id(&self) -> Option<TradeId> {
207        Some(self.trade_id)
208    }
209
210    fn currency(&self) -> Option<Currency> {
211        Some(self.currency)
212    }
213
214    fn client_order_id(&self) -> ClientOrderId {
215        self.client_order_id
216    }
217
218    fn reason(&self) -> Option<Ustr> {
219        self.reason
220    }
221
222    fn quantity(&self) -> Option<Quantity> {
223        Some(self.voided_qty)
224    }
225
226    fn time_in_force(&self) -> Option<TimeInForce> {
227        None
228    }
229
230    fn liquidity_side(&self) -> Option<LiquiditySide> {
231        Some(self.liquidity_side)
232    }
233
234    fn post_only(&self) -> Option<bool> {
235        None
236    }
237
238    fn reduce_only(&self) -> Option<bool> {
239        None
240    }
241
242    fn quote_quantity(&self) -> Option<bool> {
243        None
244    }
245
246    fn reconciliation(&self) -> bool {
247        self.reconciliation
248    }
249
250    fn price(&self) -> Option<Price> {
251        None
252    }
253
254    fn last_px(&self) -> Option<Price> {
255        Some(self.last_px)
256    }
257
258    fn last_qty(&self) -> Option<Quantity> {
259        Some(self.voided_qty)
260    }
261
262    fn activation_price(&self) -> Option<Price> {
263        None
264    }
265
266    fn trigger_price(&self) -> Option<Price> {
267        None
268    }
269
270    fn trigger_type(&self) -> Option<TriggerType> {
271        None
272    }
273
274    fn limit_offset(&self) -> Option<Decimal> {
275        None
276    }
277
278    fn trailing_offset(&self) -> Option<Decimal> {
279        None
280    }
281
282    fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
283        None
284    }
285
286    fn expire_time(&self) -> Option<UnixNanos> {
287        None
288    }
289
290    fn display_qty(&self) -> Option<Quantity> {
291        None
292    }
293
294    fn emulation_trigger(&self) -> Option<TriggerType> {
295        None
296    }
297
298    fn trigger_instrument_id(&self) -> Option<InstrumentId> {
299        None
300    }
301
302    fn contingency_type(&self) -> Option<ContingencyType> {
303        None
304    }
305
306    fn order_list_id(&self) -> Option<OrderListId> {
307        None
308    }
309
310    fn linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
311        None
312    }
313
314    fn parent_order_id(&self) -> Option<ClientOrderId> {
315        None
316    }
317
318    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
319        None
320    }
321
322    fn exec_spawn_id(&self) -> Option<ClientOrderId> {
323        None
324    }
325
326    fn venue_order_id(&self) -> Option<VenueOrderId> {
327        Some(self.venue_order_id)
328    }
329
330    fn account_id(&self) -> Option<AccountId> {
331        Some(self.account_id)
332    }
333
334    fn position_id(&self) -> Option<PositionId> {
335        self.position_id
336    }
337
338    fn commission(&self) -> Option<Money> {
339        self.commission_voided
340    }
341
342    fn ts_event(&self) -> UnixNanos {
343        self.ts_event
344    }
345
346    fn ts_init(&self) -> UnixNanos {
347        self.ts_init
348    }
349}