空蕩的體育館:為什麼 v2 的資本大多閒置
回想前兩階學到的 Uniswap v2 ETH/USDC 池:一個持有 流動性池 的 自動做市商,其儲備遵守 恆定乘積公式 x·y = k。這條單一曲線會在從 ETH = 0.0001 美元一路到 ETH = 1,000,000 美元的每一個價位上報價,而你的存款被均勻地抹在整條曲線上。但交易永遠只擠在當前價格附近。所以當 ETH 在 2,000 美元左右成交時,你停在 5 美元或 200,000 美元的那些流動性,就像你買下整座體育館的座位,觀眾卻永遠只坐滿前三排——錢付了,位子空著。
Uniswap v3(2021 年 5 月)問了一個再明顯不過的問題:如果流動性提供者能只把資本放在當前價格周圍的一段區間裡——例如 ETH 介於 1,500 到 2,500 美元——把死區留空,會怎樣?這就是 集中流動性。同樣一塊錢現在能提供深得多的流動性、每塊錢賺到多得多的 交易手續費——但前提是價格要待在你的區間內,而且代價是你以前不必做的那些功課。
虛擬儲備:把 v2 曲線平移到貼住座標軸
精巧之處在這裡。一個落在價格區間 [P_a, P_b] 的 v3 部位,行為完全等同於一個微型 v2 池——同一條雙曲線——只是這條曲線被平移到在 P_b 處碰上 x 軸、在 P_a 處碰上 y 軸。在區間內,它對虛擬儲備滿足一個恆定乘積不變量:
(x + L/sqrt(Pb)) * (y + L*sqrt(Pa)) = L^2 x, y = your REAL tokens in the position (token0, token1) L/sqrt(Pb) = virtual token0 added by the shift L*sqrt(Pa) = virtual token1 added by the shift L = the position's "liquidity" (its depth) # As long as the current price P is inside [Pa, Pb], the real amounts are: # x = L * (1/sqrt(P) - 1/sqrt(Pb)) # token0, e.g. ETH # y = L * (sqrt(P) - sqrt(Pa)) # token1, e.g. USDC
L 是流動性——可以把它想成訂單簿有多深,量化為 √P 每變動一單位、token1 儲備變動多少。合約從不把 √price 存成普通浮點數;它存的是 `sqrtPriceX96`,也就是價格的平方根乘上 2⁹⁶,於是所有深度運算都是精確的整數運算。從上面的公式直接導出兩個推論,而它們在實務上極其重要:
# Real amounts a position of liquidity L holds when P is in range:
amount0 = L * (sqrtPb - sqrtP) / (sqrtP * sqrtPb) # token0
amount1 = L * (sqrtP - sqrtPa) # token1
# If price falls TO/THROUGH Pa -> position becomes 100% token0:
# amount0 = L * (sqrtPb - sqrtPa) / (sqrtPa * sqrtPb) amount1 = 0
# If price rises TO/THROUGH Pb -> position becomes 100% token1:
# amount0 = 0 amount1 = L * (sqrtPb - sqrtPa)
# Inverse: the liquidity a given deposit buys (take the binding side):
L = min( amount0 * (sqrtP * sqrtPb) / (sqrtPb - sqrtP),
amount1 / (sqrtP - sqrtPa) )同樣的錢,八倍的深度
用數字最有感。設 ETH/USDC 在 P = 每 ETH 2,000 USDC。兩位 LP 各提供相同的流動性深度 L = 100:一位攤在完整的 v2 區間 [0, ∞],另一位集中在 [1,500, 2,500]。兩人各要鎖進多少資本?
import math
def amounts(L, P, Pa, Pb):
sP, sA, sB = math.sqrt(P), math.sqrt(Pa), math.sqrt(Pb)
sP = min(max(sP, sA), sB) # clamp price into the range
x = L * (sB - sP) / (sP * sB) # token0 (ETH)
y = L * (sP - sA) # token1 (USDC)
return x, y
L = 100
x2, y2 = amounts(L, 2000, 1e-12, 1e30) # v2: effectively [0, inf]
x3, y3 = amounts(L, 2000, 1500, 2500) # v3: concentrated band
val = lambda x, y, P: y + x * P # value in USDC
print(round(val(x2, y2, 2000), 1)) # 8944.3 USDC of capital
print(round(val(x3, y3, 2000), 1)) # 1071.3 USDC of capital
print(round(val(x2, y2, 2000) /
val(x3, y3, 2000), 2)) # 8.35x capital efficiency- v2 LP 必須投入 2.236 ETH + 4,472 USDC——在 2,000 美元/ETH 下價值 8,944 美元——才能在每個價位都達到深度 L = 100,而其中絕大部分都閒置在離 2,000 美元很遠的地方。
- v3 LP 只用 0.236 ETH + 599 USDC——價值 1,071 美元——就在 *[1,500, 2,500] 之內*達成相同的 L = 100。
- 對交易者報出相同深度、每筆兌換賺相同手續費,但承擔的資本少了約 8.35 倍——這就是資本效率的提升。省下的資本可以投到別處,或者 LP 乾脆少冒一點險。
Tick:把價格線切成 0.01% 的刻度
LP 不能選一段連續的區間——那在鏈上根本無法追蹤。v3 改用 tick(刻度) 把價格軸離散化。第 i 個 tick 對應價格 1.0001^i,所以每個 tick 寬一個基點(0.01%)。你的區間邊界必須對齊到已初始化的 tick,而哪些 tick 可用,取決於池子的費率層級:
price(i) = 1.0001 ** i # price of token1 in units of token0 tick(p) = floor( ln(p) / ln(1.0001) ) tick(2000) -> 76012 # 1.0001^76012 ~= 2000 tick(1500) -> 73135 tick(2500) -> 78244 # Fee tier -> tick spacing (bounds must be multiples of it) # 0.01% -> 1 stable pairs (USDC/USDT) # 0.05% -> 10 correlated / stable-ish # 0.30% -> 60 most volatile pairs (ETH/USDC) # 1.00% -> 200 exotic / illiquid
池子真正在乎的只有有效流動性 L——所有區間橫跨當前 tick 的部位之總和。當一筆兌換推動價格、越過某個 tick 邊界時,引擎就把該邊界的部位從 L 加入或移除(讀取它存的 `liquidityNet`)。手續費以每單位流動性的全域 `feeGrowthGlobal`、加上每個 tick 上的 `feeGrowthOutside` 快照來追蹤,於是任何部位日後都能精確算出它在區間內期間賺到的手續費。一個簡化的兌換迴圈:
# Exact-input swap across concentrated liquidity (one direction)
sqrtP = current_sqrt_price
L = active_liquidity_at_current_tick
while amount_in_remaining > 0:
tick_next = next_initialized_tick(direction)
sqrtP_limit = sqrt_price_at_tick(tick_next)
# constant-product step on the VIRTUAL reserves of the active range:
in_used, out_got, sqrtP_new = compute_swap_step(
sqrtP, sqrtP_limit, L, amount_in_remaining, fee)
amount_in_remaining -= in_used
if sqrtP_new == sqrtP_limit: # price reached the tick edge
L += liquidity_net[tick_next] # switch boundary positions on/off
sqrtP = sqrtP_limit
else: # order filled inside this tick
sqrtP = sqrtP_new
break當價格走出你的區間
集中就是槓桿,而槓桿是雙面刃。看看公式在我們 [1,500, 2,500] 例子的邊界上做了什麼。當 ETH 漲向 2,500 時,部位一路把 ETH 賣成 USDC;價格一碰到 2,500 的瞬間,它就只剩 0 ETH、1,127 USDC——全變成那個剛剛變貴、你本該繼續持有的資產。對稱地,若 ETH 跌到 1,500,部位是 0.582 ETH、0 USDC——你一路加碼買 ETH,如今 100% 押在輸家身上。無論哪一邊,一旦價格跑出區間,你就完全賺不到手續費——在價格回來之前,你只是個旁觀者。
這正是取捨的核心:集中會把你的手續費收入和 無常損失同時放大大約相同的資本效率倍數。這個部位在數學上與一個 v2 LP 完全相同,只是運作在一小片資本上,因此就每塊錢而言,在我們的例子裡一切都被放大約 8 倍。v3 裡藏不住免費的午餐——只有一個更鋒利的賭注。一段窄區間是一個高信念的下注:賭價格會待著不動、又持續成交;賭對了,報酬豐厚;賭錯了,慢慢失血。
提供流動性變成一門主動策略
由於部位只在它的區間內賺錢、且必須重新鑄造才能移動,v3 把曾經被動的「提供流動性」變成了投資組合管理。核心抉擇是區間寬度,而這是個真實的取捨:
- 窄區間 → 資本效率與手續費收入最高,但價格很快就會逃出,你便停止賺錢,而且面臨最頻繁的再平衡(每次重新鑄造都要付 gas 並實現無常損失)。只有在穩定或盤整的交易對上才划算。
- 寬區間 → 每塊錢手續費較少,但能在較大波動中仍留在區間內、很少需要再平衡;極限的 [0, ∞] 就是普通的 v2。維護更省心,回報也更低。
- 費率層級的選擇同樣重要:穩定幣對跑 0.01–0.05% 層級、用超窄的 tick(那種 stableswap 的直覺,如今用一段窄窄的 v3 區間就能達成),而波動大的交易對用 0.30%,以承擔更多無常損失換取更高的報酬。
一整個生態系因應而生來自動化這件事。主動流動性管理器(Gamma、Arrakis 之類)把一個 v3 部位包進金庫,替你再平衡與複利。在掠食的那一端則是及時流動性(JIT liquidity):搜尋者在 記憶池裡盯到一筆即將成交的大額兌換,搶先一個區塊鑄出一個超窄部位、吸走那筆兌換幾乎全部的手續費,再在同一個區塊把它銷毀——這是一種 MEV,下一篇會深入探討。而 Uniswap v4 用 hooks 把這一切一般化,讓開發者能在兌換與部位事件上跑自訂邏輯。重點是:v3 不只讓 AMM 更有效率——它把「當 LP」變成了一門技藝。
一句話講完這個取捨
v3 讓你決定你的錢落在價格曲線上的哪裡。把它集中起來,同樣的資本就掛出深得多的報價、收割多得多的手續費——但只在區間內,而且無常損失會被同一個倍數放大。資本效率與風險,是同一個旋鈕上的兩種讀數。你轉動它,AMM 就分毫不差地照數學行事。