本頁內容由 AI 自動翻譯,所有內容以英文版本為準。查看英文版 →
Sqrt 價格表示法
CLMM 將價格儲存為sqrt_price_x64 — token1 相對於 token0 價格的平方根,以 Q64.64 定點數表示:
其中 p = token1_amount / token0_amount。以 sqrt 而非 p 進行運算能將交換數學線性化(代幣數量差值關於 Δsqrt_price 是線性的),而 x64 定點數在跨越多個 tick 的交換中保持精度。
Tick ↔ sqrt 價格轉換通過 bit-by-bit 對數近似進行預計算:
在 tick_math::get_sqrt_price_at_tick 中實現為基於查表的冪運算。
流動性作為規範單位
在範圍[sqrt_a, sqrt_b] 內(其中 sqrt_a < sqrt_b),流動性 L 的頭寸對應的代幣數量如下。設 sqrt_c = sqrt_price_x64 為池的當前價格。
| 情況 | amount0 | amount1 |
|---|---|---|
sqrt_c <= sqrt_a(池價格在範圍下方) | L · (sqrt_b - sqrt_a) / (sqrt_a · sqrt_b) | 0 |
sqrt_a < sqrt_c < sqrt_b(在範圍內) | L · (sqrt_b - sqrt_c) / (sqrt_c · sqrt_b) | L · (sqrt_c - sqrt_a) |
sqrt_c >= sqrt_b(池價格在範圍上方) | 0 | L · (sqrt_b - sqrt_a) |
x = L / sqrt_p、y = L · sqrt_p。
集成商通常需要反向運算:給定 amount0 / amount1 的存款,計算符合範圍的最大 L。SDK 的 LiquidityMath.getLiquidityFromTokenAmounts 可做此計算。在範圍內的情況下的公式:
哪一側受限決定了實際消耗的比率;另一側可能有剩餘。
單個 tick 交換步驟
交換分步進行。每步要麼 (a) 在當前 tick 範圍內消耗所有可用輸入而不跨越 tick,要麼 (b) 將價格移動到下一個已初始化的 tick。 給定當前狀態(sqrt_c, L) 和向上的交換(token0 進入,token1 流出,sqrt_price 增加),到下一個已初始化 tick 的距離為 sqrt_t。在此微間隔內,輸入與價格的關係為:
及
程式執行以下兩種情況之一:
-
整個輸入能否適應? 如果剩餘輸入(扣除費用後)小於到達
sqrt_t所需的Δamount0,精確求解新的sqrt_c': (用於精確輸入token0 → token1交換)。交換在此步驟內完成,不跨越 tick。 -
輸入超過
Δamount0? 設sqrt_c' = sqrt_t,跨越 tick(應用liquidity_net),將剩餘輸入遞減Δamount0,將輸出遞增Δamount1,然後重複。
token1 → token0,價格下降)的公式中,sqrt_c 和 sqrt_t 互換,反演位置改變。
完整的 Rust 實現位於 raydium-clmm/programs/amm/src/libraries/swap_math.rs。那裡的邏輯與 Uniswap v3 的 SwapMath.computeSwapStep 逐一對應。
每個步驟的費用
交易費從每個步驟的輸入金額中扣除,與 CPMM 的慣例相同:L_i 且在此交換期間保持在範圍內的頭寸稍後能讀回 L_i · Δfee_growth_global / 2^{64} 欠款代幣。
protocol 和 fund 部分分別應計到 PoolState.protocol_fees_token_{0,1} 和 PoolState.fund_fees_token_{0,1},與 CPMM 相同。它們通過 CollectProtocolFee / CollectFundFee 進行清掃。
範圍外和範圍內的費用成長
CLMM 費用會計的棘手部分:頭寸僅在池價格在其範圍內時才賺取費用。池追蹤全局累積費用;頭寸需要知道在其特定範圍內的累積費用。 解決方案是基於 tick 的累加器。每個 tick 儲存:- 如果池的價格高於此 tick(
tick_current >= this_tick),fee_growth_outside = fee_growth_global。(到目前為止賺取的所有費用相對於當前價格位於此 tick 的「外面」— 即下方。) - 否則
fee_growth_outside = 0。
fee_growth_outside:
此規則保持的不變式:對於任何 tick t,fee_growth_outside(t) 等於在 tick_current 位於 t 的另一側時累積的費用。
範圍 [tick_lower, tick_upper] 內的費用成長隨後推導為:
頭寸儲存和讀取的內容
PersonalPositionState 儲存 fee_growth_inside_0_last_x64 和 fee_growth_inside_1_last_x64:上次觸及頭寸時的 fee_growth_inside 值。
在任何後續觸及(增加、減少、收集),程式:
- 使用上述公式計算當前的
fee_growth_inside_{0,1}_x64。 - 計算
Δ = fee_growth_inside_now − fee_growth_inside_last(u128 上的模運算)。 - 將
Δ × position.liquidity / 2^{64}加入tokens_fees_owed_{0,1}。 - 將
fee_growth_inside_last更新為新值。
CollectFees / DecreaseLiquidity 時從保險庫移出,針對 tokens_fees_owed。
獎勵
池最多 3 個獎勵流中的每一個都使用相同的範圍內成長機制,在其自己的reward_growth_global_x64 累加器中。在發放時:
— 發放隨活躍流動性反向縮放,因此更稠密的池每位置每秒支付更少,但跨越更多位置總體。每個頭寸欠款的獎勵為
並通過 CollectReward 領取。參見 /zh-Hant/products/clmm/fees。
實際例子:精確輸入交換
假設:tick_spacing = 60sqrt_price_x64 = 1 × 2^{64}— 價格 = 1.0,因此tick_current = 0。- 活躍流動性
L = 1_000_000 × 2^{64}。 - 上方下一個已初始化 tick:
t = 60(sqrt_price_b ≈1.003004 × 2^{64})。 - 交易費率:500(0.05%)。
SwapBaseInput 精確輸入 1,000 token0。
步驟 1 — 費用:
999 < 2995.5,所以整個輸入符合而不跨越 tick。
步驟 3 — 新價格:
sqrt_c' 略低於 sqrt_c。注意上面的公式適用於 token1 → token0 交換。這裡的例子是 token0 → token1,它向上驅動價格,而非向下 — 因此我們使用 token0 進入 的相應形式:
token0 → token1 的預期交換方向相符:sqrt_c 隨著價格上升。)
步驟 4 — 輸出金額:
trade_fee_rate × protocol_fee_rate / 1e6 在 LP、protocol 和 fund 之間分配(fund 類似);LP 部分流入 fee_growth_global_0_x64。
交換期間的限價單匹配
當交換步驟跨越包含開放限價單的 tick 時,這些單在 LP 曲線之前在 tick 的精確價格處消耗交換輸入。匹配在 tick 內按order_phase 隊列 FIFO 進行。
TickState 上的每個隊列狀態
orders_amount 並繼承下一個 order_phase;它們在前一隊列完全消耗前無法填充。
匹配步驟
在交換期間於每個 tick 交叉處發生的匹配的偽代碼:SettleLimitOrder(或 DecreaseLimitOrder)。池簡單地通過 unfilled_ratio_x64 追蹤隊列現在有多少已填充。每個 LimitOrderState 在開放時儲存其自己的 (order_phase, unfilled_ratio_x64) 快照,因此結算歸結為:
與 LP 曲線的互動
在交換步驟中,限價單匹配發生在 tick(零Δsqrt_price);LP 曲線消耗發生在 tick 之間。因此順序為:
- 跨越 tick
t_cross(首先應用 LPliquidity_net變化,因為這是 Uniswap-V3 的做法)。 - 填充任何位於
t_cross的限價單。 - 沿著 LP 曲線繼續到下一個已初始化的 tick 或
swap_input耗盡。
動態費用推導
PoolState.dynamic_fee_info 承載波動性狀態。每個交換步驟計算每步費率為:
其中:
- —
DYNAMIC_FEE_CONTROL_DENOMINATOR - —
VOLATILITY_ACCUMULATOR_SCALE vol_acc是下面更新規則之後的每次交換累加器tick_spacing來自PoolState.tick_spacing
累加器更新
每次交換按順序應用兩條規則: 衰減。 參考下限基於上次更新以來的時間衰減: 累積。 新累加器是參考加上自前一參考索引以來遍歷的 tick 距離:tick_spacing_index_reference()以 tick-spacing 單位,而非原始 tick:。
為何關於 tick 距離為拋物線
對累加器平方意味著費用隨著價格遠離其參考點走過的距離的平方上升。經驗上這與隨機遊走壓力下的價格方差縮放相符:2倍 tick 偏移意味著 4倍隱含波動率,因此收 4倍附加費。dynamic_fee_control 參數校準絕對水平。
filter_period 窗口防止微小的次秒級振盪(例如 MEV 機器人三明治)膨脹累加器。decay_period 窗口防止單個過去尖峰在市場平靜後無限期地收費。
數值穩健性
- 所有中間乘積通過
u128或u256形狀的算術進行。CLMM 使用U128Sqrt幫助程式和FullMath::mulDiv模式,直接移植自 Uniswap v3。 - 除法四捨五入按步驟選擇以強制不變式
k' ≥ k在本地。SwapBaseInput將輸出四捨五入向下;SwapBaseOutput將輸入四捨五入向上。 - 將
PoolState.liquidity降至零的 tick 交叉是允許的(價格可以穿過「流動性洞」),但交換簡單地前進到下一個已初始化的 tick 而不消耗輸入,不收費。 - 溢出防護:
sqrt_price_x64保持在包含範圍[MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64]內,對應於[MIN_TICK, MAX_TICK]。將超過任一界限的交換撤銷為SqrtPriceLimitOverflow。
下一步
/zh-Hant/products/clmm/ticks-and-positions瞭解 tick 圖參與遍歷方式。/zh-Hant/products/clmm/fees詳細瞭解數學的費用/獎勵部分。/zh-Hant/algorithms/clmm-math瞭解L = sqrt(x · y)和範圍與流動性公式背後的推導。
raydium-io/raydium-clmm—libraries/swap_math.rs、libraries/tick_math.rs- “Uniswap v3 Core” 白皮書,§6–7

