nautilus_model/defi/pool_analysis/
compare.rs1use super::{position::PoolPosition, profiler::PoolProfiler};
19use crate::defi::pool_analysis::snapshot::PoolSnapshot;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum PoolProfilerComparison {
24 Match,
26 SqrtPriceMismatch,
28 FeeProtocolMismatch,
30 ProtocolFeesMismatch,
32 Mismatch,
34}
35
36impl PoolProfilerComparison {
37 #[must_use]
39 pub const fn is_exact_match(self) -> bool {
40 matches!(self, Self::Match)
41 }
42
43 #[must_use]
45 pub const fn is_valid_for_snapshot(self) -> bool {
46 matches!(
47 self,
48 Self::Match
49 | Self::SqrtPriceMismatch
50 | Self::FeeProtocolMismatch
51 | Self::ProtocolFeesMismatch
52 )
53 }
54}
55
56#[must_use]
76pub fn compare_pool_profiler(profiler: &PoolProfiler, snapshot: &PoolSnapshot) -> bool {
77 compare_pool_profiler_detailed(profiler, snapshot).is_exact_match()
78}
79
80#[must_use]
92pub fn compare_pool_profiler_detailed(
93 profiler: &PoolProfiler,
94 snapshot: &PoolSnapshot,
95) -> PoolProfilerComparison {
96 assert!(profiler.is_initialized, "Profiler is not initialized");
97
98 let mut structural_match = true;
99 let mut sqrt_price_matches = true;
100 let mut fee_protocol_matches = true;
101 let mut protocol_fees_match = true;
102 let total_ticks = snapshot.ticks.len();
103 let total_positions = snapshot.positions.len();
104
105 if snapshot.state.current_tick == profiler.state.current_tick {
106 log::info!("✓ current_tick matches: {}", snapshot.state.current_tick);
107 } else {
108 log::error!(
109 "Tick mismatch: profiler={}, compared={}",
110 profiler.state.current_tick,
111 snapshot.state.current_tick
112 );
113 structural_match = false;
114 }
115
116 if snapshot.state.price_sqrt_ratio_x96 == profiler.state.price_sqrt_ratio_x96 {
117 log::info!(
118 "✓ sqrt_price_x96 matches: {}",
119 profiler.state.price_sqrt_ratio_x96,
120 );
121 } else {
122 log::warn!(
123 "Sqrt ratio mismatch: profiler={}, compared={}",
124 profiler.state.price_sqrt_ratio_x96,
125 snapshot.state.price_sqrt_ratio_x96
126 );
127 sqrt_price_matches = false;
128 }
129
130 if snapshot.state.fee_protocol == profiler.state.fee_protocol
131 && snapshot.state.fee_protocol0_basis_points == profiler.state.fee_protocol0_basis_points
132 && snapshot.state.fee_protocol1_basis_points == profiler.state.fee_protocol1_basis_points
133 {
134 log::info!(
135 "✓ fee_protocol matches: packed={}, token0_bps={:?}, token1_bps={:?}",
136 snapshot.state.fee_protocol,
137 snapshot.state.fee_protocol0_basis_points,
138 snapshot.state.fee_protocol1_basis_points,
139 );
140 } else {
141 log::warn!(
142 "Fee protocol mismatch: profiler=(packed={}, token0_bps={:?}, token1_bps={:?}), compared=(packed={}, token0_bps={:?}, token1_bps={:?})",
143 profiler.state.fee_protocol,
144 profiler.state.fee_protocol0_basis_points,
145 profiler.state.fee_protocol1_basis_points,
146 snapshot.state.fee_protocol,
147 snapshot.state.fee_protocol0_basis_points,
148 snapshot.state.fee_protocol1_basis_points,
149 );
150 fee_protocol_matches = false;
151 }
152
153 if snapshot.state.protocol_fees_token0 == profiler.state.protocol_fees_token0
154 && snapshot.state.protocol_fees_token1 == profiler.state.protocol_fees_token1
155 {
156 log::info!(
157 "✓ protocol_fees match: token0={}, token1={}",
158 snapshot.state.protocol_fees_token0,
159 snapshot.state.protocol_fees_token1
160 );
161 } else {
162 log::warn!(
163 "Protocol fees mismatch: profiler=(token0={}, token1={}), compared=(token0={}, token1={})",
164 profiler.state.protocol_fees_token0,
165 profiler.state.protocol_fees_token1,
166 snapshot.state.protocol_fees_token0,
167 snapshot.state.protocol_fees_token1
168 );
169 protocol_fees_match = false;
170 }
171
172 if snapshot.state.liquidity == profiler.tick_map.liquidity {
173 log::info!("✓ liquidity matches: {}", snapshot.state.liquidity);
174 } else {
175 log::error!(
176 "Liquidity mismatch: profiler={}, compared={}",
177 profiler.tick_map.liquidity,
178 snapshot.state.liquidity
179 );
180 structural_match = false;
181 }
182
183 let mut tick_mismatches = 0;
187
188 for tick in &snapshot.ticks {
189 if let Some(profiler_tick) = profiler.get_tick(tick.value) {
190 let mut all_tick_fields_matching = true;
191
192 if profiler_tick.liquidity_net != tick.liquidity_net {
193 log::error!(
194 "Tick {} mismatch on net liquidity: profiler={}, compared={}",
195 tick.value,
196 profiler_tick.liquidity_net,
197 tick.liquidity_net
198 );
199 all_tick_fields_matching = false;
200 }
201
202 if profiler_tick.liquidity_gross != tick.liquidity_gross {
203 log::error!(
204 "Tick {} mismatch on gross liquidity: profiler={}, compared={}",
205 tick.value,
206 profiler_tick.liquidity_gross,
207 tick.liquidity_gross
208 );
209 all_tick_fields_matching = false;
210 }
211 if !all_tick_fields_matching {
214 tick_mismatches += 1;
215 structural_match = false;
216 }
217 } else {
218 log::error!(
219 "Tick {} not found in the profiler but provided in the compare mapping",
220 tick.value
221 );
222 structural_match = false;
223 }
224 }
225
226 if tick_mismatches == 0 {
227 log::info!("✓ Provided {total_ticks} ticks with liquidity net and gross are matching");
228 }
229
230 let mut position_mismatches = 0;
232
233 for position in &snapshot.positions {
234 if let Some(profiler_position) =
235 profiler.get_position(&position.owner, position.tick_lower, position.tick_upper)
236 {
237 let position_key = PoolPosition::get_position_key(
238 &position.owner,
239 position.tick_lower,
240 position.tick_upper,
241 );
242
243 if position.liquidity != profiler_position.liquidity {
244 log::error!(
245 "Position '{}' mismatch on liquidity: profiler={}, compared={}",
246 position_key,
247 profiler_position.liquidity,
248 position.liquidity
249 );
250 position_mismatches += 1;
251 }
252 } else {
254 log::error!(
255 "Position {} not found in the profiler but provided in the compare mapping",
256 position.owner
257 );
258 structural_match = false;
259 }
260 }
261
262 if position_mismatches == 0 {
263 log::info!("✓ Provided {total_positions} active positions with liquidity are matching");
264 } else {
265 structural_match = false;
266 }
267
268 if !structural_match {
269 PoolProfilerComparison::Mismatch
270 } else if !sqrt_price_matches {
271 log::warn!("Pool profiler sqrt ratio differs, but all structural state matches");
272 PoolProfilerComparison::SqrtPriceMismatch
273 } else if !fee_protocol_matches {
274 log::warn!("Pool profiler fee protocol differs, but all structural state matches");
275 PoolProfilerComparison::FeeProtocolMismatch
276 } else if !protocol_fees_match {
277 log::warn!("Pool profiler protocol fees differ, but all structural state matches");
278 PoolProfilerComparison::ProtocolFeesMismatch
279 } else {
280 PoolProfilerComparison::Match
281 }
282}