use std::cmp::Ord;
use alloy_primitives::U256;
use crate::defi::tick_map::liquidity_math::liquidity_math_add;
#[derive(Debug, Clone)]
pub struct CrossedTick {
pub tick: i32,
pub zero_for_one: bool,
pub fee_growth_0: U256,
pub fee_growth_1: U256,
}
impl CrossedTick {
pub fn new(tick: i32, zero_for_one: bool, fee_growth_0: U256, fee_growth_1: U256) -> Self {
Self {
tick,
zero_for_one,
fee_growth_0,
fee_growth_1,
}
}
}
#[cfg_attr(
feature = "python",
pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
)]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct PoolTick {
pub value: i32,
pub liquidity_gross: u128,
pub liquidity_net: i128,
pub fee_growth_outside_0: U256,
pub fee_growth_outside_1: U256,
pub initialized: bool,
pub last_updated_block: u64,
pub updates_count: usize,
}
impl PoolTick {
pub const MIN_TICK: i32 = -887272;
pub const MAX_TICK: i32 = -Self::MIN_TICK;
#[must_use]
pub fn new(
value: i32,
liquidity_gross: u128,
liquidity_net: i128,
fee_growth_outside_0: U256,
fee_growth_outside_1: U256,
initialized: bool,
last_updated_block: u64,
) -> Self {
Self {
value,
liquidity_gross,
liquidity_net,
fee_growth_outside_0,
fee_growth_outside_1,
initialized,
last_updated_block,
updates_count: 0,
}
}
pub fn from_tick(tick: i32) -> Self {
Self::new(tick, 0, 0, U256::ZERO, U256::ZERO, false, 0)
}
pub fn update_liquidity(&mut self, liquidity_delta: i128, upper: bool) -> u128 {
let liquidity_gross_before = self.liquidity_gross;
self.liquidity_gross = liquidity_math_add(self.liquidity_gross, liquidity_delta);
if upper {
self.liquidity_net -= liquidity_delta;
} else {
self.liquidity_net += liquidity_delta;
}
self.updates_count += 1;
liquidity_gross_before
}
pub fn clear(&mut self) {
self.liquidity_gross = 0;
self.liquidity_net = 0;
self.fee_growth_outside_0 = U256::ZERO;
self.fee_growth_outside_1 = U256::ZERO;
self.initialized = false;
}
#[must_use]
pub fn is_active(&self) -> bool {
self.initialized && self.liquidity_gross > 0
}
pub fn update_fee_growth(&mut self, fee_growth_global_0: U256, fee_growth_global_1: U256) {
self.fee_growth_outside_0 = fee_growth_global_0 - self.fee_growth_outside_0;
self.fee_growth_outside_1 = fee_growth_global_1 - self.fee_growth_outside_1;
}
pub fn get_max_tick(tick_spacing: i32) -> i32 {
(Self::MAX_TICK / tick_spacing) * tick_spacing
}
pub fn get_min_tick(tick_spacing: i32) -> i32 {
(Self::MIN_TICK / tick_spacing) * tick_spacing
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_update_liquidity_add_remove() {
let mut tick = PoolTick::from_tick(100);
tick.initialized = true;
tick.update_liquidity(1000, false); assert_eq!(tick.liquidity_gross, 1000);
assert_eq!(tick.liquidity_net, 1000); assert!(tick.is_active());
tick.update_liquidity(500, false);
assert_eq!(tick.liquidity_gross, 1500);
assert_eq!(tick.liquidity_net, 1500);
assert!(tick.is_active());
tick.update_liquidity(-300, false);
assert_eq!(tick.liquidity_gross, 1200);
assert_eq!(tick.liquidity_net, 1200);
assert!(tick.is_active());
tick.update_liquidity(-1200, false);
assert_eq!(tick.liquidity_gross, 0);
assert_eq!(tick.liquidity_net, 0);
assert!(!tick.is_active()); }
#[rstest]
fn test_update_liquidity_upper_tick() {
let mut tick = PoolTick::from_tick(200);
tick.initialized = true;
tick.update_liquidity(1000, true);
assert_eq!(tick.liquidity_gross, 1000);
assert_eq!(tick.liquidity_net, -1000); assert!(tick.is_active());
tick.update_liquidity(-500, true);
assert_eq!(tick.liquidity_gross, 500);
assert_eq!(tick.liquidity_net, -500); assert!(tick.is_active());
}
#[rstest]
fn test_get_max_tick() {
let max_tick_1 = PoolTick::get_max_tick(1);
assert_eq!(max_tick_1, 887272);
let max_tick_10 = PoolTick::get_max_tick(10);
assert_eq!(max_tick_10, 887270); assert_eq!(max_tick_10 % 10, 0);
assert!(max_tick_10 <= PoolTick::MAX_TICK);
let max_tick_60 = PoolTick::get_max_tick(60);
assert_eq!(max_tick_60, 887220); assert_eq!(max_tick_60 % 60, 0);
assert!(max_tick_60 <= PoolTick::MAX_TICK);
let max_tick_200 = PoolTick::get_max_tick(200);
assert_eq!(max_tick_200, 887200); assert_eq!(max_tick_200 % 200, 0);
assert!(max_tick_200 <= PoolTick::MAX_TICK);
}
#[rstest]
fn test_get_min_tick() {
let min_tick_1 = PoolTick::get_min_tick(1);
assert_eq!(min_tick_1, -887272);
let min_tick_10 = PoolTick::get_min_tick(10);
assert_eq!(min_tick_10, -887270); assert_eq!(min_tick_10 % 10, 0);
assert!(min_tick_10 >= PoolTick::MIN_TICK);
let min_tick_60 = PoolTick::get_min_tick(60);
assert_eq!(min_tick_60, -887220); assert_eq!(min_tick_60 % 60, 0);
assert!(min_tick_60 >= PoolTick::MIN_TICK);
let min_tick_200 = PoolTick::get_min_tick(200);
assert_eq!(min_tick_200, -887200); assert_eq!(min_tick_200 % 200, 0);
assert!(min_tick_200 >= PoolTick::MIN_TICK);
}
#[rstest]
fn test_tick_spacing_symmetry() {
let spacings = [1, 10, 60, 200];
for spacing in spacings {
let max_tick = PoolTick::get_max_tick(spacing);
let min_tick = PoolTick::get_min_tick(spacing);
assert_eq!(max_tick, -min_tick, "Asymmetry for spacing {spacing}");
assert_eq!(max_tick % spacing, 0);
assert_eq!(min_tick % spacing, 0);
assert!(max_tick <= PoolTick::MAX_TICK);
assert!(min_tick >= PoolTick::MIN_TICK);
}
}
}