跳转到主要内容
本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →
本页整合了 CLMM 背后的推导过程。如需了解链上实现,请查看 products/clmm/math(引用本页)和 products/clmm/ticks-and-positions(说明跳价格子的设计)。

为什么是 sqrt-price,而不是 price

Uniswap v3 系列的 CLMM 将价格表示为其平方根,以定点 Q64.64 形式存储:
有三个原因:
  1. 线性流动性数学。 价格范围内 token0 或 token1 的金额恰好是 sqrt_price 的线性函数,而不是 price 的线性函数。存储 sqrt_price 让交换步骤可以直接计算这些线性公式,无需计算平方根。
  2. 溢出控制。 sqrt_price · L 在所有合理参数下都适应 u256price · L 可能更早溢出。
  3. 跳价格数学统一。 由于跳价格定义为 1.0001^i,所以 sqrt(price) = 1.00005^i 也是 1.00005 梯级的精确幂。每次跳价格穿越在 sqrt_price_x64 空间中转化为一个小的乘法。
价格和 sqrt-price 是一一对应的;转换公式为 price = (sqrt_price_x64 / 2^64)^2

跳价格格子

价格被离散化到一个网格上:
tick_i 是一个 i32。活跃范围是 [MIN_TICK, MAX_TICK] = [−443636, 443636],对应的价格范围约为 [2^−128, 2^128]。每个池的 tick_spacing 由其费用等级设定:紧密交易对使用较小间距(例如稳定币 0.01% 等级使用间距 1),波动性交易对使用较大间距(0.25% 等级使用 60,1% 等级使用 120)。 头寸必须使其 tick_lowertick_uppertick_spacing 对齐。池中的活跃跳价格(那些在该处开始或结束流动性的跳价格)是交换步骤关心的唯一跳价格。

流动性到金额的转换

对于流动性为 L 且价格范围为 [sqrt_lo, sqrt_hi] 的头寸(均为 sqrt_price 值): 推导:对 CPMM 不变式进行局部求导。在任何单个跳价格范围内,头寸的行为如同一个 CPMM,其虚拟准备金 (x_v, y_v) 被选择使得池当前的 (sqrt_p, L) 符合 L = sqrt(x_v · y_v)。从 sqrt_p 积分到范围边界得出上述金额。 逆向公式(用于为给定的 amount0amount1 创建头寸时):

单跳价格交换步骤

在单个跳价格范围内,池的行为类似 CPMM。给定当前 sqrt_p 和目标 sqrt_target

精确输入步骤

给定 Δin_remaining
0→1 交换会降低 sqrt_p(当我们出售 token0 时价格下跌)。1→0 交换会提高它。公式在 sqrt_psqrt_target 交换时是对称的。

精确输出步骤

结构相同,但求解 Δin 而不是输出。

多跳价格交换循环

交换会遍历跳价格直到输入耗尽或达到价格限制:
每个 single_step 使用池的当前 LL 在穿越初始化的跳价格时改变。跳价格间的流动性是恒定的,这正是单步数学具有闭式解的原因。 跳价格处的 liquidity_net 是在该跳价格处开始的头寸流动性的有符号总和减去在该处结束的头寸流动性。向上穿越会加上 liquidity_net;向下穿越会减去它。 当池在跳价格处有限价单打开时,穿越跳价格步骤也会机会性地消耗部分交换输入来填补这些订单(按队列在集群间)。匹配算法和可能在基本步骤之上应用的动态费用附加费在 products/clmm/math 中有文档说明;它们不改变上述闭式单步公式。

费用增长累加器

CLMM 按活跃流动性单位跟踪费用,每侧、全局和每跳价格:
在每个 single_step 上:
(另一侧的 fee_growth_global 在本步骤不移动,因为该侧没有 token 作为输入支付。) 穿越跳价格时,程序会翻转 fee_growth_outside
“Outside”(外侧)是相对于 tick_current 而言的。当 tick_current 在跳价格上方时,外侧意味着”下方”。当 tick_current 在下方时,外侧意味着”上方”。翻转会交换这种解释。

头寸的 fee_growth_inside

给定一个头寸 [tick_lower, tick_upper] 和当前 tick_current
头寸对于 token 侧 s 的未收取费用为:
此更新在与头寸的每次交互上运行(IncreaseLiquidityDecreaseLiquidityCollectFees)。

工作示例——穿越一个跳价格

池(简化):
  • sqrt_p_x64 = 2^64 · 1.0 = 2^64(价格 = 1.0)
  • L = 1_000_000
  • tick_current = 0
  • 下方的下一个初始化跳价格:tick = −60sqrt_price = 1.0001^(−30) ≈ 0.99700liquidity_net = −400_000(此跳价格结束一个头寸,向下穿越移除 400k)
  • 费率:0.25%
交换:Δin = 10_000 token0,方向 = 0→1。 步骤 1——至 sqrt_target = 0.99700 · 2^64
3,009 < 10,000,所以我们完全填满此步骤:
步骤 2——使用新 L = 600_000 下一个初始化跳价格(比如 tick = −120)位于 sqrt = 0.99402。重新计算 amount_in_to_target
仍然小于 Δin_remaining。再次穿越。继续直到 Δin_remaining 变为零。 完整的 Δout 序列累积到最终交换输出。

初始化和溢出保护

  • MIN_SQRT_PRICE_X64MAX_SQRT_PRICE_X64 对应 tick = ±443636。任何会将 sqrt_p 推出此范围的交换都会失败。
  • 用户的 sqrt_price_limit 参数必须在同一区间内;程序会检查。
  • L · Δsqrt 的乘积在 u256 中计算,然后移位回 u128 以避免溢出。

与 Uniswap v3 的差异

  • 预言机。 Raydium 的 ObservationState 存储 (block_timestamp, tick_cumulative, seconds_per_liquidity_cumulative) 环形缓冲;与 Uniswap 的格式略有不同,但 TWAP 数学相同。
  • Token-2022。 Raydium CLMM 支持 Token-2022 mints;转账费变体需要额外的交换前后金额调整。见 algorithms/token-2022-transfer-fees
  • 跳价格位图。 Raydium 将初始化跳价格位图打包成每个池的 [u64; 16] 以加快 find_next_initialized_tick;Uniswap 使用每字的链上映射。权衡是租金与查找成本。
  • 奖励槽。 Raydium 支持 3 个每池奖励流,每个流有单独的 reward_growth_global_x64 计数器;与费用增长累加器结构相同。

指针

来源:
  • Uniswap v3 白皮书(sqrt-price 数学的规范推导)。
  • Raydium CLMM 程序源代码。