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 log::info!("✓ fee_protocol matches: {}", snapshot.state.fee_protocol);
132 } else {
133 log::warn!(
134 "Fee protocol mismatch: profiler={}, compared={}",
135 profiler.state.fee_protocol,
136 snapshot.state.fee_protocol
137 );
138 fee_protocol_matches = false;
139 }
140
141 if snapshot.state.protocol_fees_token0 == profiler.state.protocol_fees_token0
142 && snapshot.state.protocol_fees_token1 == profiler.state.protocol_fees_token1
143 {
144 log::info!(
145 "✓ protocol_fees match: token0={}, token1={}",
146 snapshot.state.protocol_fees_token0,
147 snapshot.state.protocol_fees_token1
148 );
149 } else {
150 log::warn!(
151 "Protocol fees mismatch: profiler=(token0={}, token1={}), compared=(token0={}, token1={})",
152 profiler.state.protocol_fees_token0,
153 profiler.state.protocol_fees_token1,
154 snapshot.state.protocol_fees_token0,
155 snapshot.state.protocol_fees_token1
156 );
157 protocol_fees_match = false;
158 }
159
160 if snapshot.state.liquidity == profiler.tick_map.liquidity {
161 log::info!("✓ liquidity matches: {}", snapshot.state.liquidity);
162 } else {
163 log::error!(
164 "Liquidity mismatch: profiler={}, compared={}",
165 profiler.tick_map.liquidity,
166 snapshot.state.liquidity
167 );
168 structural_match = false;
169 }
170
171 let mut tick_mismatches = 0;
175
176 for tick in &snapshot.ticks {
177 if let Some(profiler_tick) = profiler.get_tick(tick.value) {
178 let mut all_tick_fields_matching = true;
179
180 if profiler_tick.liquidity_net != tick.liquidity_net {
181 log::error!(
182 "Tick {} mismatch on net liquidity: profiler={}, compared={}",
183 tick.value,
184 profiler_tick.liquidity_net,
185 tick.liquidity_net
186 );
187 all_tick_fields_matching = false;
188 }
189
190 if profiler_tick.liquidity_gross != tick.liquidity_gross {
191 log::error!(
192 "Tick {} mismatch on gross liquidity: profiler={}, compared={}",
193 tick.value,
194 profiler_tick.liquidity_gross,
195 tick.liquidity_gross
196 );
197 all_tick_fields_matching = false;
198 }
199 if !all_tick_fields_matching {
202 tick_mismatches += 1;
203 structural_match = false;
204 }
205 } else {
206 log::error!(
207 "Tick {} not found in the profiler but provided in the compare mapping",
208 tick.value
209 );
210 structural_match = false;
211 }
212 }
213
214 if tick_mismatches == 0 {
215 log::info!("✓ Provided {total_ticks} ticks with liquidity net and gross are matching");
216 }
217
218 let mut position_mismatches = 0;
220
221 for position in &snapshot.positions {
222 if let Some(profiler_position) =
223 profiler.get_position(&position.owner, position.tick_lower, position.tick_upper)
224 {
225 let position_key = PoolPosition::get_position_key(
226 &position.owner,
227 position.tick_lower,
228 position.tick_upper,
229 );
230
231 if position.liquidity != profiler_position.liquidity {
232 log::error!(
233 "Position '{}' mismatch on liquidity: profiler={}, compared={}",
234 position_key,
235 profiler_position.liquidity,
236 position.liquidity
237 );
238 position_mismatches += 1;
239 }
240 } else {
242 log::error!(
243 "Position {} not found in the profiler but provided in the compare mapping",
244 position.owner
245 );
246 structural_match = false;
247 }
248 }
249
250 if position_mismatches == 0 {
251 log::info!("✓ Provided {total_positions} active positions with liquidity are matching");
252 } else {
253 structural_match = false;
254 }
255
256 if !structural_match {
257 PoolProfilerComparison::Mismatch
258 } else if !sqrt_price_matches {
259 log::warn!("Pool profiler sqrt ratio differs, but all structural state matches");
260 PoolProfilerComparison::SqrtPriceMismatch
261 } else if !fee_protocol_matches {
262 log::warn!("Pool profiler fee protocol differs, but all structural state matches");
263 PoolProfilerComparison::FeeProtocolMismatch
264 } else if !protocol_fees_match {
265 log::warn!("Pool profiler protocol fees differ, but all structural state matches");
266 PoolProfilerComparison::ProtocolFeesMismatch
267 } else {
268 PoolProfilerComparison::Match
269 }
270}