Skip to main content

nautilus_execution/reconciliation/
types.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//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Shared reconciliation value types.
17
18use indexmap::{IndexMap, IndexSet};
19use nautilus_model::{
20    enums::{AvgPxReconciliation, OrderSide, PositionSide},
21    identifiers::{AccountId, InstrumentId, TradeId, VenueOrderId},
22    reports::{FillReport, OrderStatusReport},
23};
24use rust_decimal::Decimal;
25
26/// Immutable snapshot of fill data for position simulation.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub(super) struct FillSnapshot {
29    /// The venue order ID.
30    pub venue_order_id: VenueOrderId,
31    /// The order side (BUY or SELL).
32    pub side: OrderSide,
33    /// The fill quantity.
34    pub qty: Decimal,
35    /// The fill price.
36    pub px: Decimal,
37    /// The event timestamp (nanoseconds).
38    pub ts_event: u64,
39}
40
41/// Represents a position snapshot from the venue.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub(super) struct VenuePositionSnapshot {
44    /// The position side (Long, Short, or Flat).
45    pub side: PositionSide,
46    /// The position quantity (always positive, even for Short).
47    pub qty: Decimal,
48    /// The average entry price (can be zero for Flat positions).
49    pub avg_px: Decimal,
50    /// How reconciliation may use the average entry price.
51    pub avg_px_reconciliation: AvgPxReconciliation,
52    /// The decimal places the venue kept for the average entry price, if it rounds or truncates.
53    pub avg_px_precision: Option<u8>,
54}
55
56impl VenuePositionSnapshot {
57    /// Returns the quantity signed by position side.
58    pub(super) fn signed_qty(&self) -> Decimal {
59        match self.side {
60            PositionSide::Long => self.qty,
61            PositionSide::Short => -self.qty,
62            PositionSide::Flat => Decimal::ZERO,
63        }
64    }
65}
66
67/// Result of the fill adjustment process.
68#[derive(Debug, Clone, PartialEq)]
69pub(super) enum FillAdjustmentResult {
70    /// No adjustment needed - return fills unchanged.
71    NoAdjustment,
72    /// Add synthetic opening fill to oldest lifecycle.
73    AddSyntheticOpening {
74        /// The synthetic fill to add at the beginning.
75        synthetic_fill: FillSnapshot,
76        /// All existing fills to keep.
77        existing_fills: Vec<FillSnapshot>,
78    },
79    /// Replace entire current lifecycle with single synthetic fill.
80    ReplaceCurrentLifecycle {
81        /// The single synthetic fill representing the entire position.
82        synthetic_fill: FillSnapshot,
83    },
84    /// Filter fills to current lifecycle only (after last zero-crossing).
85    FilterToCurrentLifecycle {
86        /// Timestamp of the last zero-crossing.
87        last_zero_crossing_ts: u64,
88        /// Fills from current lifecycle.
89        current_lifecycle_fills: Vec<FillSnapshot>,
90    },
91}
92
93impl FillSnapshot {
94    /// Create a new fill snapshot.
95    #[must_use]
96    pub(super) fn new(
97        venue_order_id: VenueOrderId,
98        side: OrderSide,
99        qty: Decimal,
100        px: Decimal,
101        ts_event: u64,
102    ) -> Self {
103        Self {
104            venue_order_id,
105            side,
106            qty,
107            px,
108            ts_event,
109        }
110    }
111
112    /// Return signed direction multiplier: +1 for BUY, -1 for SELL.
113    #[must_use]
114    pub(super) fn direction(&self) -> i8 {
115        match self.side {
116            OrderSide::Buy => 1,
117            OrderSide::Sell => -1,
118        }
119    }
120}
121
122/// Result of processing fill reports for reconciliation.
123#[derive(Debug, Clone)]
124pub struct ReconciliationResult {
125    /// Order status reports keyed by venue order ID.
126    pub orders: IndexMap<VenueOrderId, OrderStatusReport>,
127    /// Fill reports keyed by venue order ID.
128    pub fills: IndexMap<VenueOrderId, Vec<FillReport>>,
129    /// Orders whose fills recover order state only because synthetic fills replace their economics.
130    pub order_only_ids: IndexSet<VenueOrderId>,
131    /// Fills that recover order state only because they precede the current position lifecycle.
132    pub order_only_fill_keys: IndexSet<(AccountId, InstrumentId, TradeId)>,
133}