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 #[must_use]
42 pub fn new(tick: i32, zero_for_one: bool, fee_growth_0: U256, fee_growth_1: U256) -> Self {
43 Self {
44 tick,
45 zero_for_one,
46 fee_growth_0,
47 fee_growth_1,
48 }
49 }
50}
51
52#[cfg_attr(
54 feature = "python",
55 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
56)]
57#[cfg_attr(
58 feature = "python",
59 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
60)]
61#[derive(
62 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
63)]
64pub struct PoolTick {
65 pub value: i32,
67 pub liquidity_gross: u128,
69 pub liquidity_net: i128,
71 pub fee_growth_outside_0: U256,
73 pub fee_growth_outside_1: U256,
75 pub initialized: bool,
77 pub last_updated_block: u64,
79 pub updates_count: usize,
81}
82
83impl PoolTick {
84 pub const MIN_TICK: i32 = -887_272;
86 pub const MAX_TICK: i32 = -Self::MIN_TICK;
88
89 #[must_use]
91 pub fn new(
92 value: i32,
93 liquidity_gross: u128,
94 liquidity_net: i128,
95 fee_growth_outside_0: U256,
96 fee_growth_outside_1: U256,
97 initialized: bool,
98 last_updated_block: u64,
99 ) -> Self {
100 Self {
101 value,
102 liquidity_gross,
103 liquidity_net,
104 fee_growth_outside_0,
105 fee_growth_outside_1,
106 initialized,
107 last_updated_block,
108 updates_count: 0,
109 }
110 }
111
112 #[must_use]
114 pub fn from_tick(tick: i32) -> Self {
115 Self::new(tick, 0, 0, U256::ZERO, U256::ZERO, false, 0)
116 }
117
118 pub fn update_liquidity(&mut self, liquidity_delta: i128, upper: bool) -> u128 {
120 let liquidity_gross_before = self.liquidity_gross;
121 self.liquidity_gross = liquidity_math_add(self.liquidity_gross, liquidity_delta);
122
123 if upper {
125 self.liquidity_net -= liquidity_delta;
126 } else {
127 self.liquidity_net += liquidity_delta;
128 }
129 self.updates_count += 1;
130
131 liquidity_gross_before
132 }
133
134 pub fn clear(&mut self) {
136 self.liquidity_gross = 0;
137 self.liquidity_net = 0;
138 self.fee_growth_outside_0 = U256::ZERO;
139 self.fee_growth_outside_1 = U256::ZERO;
140 self.initialized = false;
141 }
142
143 #[must_use]
145 pub fn is_active(&self) -> bool {
146 self.initialized && self.liquidity_gross > 0
147 }
148
149 pub fn update_fee_growth(&mut self, fee_growth_global_0: U256, fee_growth_global_1: U256) {
151 self.fee_growth_outside_0 = fee_growth_global_0 - self.fee_growth_outside_0;
152 self.fee_growth_outside_1 = fee_growth_global_1 - self.fee_growth_outside_1;
153 }
154
155 #[must_use]
157 pub fn get_max_tick(tick_spacing: i32) -> i32 {
158 (Self::MAX_TICK / tick_spacing) * tick_spacing
160 }
161
162 #[must_use]
164 pub fn get_min_tick(tick_spacing: i32) -> i32 {
165 (Self::MIN_TICK / tick_spacing) * tick_spacing
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use rstest::rstest;
173
174 use super::*;
175
176 #[rstest]
177 fn test_update_liquidity_add_remove() {
178 let mut tick = PoolTick::from_tick(100);
179 tick.initialized = true;
180
181 tick.update_liquidity(1000, false); assert_eq!(tick.liquidity_gross, 1000);
184 assert_eq!(tick.liquidity_net, 1000); assert!(tick.is_active());
186
187 tick.update_liquidity(500, false);
189 assert_eq!(tick.liquidity_gross, 1500);
190 assert_eq!(tick.liquidity_net, 1500);
191 assert!(tick.is_active());
192
193 tick.update_liquidity(-300, false);
195 assert_eq!(tick.liquidity_gross, 1200);
196 assert_eq!(tick.liquidity_net, 1200);
197 assert!(tick.is_active());
198
199 tick.update_liquidity(-1200, false);
201 assert_eq!(tick.liquidity_gross, 0);
202 assert_eq!(tick.liquidity_net, 0);
203 assert!(!tick.is_active()); }
205
206 #[rstest]
207 fn test_update_liquidity_upper_tick() {
208 let mut tick = PoolTick::from_tick(200);
209 tick.initialized = true;
210
211 tick.update_liquidity(1000, true);
213 assert_eq!(tick.liquidity_gross, 1000);
214 assert_eq!(tick.liquidity_net, -1000); assert!(tick.is_active());
216
217 tick.update_liquidity(-500, true);
219 assert_eq!(tick.liquidity_gross, 500);
220 assert_eq!(tick.liquidity_net, -500); assert!(tick.is_active());
222 }
223
224 #[rstest]
225 fn test_get_max_tick() {
226 let max_tick_1 = PoolTick::get_max_tick(1);
230 assert_eq!(max_tick_1, 887_272); let max_tick_10 = PoolTick::get_max_tick(10);
234 assert_eq!(max_tick_10, 887_270); assert_eq!(max_tick_10 % 10, 0);
236 assert!(max_tick_10 <= PoolTick::MAX_TICK);
237
238 let max_tick_60 = PoolTick::get_max_tick(60);
240 assert_eq!(max_tick_60, 887_220); assert_eq!(max_tick_60 % 60, 0);
242 assert!(max_tick_60 <= PoolTick::MAX_TICK);
243
244 let max_tick_200 = PoolTick::get_max_tick(200);
246 assert_eq!(max_tick_200, 887_200); assert_eq!(max_tick_200 % 200, 0);
248 assert!(max_tick_200 <= PoolTick::MAX_TICK);
249 }
250
251 #[rstest]
252 fn test_get_min_tick() {
253 let min_tick_1 = PoolTick::get_min_tick(1);
257 assert_eq!(min_tick_1, -887_272); let min_tick_10 = PoolTick::get_min_tick(10);
261 assert_eq!(min_tick_10, -887_270); assert_eq!(min_tick_10 % 10, 0);
263 assert!(min_tick_10 >= PoolTick::MIN_TICK);
264
265 let min_tick_60 = PoolTick::get_min_tick(60);
267 assert_eq!(min_tick_60, -887_220); assert_eq!(min_tick_60 % 60, 0);
269 assert!(min_tick_60 >= PoolTick::MIN_TICK);
270
271 let min_tick_200 = PoolTick::get_min_tick(200);
273 assert_eq!(min_tick_200, -887_200); assert_eq!(min_tick_200 % 200, 0);
275 assert!(min_tick_200 >= PoolTick::MIN_TICK);
276 }
277
278 #[rstest]
279 fn test_tick_spacing_symmetry() {
280 let spacings = [1, 10, 60, 200];
282
283 for spacing in spacings {
284 let max_tick = PoolTick::get_max_tick(spacing);
285 let min_tick = PoolTick::get_min_tick(spacing);
286
287 assert_eq!(max_tick, -min_tick, "Asymmetry for spacing {spacing}");
289
290 assert_eq!(max_tick % spacing, 0);
292 assert_eq!(min_tick % spacing, 0);
293
294 assert!(max_tick <= PoolTick::MAX_TICK);
296 assert!(min_tick >= PoolTick::MIN_TICK);
297 }
298 }
299}