Skip to main content

nautilus_model/defi/pool_analysis/
snapshot.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
16use alloy_primitives::{U160, U256};
17use nautilus_core::UnixNanos;
18use serde::{Deserialize, Serialize};
19
20use crate::{
21    defi::{
22        data::block::BlockPosition, pool_analysis::position::PoolPosition, tick_map::tick::PoolTick,
23    },
24    identifiers::InstrumentId,
25};
26
27/// Protocol-fee denominator for basis-point fee shares.
28pub const PROTOCOL_FEE_BASIS_POINTS_DENOMINATOR: u32 = 10_000;
29
30/// Complete snapshot of a liquidity pool's state at a specific point in time.
31///
32/// `PoolSnapshot` provides a self-contained representation of a pool's
33/// entire state, bundling together the global state variables, all liquidity positions,
34/// and the complete tick distribution.
35#[cfg_attr(
36    feature = "python",
37    pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
38)]
39#[cfg_attr(
40    feature = "python",
41    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
42)]
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct PoolSnapshot {
45    /// The instrument ID of the pool this snapshot represents.
46    pub instrument_id: InstrumentId,
47    /// Global pool state including price, tick, fees, and cumulative flows.
48    pub state: PoolState,
49    /// All liquidity positions in the pool.
50    pub positions: Vec<PoolPosition>,
51    /// Complete tick distribution across the pool's price range.
52    pub ticks: Vec<PoolTick>,
53    /// Analytics counters for the pool.
54    pub analytics: PoolAnalytics,
55    /// Block position where this snapshot was taken.
56    pub block_position: BlockPosition,
57    /// UNIX timestamp (nanoseconds) when the snapshot event occurred.
58    #[serde(default)]
59    pub ts_event: UnixNanos,
60    /// UNIX timestamp (nanoseconds) when the instance was created.
61    #[serde(default)]
62    pub ts_init: UnixNanos,
63}
64
65impl PoolSnapshot {
66    /// Creates a new `PoolSnapshot` with the specified parameters.
67    #[must_use]
68    #[expect(clippy::too_many_arguments)]
69    pub fn new(
70        instrument_id: InstrumentId,
71        state: PoolState,
72        positions: Vec<PoolPosition>,
73        ticks: Vec<PoolTick>,
74        analytics: PoolAnalytics,
75        block_position: BlockPosition,
76        ts_event: UnixNanos,
77        ts_init: UnixNanos,
78    ) -> Self {
79        Self {
80            instrument_id,
81            state,
82            positions,
83            ticks,
84            analytics,
85            block_position,
86            ts_event,
87            ts_init,
88        }
89    }
90}
91
92/// Global state snapshot of a liquidity pool at a specific point in time.
93///
94/// `PoolState` encapsulates the core global variables that define a UniswapV3-style
95/// AMM pool's current state. This includes the current price position, cumulative
96/// deposit/withdrawal flows, and protocol fee configuration.
97#[cfg_attr(
98    feature = "python",
99    pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
100)]
101#[cfg_attr(
102    feature = "python",
103    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
104)]
105#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
106pub struct PoolState {
107    /// Current tick position of the pool price.
108    pub current_tick: i32,
109    /// Current sqrt price ratio as Q64.96 fixed point number.
110    pub price_sqrt_ratio_x96: U160,
111    /// Current active liquidity in the pool.
112    pub liquidity: u128,
113    /// Accumulated protocol fees in token0 units.
114    pub protocol_fees_token0: U256,
115    /// Accumulated protocol fees in token1 units.
116    pub protocol_fees_token1: U256,
117    /// Protocol fee packed: lower 4 bits for token0, upper 4 bits for token1.
118    pub fee_protocol: u8,
119    /// Token0 protocol-fee share in basis points, when applicable.
120    #[serde(default)]
121    pub fee_protocol0_basis_points: Option<u32>,
122    /// Token1 protocol-fee share in basis points, when applicable.
123    #[serde(default)]
124    pub fee_protocol1_basis_points: Option<u32>,
125    /// Global fee growth for token0 as Q128.128 fixed-point number.
126    pub fee_growth_global_0: U256,
127    /// Global fee growth for token1 as Q128.128 fixed-point number.
128    pub fee_growth_global_1: U256,
129}
130
131impl PoolState {
132    /// Creates a new `PoolState` with the specified parameters.
133    #[must_use]
134    pub fn new(protocol_fees_token0: U256, protocol_fees_token1: U256, fee_protocol: u8) -> Self {
135        Self {
136            current_tick: 0,
137            price_sqrt_ratio_x96: U160::ZERO,
138            liquidity: 0,
139            protocol_fees_token0,
140            protocol_fees_token1,
141            fee_protocol,
142            fee_protocol0_basis_points: None,
143            fee_protocol1_basis_points: None,
144            fee_growth_global_0: U256::ZERO,
145            fee_growth_global_1: U256::ZERO,
146        }
147    }
148
149    /// Returns the Uniswap V3 protocol-fee denominator for the input token.
150    #[must_use]
151    pub const fn uniswap_v3_fee_protocol(&self, zero_for_one: bool) -> u8 {
152        if zero_for_one {
153            self.fee_protocol % 16
154        } else {
155            self.fee_protocol >> 4
156        }
157    }
158
159    /// Returns the basis-point protocol-fee share for the input token, when applicable.
160    #[must_use]
161    pub const fn fee_protocol_basis_points(&self, zero_for_one: bool) -> Option<u32> {
162        if zero_for_one {
163            self.fee_protocol0_basis_points
164        } else {
165            self.fee_protocol1_basis_points
166        }
167    }
168
169    /// Sets the Uniswap V3 packed protocol-fee byte and clears the basis-point representation.
170    pub fn set_uniswap_v3_fee_protocol(&mut self, fee_protocol: u8) {
171        self.fee_protocol = fee_protocol;
172        self.fee_protocol0_basis_points = None;
173        self.fee_protocol1_basis_points = None;
174    }
175
176    /// Sets basis-point protocol-fee shares and clears the Uniswap packed byte.
177    pub fn set_protocol_fee_basis_points(&mut self, fee_protocol0: u32, fee_protocol1: u32) {
178        self.fee_protocol = 0;
179        self.fee_protocol0_basis_points = Some(fee_protocol0);
180        self.fee_protocol1_basis_points = Some(fee_protocol1);
181    }
182}
183
184impl Default for PoolState {
185    fn default() -> Self {
186        Self {
187            current_tick: 0,
188            price_sqrt_ratio_x96: U160::ZERO,
189            liquidity: 0,
190            protocol_fees_token0: U256::ZERO,
191            protocol_fees_token1: U256::ZERO,
192            fee_protocol: 0,
193            fee_protocol0_basis_points: None,
194            fee_protocol1_basis_points: None,
195            fee_growth_global_0: U256::ZERO,
196            fee_growth_global_1: U256::ZERO,
197        }
198    }
199}
200
201/// Analytics counters and metrics for pool operations.
202///
203/// It tracks cumulative statistics about pool activity, including
204/// deposit and collection flows, event counts, and performance metrics for debugging.
205#[cfg_attr(
206    feature = "python",
207    pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
208)]
209#[cfg_attr(
210    feature = "python",
211    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
212)]
213#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
214pub struct PoolAnalytics {
215    /// Total amount of token0 deposited through mints.
216    pub total_amount0_deposited: U256,
217    /// Total amount of token1 deposited through mints.
218    pub total_amount1_deposited: U256,
219    /// Total amount of token0 collected
220    pub total_amount0_collected: U256,
221    /// Total amount of token1 collected.
222    pub total_amount1_collected: U256,
223    /// Total number of swap events processed.
224    pub total_swaps: u64,
225    /// Total number of mint events processed.
226    pub total_mints: u64,
227    /// Total number of burn events processed.
228    pub total_burns: u64,
229    /// Total number of fee collection events processed.
230    pub total_fee_collects: u64,
231    /// Total number of flash events processed.
232    pub total_flashes: u64,
233    /// Liquidity utilization rate (active liquidity / total liquidity)
234    pub liquidity_utilization_rate: f64,
235}
236
237impl Default for PoolAnalytics {
238    fn default() -> Self {
239        Self {
240            total_amount0_deposited: U256::ZERO,
241            total_amount1_deposited: U256::ZERO,
242            total_amount0_collected: U256::ZERO,
243            total_amount1_collected: U256::ZERO,
244            total_swaps: 0,
245            total_mints: 0,
246            total_burns: 0,
247            total_fee_collects: 0,
248            total_flashes: 0,
249            liquidity_utilization_rate: 0.0,
250        }
251    }
252}