Source code for uniswap.v3.math

"""
Notes
-----
Below implementation based on the great article of Atis Elsts.

.. [1] Atis Elsts "LIQUIDITY MATH IN UNISWAP V3",
    https://atiselsts.github.io/pdfs/uniswap-v3-liquidity-math.pdf, 2022


Implementation utilize description of liquidity based on book of
Ivan Kuznetsov and all https://github.com/Jeiwan/uniswapv3-book contributors.

.. [2] Source: https://uniswapv3book.com/docs/milestone_1/calculating-liquidity/


"""
from math import sqrt, log

Q32 = 2**32
Q96 = 2**96
Q128 = 2**128
MAX_UINT_128 = 2**128 - 1
MAX_UINT_256 = 2**256 - 1
MIN_TICK = -887272
MAX_TICK = -MIN_TICK


[docs]def from_sqrtPriceX96(sqrtPriceX96: int) -> float: """Return price by sqrtPriceX96""" return (sqrtPriceX96**2) / (2**192)
[docs]def to_sqrtPriceX96(price: float) -> int: """Return sqrtPriceX96 by the price""" return int(price**0.5 * 2**96)
[docs]def mul_shift(val, mul_by): return (val * mul_by) // Q128
[docs]def get_sqrt_ratio_at_tick(tick: int) -> int: """ Get sqrtRatioX96 from a tick. Original code: https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/TickMath.sol#L23 Parameters ---------- tick : int Value of the Tick Returns ------- int sqrtRatioX96 of a Price for a provided tick Notes ----- A Python equivalent don't give the same precision of the result. Its Python equivalent is:: def get_sqrt_ratio_at_tick(tick): return int(sqrt(1.0001**tick) * Q96) """ # XXX # https://github.com/Uniswap/v3-core/blob/main/contracts/interfaces/pool/IUniswapV3PoolState.sol#L10 # noqa valid = tick >= MIN_TICK and tick <= MAX_TICK if not valid: raise Exception("Invariant TICK") abs_tick = abs(tick) ratio = ( int(0xFFFCB933BD6FAD37AA2D162D1A594001) if (abs_tick & 0x1) != 0 else int(0x100000000000000000000000000000000) ) if (abs_tick & int(0x2)) != 0: ratio = mul_shift(ratio, int(0xFFF97272373D413259A46990580E213A)) if (abs_tick & int(0x4)) != 0: ratio = mul_shift(ratio, int(0xFFF2E50F5F656932EF12357CF3C7FDCC)) if (abs_tick & int(0x8)) != 0: ratio = mul_shift(ratio, int(0xFFE5CACA7E10E4E61C3624EAA0941CD0)) if (abs_tick & int(0x10)) != 0: ratio = mul_shift(ratio, int(0xFFCB9843D60F6159C9DB58835C926644)) if (abs_tick & int(0x20)) != 0: ratio = mul_shift(ratio, int(0xFF973B41FA98C081472E6896DFB254C0)) if (abs_tick & int(0x40)) != 0: ratio = mul_shift(ratio, int(0xFF2EA16466C96A3843EC78B326B52861)) if (abs_tick & int(0x80)) != 0: ratio = mul_shift(ratio, int(0xFE5DEE046A99A2A811C461F1969C3053)) if (abs_tick & int(0x100)) != 0: ratio = mul_shift(ratio, int(0xFCBE86C7900A88AEDCFFC83B479AA3A4)) if (abs_tick & int(0x200)) != 0: ratio = mul_shift(ratio, int(0xF987A7253AC413176F2B074CF7815E54)) if (abs_tick & int(0x400)) != 0: ratio = mul_shift(ratio, int(0xF3392B0822B70005940C7A398E4B70F3)) if (abs_tick & int(0x800)) != 0: ratio = mul_shift(ratio, int(0xE7159475A2C29B7443B29C7FA6E889D9)) if (abs_tick & int(0x1000)) != 0: ratio = mul_shift(ratio, int(0xD097F3BDFD2022B8845AD8F792AA5825)) if (abs_tick & int(0x2000)) != 0: ratio = mul_shift(ratio, int(0xA9F746462D870FDF8A65DC1F90E061E5)) if (abs_tick & int(0x4000)) != 0: ratio = mul_shift(ratio, int(0x70D869A156D2A1B890BB3DF62BAF32F7)) if (abs_tick & int(0x8000)) != 0: ratio = mul_shift(ratio, int(0x31BE135F97D08FD981231505542FCFA6)) if (abs_tick & int(0x10000)) != 0: ratio = mul_shift(ratio, int(0x9AA508B5B7A84E1C677DE54F3E99BC9)) if (abs_tick & int(0x20000)) != 0: ratio = mul_shift(ratio, int(0x5D6AF8DEDB81196699C329225EE604)) if (abs_tick & int(0x40000)) != 0: ratio = mul_shift(ratio, int(0x2216E584F5FA1EA926041BEDFE98)) if (abs_tick & int(0x80000)) != 0: ratio = mul_shift(ratio, int(0x48A170391F7DC42444E8FA2)) if tick > 0: ratio = MAX_UINT_256 // ratio if ratio % Q32 > 0: return (ratio // Q32) + 1 else: return ratio // Q32
[docs]def get_amount0_delta(sqrt_ratio_ax_96, sqrt_ratio_bx_96, liquidity): if sqrt_ratio_ax_96 > sqrt_ratio_bx_96: sqrt_ratio_ax_96, sqrt_ratio_bx_96 = sqrt_ratio_bx_96, sqrt_ratio_ax_96 numerator1 = liquidity << 96 numerator2 = sqrt_ratio_bx_96 - sqrt_ratio_ax_96 return ((numerator1 * numerator2) // sqrt_ratio_bx_96) // sqrt_ratio_ax_96
# TODO # replace with own functions
[docs]def get_amount0( tick_current: int, sqrt_price_x_96: int, tick_lower: int, tick_upper: int, liquidity: int, ) -> int: if tick_current < tick_lower: tick0ratio = get_sqrt_ratio_at_tick(tick_lower) tick1ratio = get_sqrt_ratio_at_tick(tick_upper) elif tick_current < tick_upper: tick0ratio = sqrt_price_x_96 tick1ratio = get_sqrt_ratio_at_tick(tick_upper) else: return 0 return get_amount0_delta(tick0ratio, tick1ratio, liquidity)
[docs]def get_amount1_delta(sqrt_ratio_ax_96, sqrt_ratio_bx_96, liquidity): if sqrt_ratio_ax_96 > sqrt_ratio_bx_96: sqrt_ratio_ax_96, sqrt_ratio_bx_96 = sqrt_ratio_bx_96, sqrt_ratio_ax_96 ratio_diff = sqrt_ratio_bx_96 - sqrt_ratio_ax_96 return (liquidity * ratio_diff) // Q96
[docs]def get_amount1( tick_current: int, sqrt_price_x_96: int, tick_lower: int, tick_upper: int, liquidity: int, ) -> int: if tick_current < tick_lower: return 0 elif tick_current < tick_upper: tick0ratio = get_sqrt_ratio_at_tick(tick_lower) tick1ratio = sqrt_price_x_96 else: tick0ratio = get_sqrt_ratio_at_tick(tick_lower) tick1ratio = get_sqrt_ratio_at_tick(tick_upper) return get_amount1_delta(tick0ratio, tick1ratio, liquidity)
[docs]def get_liquidity0(amount: int, pa: int, pb: int) -> int: """ Get a liquidity by sqrt price range and an amount of the first asset. Implementation based on the article [2]_ Notes ----- """ if pa > pb: pa, pb = pb, pa return int((amount * (pa * pb) // Q96) // (pb - pa))
[docs]def get_liquidity1(amount: int, pa: int, pb: int) -> int: """ Get a liquidity by a range of sqrt prices and an amount of the second asset. Implementation based on the article [2]_ """ if pa > pb: pa, pb = pb, pa return int(amount * Q96 // (pb - pa))
[docs]def get_liquidity( sqrt_price_x_96: int, sqrt_price_x_96_tick_lower: int, sqrt_price_x_96_tick_upper: int, amount0: int, amount1: int, ) -> int: """ Get liquidity of the position by a sqrt of a current price, a lower and upper and amounts of assets. Implementation based on the article [2]_ Parameters ---------- sqrt_price_x_96 : int current sqrtPriceX96 sqrt_price_x_96_tick_lower : int sqrtPriceX96 at tick_lower sqrt_price_x_96_tick_upper : int sqrtPriceX96 at tick_upper amount0: int Amount of the first asset amount1: int Amount of the second asset Returns ------- float Liquidity """ pa = sqrt_price_x_96_tick_lower # get_sqrt_ratio_at_tick(tick_lower) pb = sqrt_price_x_96_tick_upper # get_sqrt_ratio_at_tick(tick_upper) pc = sqrt_price_x_96 liq0 = get_liquidity0(amount0, pc, pb) liq1 = get_liquidity1(amount1, pc, pa) return int(min(liq0, liq1))
[docs]def get_amount0_from_price_range(p: float, pa: float, pb: float, amount1): """ Get amount1 from price range and amount0. Formula (1) [1]_ Parameters ---------- p : float current Price pa : float price of a lowerTick (i.e.: `from_sqrtPriceX96(get_sqrt_ratio_at_tick(lower_tick))`) pb : float price of a a upperTick (i.e.: `from_sqrtPriceX96(get_sqrt_ratio_at_tick(upper_tick))`) amount0: float Amount of the first asset (not in Wei) Returns ------- float Amount of the first asset """ p = max(min(p, pb), pa) liquidity = amount1 / ((sqrt(p) - sqrt(pa))) # Ref. [1] (8) amount0 = liquidity * ((sqrt(pb) - sqrt(p)) / (sqrt(p) * sqrt(pb))) # (11) return amount0
[docs]def get_amount1_from_price_range(p: float, pa: float, pb: float, amount0: float): """ Get amount1 from price range and amount1. 3.2.1 Example 1: Amount of assets from a range [1]_ Parameters ---------- p : float current Price pa : float price of a lowerTick (i.e.: `from_sqrtPriceX96(get_sqrt_ratio_at_tick(lower_tick))`) pb : float price of a a upperTick (i.e.: `from_sqrtPriceX96(get_sqrt_ratio_at_tick(upper_tick))`) amount0: float Amount of the first asset (not in Wei) Returns ------- float Amount of the second asset """ p = max(min(p, pb), pa) liquidity_amount0 = ( # Ref. [1] (5) amount0 * (sqrt(p) * sqrt(pb)) / (sqrt(pb) - sqrt(p)) ) amount1 = liquidity_amount0 * (sqrt(p) - sqrt(pa)) return amount1
[docs]def get_amount0_from_tick_range(p: int, pa: int, pb: int, amount1: int) -> int: """ Get amount1 from a tick range and amount0. Formula (1) [1]_ Parameters ---------- p : int sqrtRatioX96 of current Price pa : int sqrtRatioX96 from a lowerTick (sqrtRatioAX96) pb : int sqrtRatioX96 from a upperTick (sqrtRatioAX96) amount1: int Amount of the second asset in Wei Returns ------- int Amount of the first asset in Wei """ p = max(min(p, pb), pa) # liquidity_amount1 = (amount1 * Q96) // (p - pa) # Ref. [1] (8) liquidity_amount1 = get_liquidity1(amount1, pa, p) amount0 = (liquidity_amount1 * Q96) * (pb - p) // (p * pb) # (11) return amount0
[docs]def get_amount1_from_tick_range(p: int, pa: int, pb: int, amount0: int) -> int: """ Get amount1 from tick range and amount1. 3.2.1 Example 1: Amount of assets from a range [1]_ Parameters ---------- p : int sqrtRatioX96 of current Price pa : int sqrtRatioX96 from a lowerTick (sqrtRatioAX96) pb : int sqrtRatioX96 from a upperTick (sqrtRatioAX96) amount0: int Amount of the first asset in Wei Returns ------- int Amount of the second asset in Wei """ p = max(min(p, pb), pa) # liquidity_amount0 = (amount0 * (p * pb) / Q96) / (pb - p) # Ref. [1] (5) liquidity_amount0 = get_liquidity0(amount0, p, pb) amount1 = liquidity_amount0 * (p - pa) / Q96 return int(amount1)
[docs]def get_tick_from_price(price: float) -> int: """Return tick by provided price Parameters ---------- price : float price Returns ------- int Tick """ tick = log(price, 1.0001) return int(round(tick, 0))