nautilus_model/defi/tick_map/
tick.rs1use std::cmp::Ord;
17
18use alloy_primitives::U256;
19
20use crate::defi::tick_map::liquidity_math::liquidity_math_add;
21
22#[derive(Debug, Clone)]
28pub struct CrossedTick {
29 pub tick: i32,
31 pub zero_for_one: bool,
33 pub fee_growth_0: U256,
35 pub fee_growth_1: U256,
37}
38
39impl CrossedTick {
40 pub fn new(tick: i32, zero_for_one: bool, fee_growth_0: U256, fee_growth_1: U256) -> Self {
42 Self {
43 tick,
44 zero_for_one,
45 fee_growth_0,
46 fee_growth_1,
47 }
48 }
49}
50
51#[cfg_attr(
53 feature = "python",
54 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
55)]
56#[derive(
57 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
58)]
59pub struct PoolTick {
60 pub value: i32,
62 pub liquidity_gross: u128,
64 pub liquidity_net: i128,
66 pub fee_growth_outside_0: U256,
68 pub fee_growth_outside_1: U256,
70 pub initialized: bool,
72 pub last_updated_block: u64,
74 pub updates_count: usize,
76}
77
78impl PoolTick {
79 pub const MIN_TICK: i32 = -887272;
81 pub const MAX_TICK: i32 = -Self::MIN_TICK;
83
84 #[must_use]
86 pub fn new(
87 value: i32,
88 liquidity_gross: u128,
89 liquidity_net: i128,
90 fee_growth_outside_0: U256,
91 fee_growth_outside_1: U256,
92 initialized: bool,
93 last_updated_block: u64,
94 ) -> Self {
95 Self {
96 value,
97 liquidity_gross,
98 liquidity_net,
99 fee_growth_outside_0,
100 fee_growth_outside_1,
101 initialized,
102 last_updated_block,
103 updates_count: 0,
104 }
105 }
106
107 pub fn from_tick(tick: i32) -> Self {
109 Self::new(tick, 0, 0, U256::ZERO, U256::ZERO, false, 0)
110 }
111
112 pub fn update_liquidity(&mut self, liquidity_delta: i128, upper: bool) -> u128 {
114 let liquidity_gross_before = self.liquidity_gross;
115 self.liquidity_gross = liquidity_math_add(self.liquidity_gross, liquidity_delta);
116
117 if upper {
119 self.liquidity_net -= liquidity_delta;
120 } else {
121 self.liquidity_net += liquidity_delta;
122 }
123 self.updates_count += 1;
124
125 liquidity_gross_before
126 }
127
128 pub fn clear(&mut self) {
130 self.liquidity_gross = 0;
131 self.liquidity_net = 0;
132 self.fee_growth_outside_0 = U256::ZERO;
133 self.fee_growth_outside_1 = U256::ZERO;
134 self.initialized = false;
135 }
136
137 #[must_use]
139 pub fn is_active(&self) -> bool {
140 self.initialized && self.liquidity_gross > 0
141 }
142
143 pub fn update_fee_growth(&mut self, fee_growth_global_0: U256, fee_growth_global_1: U256) {
145 self.fee_growth_outside_0 = fee_growth_global_0 - self.fee_growth_outside_0;
146 self.fee_growth_outside_1 = fee_growth_global_1 - self.fee_growth_outside_1;
147 }
148
149 pub fn get_max_tick(tick_spacing: i32) -> i32 {
151 (Self::MAX_TICK / tick_spacing) * tick_spacing
153 }
154
155 pub fn get_min_tick(tick_spacing: i32) -> i32 {
157 (Self::MIN_TICK / tick_spacing) * tick_spacing
159 }
160}
161
162#[cfg(test)]
167mod tests {
168 use rstest::rstest;
169
170 use super::*;
171
172 #[rstest]
173 fn test_update_liquidity_add_remove() {
174 let mut tick = PoolTick::from_tick(100);
175 tick.initialized = true;
176
177 tick.update_liquidity(1000, false); assert_eq!(tick.liquidity_gross, 1000);
180 assert_eq!(tick.liquidity_net, 1000); assert!(tick.is_active());
182
183 tick.update_liquidity(500, false);
185 assert_eq!(tick.liquidity_gross, 1500);
186 assert_eq!(tick.liquidity_net, 1500);
187 assert!(tick.is_active());
188
189 tick.update_liquidity(-300, false);
191 assert_eq!(tick.liquidity_gross, 1200);
192 assert_eq!(tick.liquidity_net, 1200);
193 assert!(tick.is_active());
194
195 tick.update_liquidity(-1200, false);
197 assert_eq!(tick.liquidity_gross, 0);
198 assert_eq!(tick.liquidity_net, 0);
199 assert!(!tick.is_active()); }
201
202 #[rstest]
203 fn test_update_liquidity_upper_tick() {
204 let mut tick = PoolTick::from_tick(200);
205 tick.initialized = true;
206
207 tick.update_liquidity(1000, true);
209 assert_eq!(tick.liquidity_gross, 1000);
210 assert_eq!(tick.liquidity_net, -1000); assert!(tick.is_active());
212
213 tick.update_liquidity(-500, true);
215 assert_eq!(tick.liquidity_gross, 500);
216 assert_eq!(tick.liquidity_net, -500); assert!(tick.is_active());
218 }
219
220 #[rstest]
221 fn test_get_max_tick() {
222 let max_tick_1 = PoolTick::get_max_tick(1);
226 assert_eq!(max_tick_1, 887272); let max_tick_10 = PoolTick::get_max_tick(10);
230 assert_eq!(max_tick_10, 887270); assert_eq!(max_tick_10 % 10, 0);
232 assert!(max_tick_10 <= PoolTick::MAX_TICK);
233
234 let max_tick_60 = PoolTick::get_max_tick(60);
236 assert_eq!(max_tick_60, 887220); assert_eq!(max_tick_60 % 60, 0);
238 assert!(max_tick_60 <= PoolTick::MAX_TICK);
239
240 let max_tick_200 = PoolTick::get_max_tick(200);
242 assert_eq!(max_tick_200, 887200); assert_eq!(max_tick_200 % 200, 0);
244 assert!(max_tick_200 <= PoolTick::MAX_TICK);
245 }
246
247 #[rstest]
248 fn test_get_min_tick() {
249 let min_tick_1 = PoolTick::get_min_tick(1);
253 assert_eq!(min_tick_1, -887272); let min_tick_10 = PoolTick::get_min_tick(10);
257 assert_eq!(min_tick_10, -887270); assert_eq!(min_tick_10 % 10, 0);
259 assert!(min_tick_10 >= PoolTick::MIN_TICK);
260
261 let min_tick_60 = PoolTick::get_min_tick(60);
263 assert_eq!(min_tick_60, -887220); assert_eq!(min_tick_60 % 60, 0);
265 assert!(min_tick_60 >= PoolTick::MIN_TICK);
266
267 let min_tick_200 = PoolTick::get_min_tick(200);
269 assert_eq!(min_tick_200, -887200); assert_eq!(min_tick_200 % 200, 0);
271 assert!(min_tick_200 >= PoolTick::MIN_TICK);
272 }
273
274 #[rstest]
275 fn test_tick_spacing_symmetry() {
276 let spacings = [1, 10, 60, 200];
278
279 for spacing in spacings {
280 let max_tick = PoolTick::get_max_tick(spacing);
281 let min_tick = PoolTick::get_min_tick(spacing);
282
283 assert_eq!(max_tick, -min_tick, "Asymmetry for spacing {}", spacing);
285
286 assert_eq!(max_tick % spacing, 0);
288 assert_eq!(min_tick % spacing, 0);
289
290 assert!(max_tick <= PoolTick::MAX_TICK);
292 assert!(min_tick >= PoolTick::MIN_TICK);
293 }
294 }
295}