跳轉到主要內容

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

本頁內容由 AI 自動翻譯,所有內容以英文版本為準。查看英文版 →

為什麼是 sqrt 價格,而不是價格

Uniswap v3 系列 CLMM 將價格表示為其平方根,存儲在定點 Q64.64 格式中:
sqrt_price_x64 = floor(sqrt(price) · 2^64)
有三個理由:
  1. 線性流動性數學。 價格範圍內的 token0 或 token1 數量實際上是 sqrt_price 的線性函數,而不是 price 的函數。存儲 sqrt_price 可以讓交換步驟計算這些線性公式時無需計算平方根。
  2. 溢出控制。 sqrt_price · L 在所有合理的參數下都能放入 u256price · L 可能會更早溢出。
  3. 跳點數學均勻。 由於跳點定義為 1.0001^isqrt(price) = 1.00005^i 也是 1.00005 梯級的精確冪。每次跳點穿越轉換為 sqrt_price_x64 空間中的小乘法。
價格和 sqrt 價格是一一對應的;轉換公式是 price = (sqrt_price_x64 / 2^64)^2

跳點格子

價格被離散化到網格上:
price(tick_i) = 1.0001^i
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_upper 對齐到 tick_spacing。池的活躍跳點(那些在其處有流動性開始或結束的跳點)是交換步驟唯一關心的跳點。

流動性轉金額

對於流動性為 L 和價格範圍 [sqrt_lo, sqrt_hi] 的頭寸(所有 sqrt_price 值):
池狀態Token0 金額Token1 金額
價格在範圍上方 (sqrt_p ≥ sqrt_hi)0L · (sqrt_hi − sqrt_lo)
價格在範圍內L · (sqrt_hi − sqrt_p) / (sqrt_p · sqrt_hi)L · (sqrt_p − sqrt_lo)
價格在範圍下方 (sqrt_p ≤ sqrt_lo)L · (sqrt_hi − sqrt_lo) / (sqrt_lo · sqrt_hi)0
推導:對 CPMM 不變量在局部進行微分。在任何單個跳點範圍內,頭寸的行為類似於 CPMM,虛擬儲備為 (x_v, y_v),選擇使得池的當前 (sqrt_p, L)L = sqrt(x_v · y_v) 一致。從 sqrt_p 積分到範圍邊界得到上面的金額。 逆向公式(用於為給定的 amount0amount1 鑄造頭寸時):
L_from_amount0(amount0, sqrt_lo, sqrt_hi, sqrt_p) =
    amount0 · sqrt_p · sqrt_hi / (sqrt_hi − sqrt_p)

L_from_amount1(amount1, sqrt_lo, sqrt_hi, sqrt_p) =
    amount1 / (sqrt_p − sqrt_lo)

// 對於對範圍內頭寸的對稱存入,取最小值。
L = min(L_from_amount0, L_from_amount1)

單跳交換步驟

在單個跳點範圍內,池的行為類似於 CPMM。給定當前 sqrt_p 和目標 sqrt_target
Δamount0_step = L · (sqrt_target − sqrt_p) / (sqrt_p · sqrt_target)     // 如果交換 token0
Δamount1_step = L · (sqrt_target − sqrt_p)                              // 如果交換 token1

精確輸入步驟

給定 Δin_remaining
// 如果我們填充到跳點邊界,新 sqrt_p 的候選值:
sqrt_after_full = sqrt_target
amount_to_full  = Δamount_in_to_reach(sqrt_p → sqrt_target)

if Δin_remaining ≥ amount_to_full:
    // 消耗該桶的剩餘部分
    sqrt_p'         = sqrt_target
    Δin_consumed    = amount_to_full
    Δout            = amount_out_at_boundary
else:
    // 我們不穿越;求解終端 sqrt_p
    sqrt_p'         = L · sqrt_p / (L + Δin_remaining · sqrt_p)      // 對於 0→1 交換
    Δin_consumed    = Δin_remaining
    Δout            = L · (sqrt_p − sqrt_p')                          // 與 Δsqrt 成比例
0→1 交換降低 sqrt_p(當我們賣出 token0 時價格下降)。1→0 交換提高它。公式在 sqrt_psqrt_target 互換時是對稱的。

精確輸出步驟

相同結構,但改為求解 Δin

多跳交換循環

交換遍歷跳點直到輸入耗盡或價格限制被觸及:
while Δin_remaining > 0 and sqrt_p != sqrt_price_limit:
    next_tick = find_next_initialized_tick(pool.tick_current, direction)
    sqrt_target = min(next_tick.sqrt_price, sqrt_price_limit)       // 方向上

    (Δin, Δout, sqrt_p') = single_step(sqrt_p, sqrt_target, L, Δin_remaining)

    Δin_remaining -= Δin
    accumulated_out += Δout

    if sqrt_p' == next_tick.sqrt_price:
        // 穿越跳點
        L += next_tick.liquidity_net * direction_sign
        flip_fee_growth_outside(next_tick)
        match_limit_orders_at_tick(next_tick, ...)        // 見 products/clmm/math
        pool.tick_current = next_tick.tick_index
    sqrt_p = sqrt_p'
每個 single_step 使用池的當前 LL 只在穿越初始化跳點時改變。跳點之間的流動性是常數,這正是使步驟數學為封閉形式的原因。 跳點處的 liquidity_net 是在該跳點開始的頭寸流動性減去在該跳點結束的頭寸流動性的有符號和。向上穿越添加 liquidity_net;向下穿越減去它。 當池在跳點處有限價訂單打開時,穿越跳點步驟也會機會性地消耗部分交換輸入來填充這些訂單(按群組跨越 FIFO)。匹配算法和可能在基本步驟之上應用的動態費用附加費在 products/clmm/math 中記錄;它們不改變上面的封閉形式單步公式。

費用增長累加器

CLMM 跟蹤每單位活躍流動性的費用,按側,全局和每跳點:
fee_growth_global_0_x64     // Q64.64,單調遞增
fee_growth_global_1_x64
tick.fee_growth_outside_0_x64   // "該跳點在活躍範圍外時應計的費用"
tick.fee_growth_outside_1_x64
在每個 single_step 上:
step_lp_fee = (Δin · fee_rate) · (1 − protocol_fraction − fund_fraction)
fee_growth_global += step_lp_fee · 2^64 / L     // 僅針對輸入側
(另一側的 fee_growth_global 在此步驟上不移動,因為該側沒有代幣作為輸入支付。) 穿越跳點時,程序翻轉 fee_growth_outside
tick.fee_growth_outside_0_x64 = fee_growth_global_0_x64 − tick.fee_growth_outside_0_x64
tick.fee_growth_outside_1_x64 = fee_growth_global_1_x64 − tick.fee_growth_outside_1_x64
「Outside」相對於 tick_current。當 tick_current 在跳點上方時,outside 表示「下方」。當 tick_current 在下方時,outside 表示「上方」。翻轉交換了解釋。

頭寸的 fee_growth_inside

給定頭寸 [tick_lower, tick_upper] 和當前 tick_current
if tick_current >= tick_upper:
    inside = tick_lower.fee_growth_outside − tick_upper.fee_growth_outside
else if tick_current < tick_lower:
    inside = tick_upper.fee_growth_outside − tick_lower.fee_growth_outside
else:     // 頭寸在範圍內
    inside = fee_growth_global
           − tick_lower.fee_growth_outside
           − tick_upper.fee_growth_outside
頭寸對於代幣側 s 的未收集費用為:
tokens_owed_s += L · (fee_growth_inside_s − fee_growth_inside_last_s) / 2^64
fee_growth_inside_last_s = fee_growth_inside_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
amount_in_to_target = L · (1/sqrt_target − 1/sqrt_p)
                    = 1_000_000 · (1/0.99700 − 1/1.0)
                    ≈ 1_000_000 · 0.003009
                    ≈ 3_009
3,009 < 10,000,所以我們完全填充此步驟:
Δin_step  = 3_009 / (1 − 0.0025)  = 3_017    // 費用總額
Δout_step = L · (sqrt_p − sqrt_target) ≈ 1_000_000 · 0.00299 ≈ 2_990
sqrt_p    = 0.99700 · 2^64
tick_current = −60
L         = 1_000_000 + (−400_000)  = 600_000         // 穿越了跳點
tick −60 處的 fee_growth_outside 被翻轉
Δin_remaining = 10_000 − 3_017 = 6_983
步驟 2 — 使用新的 L = 600_000 下一個初始化跳點(例如 tick = −120)位於 sqrt = 0.99402。重新計算 amount_in_to_target
amount_in_to_target = 600_000 · (1/0.99402 − 1/0.99700)
                    ≈ 600_000 · 0.003010
                    ≈ 1_806
仍然少於 Δ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 鑄幣;轉移費用變體需要額外的交換前/後金額調整。見 algorithms/token-2022-transfer-fees
  • 跳點位圖。 Raydium 將初始化跳點位圖打包到每個池的 [u64; 16] 中以快速 find_next_initialized_tick;Uniswap 使用每字映射。權衡是租金 vs 查找成本。
  • 獎勵槽。 Raydium 支持每個池 3 個獎勵流,有單獨的 reward_growth_global_x64 計數器;與費用增長累加器的結構相同。

指針

資料來源:
  • Uniswap v3 白皮書(sqrt 價格數學的規範推導)。
  • Raydium CLMM 程式源代碼。