Source code for nqs_sdk.bindings.protocols.uniswap_v3.uniswap_utils
import math
from decimal import Decimal
from typing import Tuple
# Maximum tick value for Uniswap V3 pools
MAX_TICK = 887272
[docs]
class InvalidPriceError(ValueError):
"""Raised when price value is invalid (not strictly positive and finite)."""
pass
[docs]
def price_to_tick(price: Decimal, decimals0: int, decimals1: int, tick_spacing: int = 1, lower: bool = True) -> int:
"""
Convert a price to a tick value for Uniswap V3.
Args:
price: The price to convert (must be strictly positive and finite)
decimals0: Number of decimals for token0
decimals1: Number of decimals for token1
tick_spacing: Spacing between valid ticks (default: 1)
lower: Whether to round down (True) or up (False) when between ticks
Returns:
The corresponding tick value
Raises:
InvalidPriceError: When the price is not strictly positive and finite
(e.g., negative, zero, infinity, or NaN)
"""
# Validate that price is strictly positive and finite
if price <= 0 or not price.is_finite():
raise InvalidPriceError(f"Price must be strictly positive and finite, got: {price}")
# Compute tick as log base sqrt(1.0001) of sqrt_price
ic = price.scaleb(decimals1 - decimals0).sqrt().ln() / Decimal("1.0001").sqrt().ln()
if lower:
tick = max(math.floor(round(ic) / tick_spacing) * tick_spacing, -MAX_TICK)
else:
tick = min(math.ceil(round(ic) / tick_spacing) * tick_spacing, MAX_TICK)
return tick
[docs]
def tick_to_price(tick: int, decimals0: int, decimals1: int) -> Decimal:
return (Decimal(1.0001) ** tick).scaleb(-(decimals1 - decimals0))
[docs]
def price_to_sqrtp(p: Decimal) -> int:
return int(p.sqrt() * Decimal("2") ** 96)
[docs]
def calculate_max_amounts(
price_lower: Decimal, price: Decimal, price_upper: Decimal, amount0: Decimal, amount1: Decimal
) -> Decimal:
"""
Calculate the maximum liquidity that can be minted for a Uniswap V3 position.
This function determines the maximum amount of liquidity that can be created
from the available token amounts (amount0 and amount1) for a concentrated
liquidity position with specified price bounds.
Args:
price_lower (Decimal): Lower price bound of the position (must be positive)
price (Decimal): Current price of the pool (must be positive)
price_upper (Decimal): Upper price bound of the position (must be > price_lower)
amount0 (Decimal): Available amount of token0 (must be >= 0)
amount1 (Decimal): Available amount of token1 (must be >= 0)
Returns:
Decimal: Maximum liquidity that can be minted with the given token amounts
Raises:
AssertionError: If any of the following conditions are not met:
- amount0 >= 0
- amount1 >= 0
- price_lower < price_upper
- calculated liquidity >= 0
Notes:
The calculation depends on where the current price falls relative to the position bounds:
- If price <= price_lower: Only token0 is needed, liquidity is limited by amount0
- If price_lower < price < price_upper: Both tokens are needed, liquidity is limited
by whichever token provides less liquidity
- If price >= price_upper: Only token1 is needed, liquidity is limited by amount1
This follows the standard Uniswap V3 concentrated liquidity formulas:
- L = Δx / (1/√P - 1/√P_upper) for token0
- L = Δy / (√P - √P_lower) for token1
Example:
>>> from decimal import Decimal
>>> price_lower = Decimal('1.5')
>>> price = Decimal('2.0')
>>> price_upper = Decimal('2.5')
>>> amount0 = Decimal('100')
>>> amount1 = Decimal('200')
>>> liquidity = calculate_max_amounts(price_lower, price, price_upper, amount0, amount1)
"""
assert amount0 >= 0
assert amount1 >= 0
sqrt_price_lower = price_lower.sqrt()
sqrt_price = price.sqrt()
sqrt_price_upper = price_upper.sqrt()
assert sqrt_price_lower < sqrt_price_upper
if sqrt_price <= sqrt_price_lower:
liquidity = amount0 / (1 / sqrt_price_lower - 1 / sqrt_price_upper)
elif sqrt_price < sqrt_price_upper:
liquidity_0 = amount0 / (1 / sqrt_price - 1 / sqrt_price_upper)
liquidity_1 = amount1 / (sqrt_price - sqrt_price_lower)
liquidity = min(liquidity_0, liquidity_1)
else:
liquidity = amount1 / (sqrt_price_upper - sqrt_price_lower)
assert liquidity >= 0
return liquidity
[docs]
def token_amounts_from_liquidity(
price_lower: Decimal, price: Decimal, price_upper: Decimal, liquidity_amount: Decimal
) -> Tuple[Decimal, Decimal]:
assert liquidity_amount >= 0
sqrt_price_lower = price_lower.sqrt()
sqrt_price = price.sqrt()
sqrt_price_upper = price_upper.sqrt()
token0_amount = Decimal(0)
token1_amount = Decimal(0)
if sqrt_price <= sqrt_price_lower:
token0_amount = liquidity_amount * (1 / sqrt_price_lower - 1 / sqrt_price_upper)
elif sqrt_price >= sqrt_price_upper:
token1_amount = liquidity_amount * (sqrt_price_upper - sqrt_price_lower)
else:
token0_amount = liquidity_amount * (1 / sqrt_price - 1 / sqrt_price_upper)
token1_amount = liquidity_amount * (sqrt_price - sqrt_price_lower)
return token0_amount, token1_amount
[docs]
def calculate_optimal_rebalancing(
price_lower: Decimal, price: Decimal, price_upper: Decimal, amount0: Decimal, amount1: Decimal
) -> tuple[Decimal, Decimal]:
sqrt_price_lower = price_lower.sqrt()
sqrt_price = price.sqrt()
sqrt_price_upper = price_upper.sqrt()
x_unit = (sqrt_price_upper - sqrt_price) / (sqrt_price * sqrt_price_upper)
y_unit = sqrt_price - sqrt_price_lower
v_wallet = amount0 * price + amount1
v_unit = x_unit * price + y_unit
n_units = v_wallet / v_unit
x_pos = n_units * x_unit
y_pos = n_units * y_unit
return x_pos, y_pos
[docs]
def get_tick_spacing(fee_tier: Decimal) -> int:
"""
Get the tick spacing for a given fee tier using string formatting with controlled precision.
"""
# cf https://support.uniswap.org/hc/en-us/articles/20904283758349-What-are-fee-tiers
fee_to_tick_spacing = {
Decimal("0.01"): 1, # 0.01%
Decimal("0.05"): 10, # 0.05%
Decimal("0.30"): 60, # 0.3%
Decimal("1.00"): 200, # 1%
}
if fee_tier in fee_to_tick_spacing:
return fee_to_tick_spacing[fee_tier]
valid_fees = ", ".join(str(key) for key in fee_to_tick_spacing.keys())
raise ValueError(f"Unrecognized fee tier: {fee_tier}. Valid fee tiers are: [{valid_fees}]")