Skip to content

Commit

Permalink
feat(tick): Refactor price tick calculations and implement tick range…
Browse files Browse the repository at this point in the history
… function (#42)

The logic in 'price_tick_conversions.rs' was refactored to enhance efficiency by using a single instance of BigDecimal::from(1), reducing code redundancy. Added a new function 'tick_range_from_width_and_ratio' to determine the tick range based on specified position ratio and range width, providing a more straightforward approach to tick range calculations.
  • Loading branch information
shuhuiluo authored Mar 4, 2024
1 parent 5730e4c commit 866a441
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "uniswap-v3-sdk"
version = "0.26.0"
version = "0.26.1"
edition = "2021"
authors = ["Shuhui Luo <twitter.com/aureliano_law>"]
description = "Uniswap V3 SDK for Rust"
Expand Down
80 changes: 75 additions & 5 deletions src/extensions/price_tick_conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@ pub fn price_to_closest_tick_safe(price: &Price<Token, Token>) -> Result<i32> {
/// let token0 = token!(1, "2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", 8, "WBTC");
/// let token1 = token!(1, "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 18, "WETH");
/// let fee = FeeAmount::MEDIUM;
/// let min_price = Price::new(token0.clone(), token1.clone(), MIN_PRICE.denominator(), 1);
/// let min_price = Price::new(
/// token0.clone(),
/// token1.clone(),
/// MIN_PRICE.denominator(),
/// MIN_PRICE.numerator(),
/// );
/// let max_price = Price::new(
/// token0.clone(),
/// token1.clone(),
Expand Down Expand Up @@ -260,25 +265,26 @@ pub fn token0_ratio_to_price(
tick_lower: i32,
tick_upper: i32,
) -> Result<BigDecimal> {
let one = BigDecimal::from(1);
if tick_upper <= tick_lower {
bail!("Invalid tick range: tickUpper must be greater than tickLower");
}
if token0_ratio < BigDecimal::zero() || token0_ratio > BigDecimal::from(1) {
if token0_ratio < BigDecimal::zero() || token0_ratio > one {
bail!("Invalid token0ValueProportion: must be a value between 0 and 1, inclusive");
}
if token0_ratio.is_zero() {
return tick_to_big_price(tick_upper);
}
if token0_ratio == BigDecimal::from(1) {
if token0_ratio == one {
return tick_to_big_price(tick_lower);
}
let sqrt_ratio_lower_x96 = get_sqrt_ratio_at_tick(tick_lower)?;
let sqrt_ratio_upper_x96 = get_sqrt_ratio_at_tick(tick_upper)?;
let l = u256_to_big_decimal(sqrt_ratio_lower_x96) / u256_to_big_decimal(Q96);
let u = u256_to_big_decimal(sqrt_ratio_upper_x96) / u256_to_big_decimal(Q96);
let r = token0_ratio;
let a = &r - BigDecimal::from(1);
let b = &u * (BigDecimal::from(1) - BigDecimal::from(2) * &r);
let a = &r - one.clone();
let b = &u * (one - BigDecimal::from(2) * &r);
let c = r * l * u;
let numerator = &b + (b.square() - BigDecimal::from(4) * &a * c).sqrt().unwrap();
let denominator = BigDecimal::from(-2) * a;
Expand Down Expand Up @@ -334,6 +340,70 @@ pub fn token0_price_to_ratio(
}
}

/// Returns the tick range for a position ratio and range width.
///
/// ## Arguments
///
/// * `width`: The width of the range.
/// * `tick_current`: The current tick of the pool.
/// * `token0_ratio`: The proportion of the position value that is held in token0, as a
/// [`BigDecimal`] number between 0 and 1, inclusive.
///
/// ## Returns
///
/// The tick range as a tuple of `(tick_lower, tick_upper)`.
///
/// ## Examples
///
/// ```
/// use bigdecimal::BigDecimal;
/// use uniswap_v3_sdk::prelude::*;
///
/// let tick_current = 200000;
/// let price = tick_to_big_price(tick_current).unwrap();
/// let token0_ratio = "0.3".parse::<BigDecimal>().unwrap();
/// let width = 1000;
/// let (tick_lower, tick_upper) =
/// tick_range_from_width_and_ratio(width, tick_current, token0_ratio.clone()).unwrap();
/// assert_eq!(tick_upper - tick_lower, width);
/// let price_lower_sqrt = tick_to_big_price(tick_lower).unwrap().sqrt().unwrap();
/// let price_upper_sqrt = tick_to_big_price(tick_upper).unwrap().sqrt().unwrap();
/// let one = BigDecimal::from(1);
/// let amount0 = one.clone() / price.sqrt().unwrap() - one / price_upper_sqrt;
/// let amount1 = price.sqrt().unwrap() - price_lower_sqrt;
/// let value0 = amount0 * &price;
/// let ratio = &value0 / (&value0 + amount1);
/// assert!((ratio - token0_ratio).abs() < "0.001".parse::<BigDecimal>().unwrap());
/// ```
pub fn tick_range_from_width_and_ratio(
width: i32,
tick_current: i32,
token0_ratio: BigDecimal,
) -> Result<(i32, i32)> {
let one = BigDecimal::from(1);
let two = BigDecimal::from(2);
if token0_ratio < BigDecimal::zero() || token0_ratio > one {
bail!("Invalid token0ValueProportion: must be a value between 0 and 1, inclusive");
}
let (tick_lower, tick_upper) = if token0_ratio.is_zero() {
(tick_current - width, tick_current)
} else if token0_ratio == one {
(tick_current, tick_current + width)
} else {
let price = tick_to_big_price(tick_current)?;
let a = token0_ratio;
let b = (one.clone() - &a * two.clone()) * price.sqrt().unwrap();
let c = &price * (&a - one) / tick_to_big_price(width)?.sqrt().unwrap();
let price_lower_sqrt =
((&b * &b - &a * &c * BigDecimal::from(4)).sqrt().unwrap() - &b) / (&a * two);
let sqrt_ratio_lower_x96 = price_lower_sqrt * u256_to_big_decimal(Q96);
let tick_lower =
get_tick_at_sqrt_ratio(big_int_to_u256(sqrt_ratio_lower_x96.to_bigint().unwrap()))?;
(tick_lower, tick_lower + width)
};
Ok((tick_lower, tick_upper))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit 866a441

Please sign in to comment.