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    /// The trader ID associated with the event.
50    pub trader_id: TraderId,
51    /// The strategy ID associated with the event.
52    pub strategy_id: StrategyId,
53    /// The instrument ID associated with the event.
54    pub instrument_id: InstrumentId,
55    /// The client order ID associated with the event.
56    pub client_order_id: ClientOrderId,
57    /// The venue order ID associated with the event.
58    pub venue_order_id: VenueOrderId,
59    /// The account ID associated with the event.
60    pub account_id: AccountId,
61    /// The venue identifier for the correction which voided the fill.
62    pub correction_id: Ustr,
63    /// The trade ID of the voided fill.
64    pub trade_id: TradeId,
65    /// The quantity removed from the order by the void.
66    pub voided_qty: Quantity,
67    /// The commission reversed by the void, if any.
68    pub commission_voided: Option<Money>,
69    /// The order side of the voided fill.
70    pub order_side: OrderSide,
71    /// The order type of the voided fill.
72    pub order_type: OrderType,
73    /// The last price of the voided fill.
74    pub last_px: Price,
75    /// The currency of the voided fill.
76    pub currency: Currency,
77    /// The liquidity side of the voided fill.
78    pub liquidity_side: LiquiditySide,
79    /// The position ID associated with the voided fill, if any.
80    pub position_id: Option<PositionId>,
81    /// The reason the fill was voided.
82    pub reason: Option<Ustr>,
83    /// The venue specific information for the void.
84    pub info: Option<IndexMap<Ustr, Ustr>>,
85    /// The unique identifier for the event.
86    pub event_id: UUID4,
87    /// UNIX timestamp (nanoseconds) when the event occurred.
88    pub ts_event: UnixNanos,
89    /// UNIX timestamp (nanoseconds) when the event was initialized.
90    pub ts_init: UnixNanos,
91    /// If the event was generated during reconciliation.
92    pub reconciliation: bool,
93    /// If the void reopened an order which had already closed.
94    #[serde(default)]
95    pub is_reopened: bool,
96    /// The causation ID associated with the event.
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub causation_id: Option<UUID4>,
99}
100
101impl OrderFillVoided {
102    #[expect(clippy::too_many_arguments)]
103    #[must_use]
104    pub fn new(
105        trader_id: TraderId,
106        strategy_id: StrategyId,
107        instrument_id: InstrumentId,
108        client_order_id: ClientOrderId,
109        venue_order_id: VenueOrderId,
110        account_id: AccountId,
111        correction_id: Ustr,
112        trade_id: TradeId,
113        voided_qty: Quantity,
114        commission_voided: Option<Money>,
115        order_side: OrderSide,
116        order_type: OrderType,
117        last_px: Price,
118        currency: Currency,
119        liquidity_side: LiquiditySide,
120        position_id: Option<PositionId>,
121        reason: Option<Ustr>,
122        info: Option<IndexMap<Ustr, Ustr>>,
123        event_id: UUID4,
124        ts_event: UnixNanos,
125        ts_init: UnixNanos,
126        reconciliation: bool,
127        is_reopened: bool,
128    ) -> Self {
129        Self {
130            trader_id,
131            strategy_id,
132            instrument_id,
133            client_order_id,
134            venue_order_id,
135            account_id,
136            correction_id,
137            trade_id,
138            voided_qty,
139            commission_voided,
140            order_side,
141            order_type,
142            last_px,
143            currency,
144            liquidity_side,
145            position_id,
146            reason,
147            info,
148            event_id,
149            ts_event,
150            ts_init,
151            reconciliation,
152            is_reopened,
153            causation_id: None,
154        }
155    }
156}
157
158impl Debug for OrderFillVoided {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        write!(
161            f,
162            "{}(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={})",
163            stringify!(OrderFillVoided),
164            self.trader_id,
165            self.strategy_id,
166            self.instrument_id,
167            self.client_order_id,
168            self.venue_order_id,
169            self.account_id,
170            self.correction_id,
171            self.trade_id,
172            self.voided_qty,
173            self.commission_voided,
174            self.is_reopened,
175            self.event_id,
176            self.ts_event,
177            self.ts_init,
178        )
179    }
180}
181
182impl Display for OrderFillVoided {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        write!(
185            f,
186            "{}(instrument_id={}, client_order_id={}, venue_order_id={}, correction_id={}, trade_id={}, voided_qty={}, is_reopened={}, reason={}, ts_event={})",
187            stringify!(OrderFillVoided),
188            self.instrument_id,
189            self.client_order_id,
190            self.venue_order_id,
191            self.correction_id,
192            self.trade_id,
193            self.voided_qty,
194            self.is_reopened,
195            self.reason.map_or("None", |reason| reason.as_str()),
196            self.ts_event,
197        )
198    }
199}
200
201impl OrderEvent for OrderFillVoided {
202    fn id(&self) -> UUID4 {
203        self.event_id
204    }
205
206    fn type_name(&self) -> &'static str {
207        stringify!(OrderFillVoided)
208    }
209
210    fn order_type(&self) -> Option<OrderType> {
211        Some(self.order_type)
212    }
213
214    fn order_side(&self) -> Option<OrderSide> {
215        Some(self.order_side)
216    }
217
218    fn trader_id(&self) -> TraderId {
219        self.trader_id
220    }
221
222    fn strategy_id(&self) -> StrategyId {
223        self.strategy_id
224    }
225
226    fn instrument_id(&self) -> InstrumentId {
227        self.instrument_id
228    }
229
230    fn trade_id(&self) -> Option<TradeId> {
231        Some(self.trade_id)
232    }
233
234    fn currency(&self) -> Option<Currency> {
235        Some(self.currency)
236    }
237
238    fn client_order_id(&self) -> ClientOrderId {
239        self.client_order_id
240    }
241
242    fn reason(&self) -> Option<Ustr> {
243        self.reason
244    }
245
246    fn quantity(&self) -> Option<Quantity> {
247        Some(self.voided_qty)
248    }
249
250    fn time_in_force(&self) -> Option<TimeInForce> {
251        None
252    }
253
254    fn liquidity_side(&self) -> Option<LiquiditySide> {
255        Some(self.liquidity_side)
256    }
257
258    fn post_only(&self) -> Option<bool> {
259        None
260    }
261
262    fn reduce_only(&self) -> Option<bool> {
263        None
264    }
265
266    fn quote_quantity(&self) -> Option<bool> {
267        None
268    }
269
270    fn reconciliation(&self) -> bool {
271        self.reconciliation
272    }
273
274    fn price(&self) -> Option<Price> {
275        None
276    }
277
278    fn last_px(&self) -> Option<Price> {
279        Some(self.last_px)
280    }
281
282    fn last_qty(&self) -> Option<Quantity> {
283        Some(self.voided_qty)
284    }
285
286    fn activation_price(&self) -> Option<Price> {
287        None
288    }
289
290    fn trigger_price(&self) -> Option<Price> {
291        None
292    }
293
294    fn trigger_type(&self) -> Option<TriggerType> {
295        None
296    }
297
298    fn limit_offset(&self) -> Option<Decimal> {
299        None
300    }
301
302    fn trailing_offset(&self) -> Option<Decimal> {
303        None
304    }
305
306    fn trailing_offset_type(&self) -> Option<TrailingOffsetType> {
307        None
308    }
309
310    fn expire_time(&self) -> Option<UnixNanos> {
311        None
312    }
313
314    fn display_qty(&self) -> Option<Quantity> {
315        None
316    }
317
318    fn emulation_trigger(&self) -> Option<TriggerType> {
319        None
320    }
321
322    fn trigger_instrument_id(&self) -> Option<InstrumentId> {
323        None
324    }
325
326    fn contingency_type(&self) -> Option<ContingencyType> {
327        None
328    }
329
330    fn order_list_id(&self) -> Option<OrderListId> {
331        None
332    }
333
334    fn linked_order_ids(&self) -> Option<Vec<ClientOrderId>> {
335        None
336    }
337
338    fn parent_order_id(&self) -> Option<ClientOrderId> {
339        None
340    }
341
342    fn exec_algorithm_id(&self) -> Option<ExecAlgorithmId> {
343        None
344    }
345
346    fn exec_spawn_id(&self) -> Option<ClientOrderId> {
347        None
348    }
349
350    fn venue_order_id(&self) -> Option<VenueOrderId> {
351        Some(self.venue_order_id)
352    }
353
354    fn account_id(&self) -> Option<AccountId> {
355        Some(self.account_id)
356    }
357
358    fn position_id(&self) -> Option<PositionId> {
359        self.position_id
360    }
361
362    fn commission(&self) -> Option<Money> {
363        self.commission_voided
364    }
365
366    fn ts_event(&self) -> UnixNanos {
367        self.ts_event
368    }
369
370    fn ts_init(&self) -> UnixNanos {
371        self.ts_init
372    }
373    fn causation_id(&self) -> Option<UUID4> {
374        self.causation_id
375    }
376
377    fn correction_id(&self) -> Option<Ustr> {
378        Some(self.correction_id)
379    }
380
381    fn is_reopened(&self) -> bool {
382        self.is_reopened
383    }
384
385    fn info(&self) -> Option<IndexMap<Ustr, Ustr>> {
386        self.info.clone()
387    }
388}