Uniswap v3 cho Quant: Thanh khoản Tập trung và Toán học Tick từ Nguyên lý Cơ bản
Một vị thế LP trên Uniswap v3 thực chất là một lệnh range order kiểu sổ lệnh giới hạn được cải trang. Nạp thanh khoản vào khoảng giữa hai mức giá, và bạn đã cam kết mua tài sản trên đường giá đi xuống xuyên qua khoảng của mình và bán ra trên đường giá đi lên — đúng như những gì một lưới các lệnh giới hạn đang chờ khớp thực hiện trên một CLOB. Nhưng hợp đồng không lưu trữ giá, khối lượng, hay sổ lệnh. Nó lưu ba con số: một mức giá căn bậc hai ở định dạng số cố định Q64.96, một chỉ số tick nguyên, và một giá trị thanh khoản tổng hợp . Nếu bạn muốn suy luận về một vị thế v3 theo cách bạn suy luận về báo giá trong một mô hình tạo lập thị trường — tồn kho, giá khớp, lựa chọn bất lợi — bạn phải có khả năng dịch qua lại giữa những nguyên thủy on-chain đó và các khái niệm giao dịch mà chúng mã hóa. Bài viết này xây dựng phép dịch đó từ nguyên lý cơ bản, bám theo whitepaper Uniswap v3 (Adams, Zinsmeister, Salem, Keefer, Robinson, 2021) và các hợp đồng lõi, rồi kết thúc ở nơi mà mọi cuộc thảo luận nghiêm túc về LP giờ đây đều bắt đầu: loss-versus-rebalancing.
Từ x·y = k đến virtual reserves
Uniswap v2 là nhà tạo lập thị trường tích hằng số: một pool giữ dự trữ của token0 và của token1 và áp đặt
trên mỗi giao dịch swap. Giá cận biên của token0 tính theo đơn vị token1 là , và thanh khoản được trải đều trên toàn bộ trục giá . Đó là cách sử dụng vốn kém hiệu quả đến cực điểm: một cặp stablecoin giao dịch trong khoảng 0.999 đến 1.001 vẫn giữ hơn 99% vốn của nó nằm ở những mức giá sẽ không bao giờ được in.
Nước đi của v3 là cho phép mỗi LP giới hạn vốn của mình vào một khoảng . Bên trong khoảng đó, vị thế phải hành xử y hệt như một pool v2 — cùng đường cong bonding, cùng cơ chế định giá cận biên — nhưng chỉ dùng phần dự trữ cần thiết để bao phủ khoảng đó. Whitepaper hình thức hóa điều này bằng virtual reserves: vị thế hành xử như thể nó nắm dự trữ kiểu v2 nằm trên đường cong , trong khi dự trữ thực (real reserves) chính là dự trữ ảo trừ đi phần mà vị thế sẽ nắm giữ tại các biên của khoảng:
Đây là phương trình 2.2 của whitepaper, và nó là phương trình quan trọng nhất trong toàn bộ v3. Đường cong đã được tịnh tiến chạm vào các trục: tại dự trữ thực chạm không (vị thế 100% là token1), và tại dự trữ thực chạm không (100% token0). Bên ngoài khoảng, vị thế trở nên bất động — một túi cố định của một token duy nhất, không thu về gì.

Tham số , gọi là thanh khoản (liquidity), là bất biến thay thế cho . Nó được định nghĩa là , và có một cách diễn giải sạch sẽ mà whitepaper nêu rõ: thanh khoản là "virtual reserves trên mỗi đơn vị giá căn bậc hai". Tại bất kỳ giá nào bên trong khoảng, virtual reserves là
Vì sao biến trạng thái lại là √P
Lõi của v3 không theo dõi giá. Nó theo dõi , lưu dưới dạng sqrtPriceX96, một số cố định Q64.96 không dấu:
trong đó là giá thô (raw): số đơn vị cơ sở của token1 trên mỗi đơn vị cơ sở của token0, đã tính cả decimals (chi tiết bên dưới). Lý do dùng căn bậc hai không phải là mánh tiết kiệm gas, mà là đại số. Vi phân các đẳng thức virtual reserve và bạn nhận được hai phương trình swap nền tảng, được hiện thực trong thư viện SqrtPriceMath:
Cả hai delta token đều tuyến tính theo giá căn bậc hai (hoặc nghịch đảo của nó), với là hằng số tỷ lệ. Do đó một swap trong phạm vi một tick không cần đảo ngược đường cong, không cần lặp Newton — chỉ một phép nhân để dịch , rồi hai phép nhân để tính lượng token. Khi một swap đủ lớn để đẩy giá vượt qua một tick đã khởi tạo, pool sẽ băng qua nó (cross), cộng vào hoặc trừ đi lượng thanh khoản ròng được tham chiếu tại đó (liquidityNet), và tiếp tục với tổng hợp mới. Về tổng thể, pool là một AMM tích-hằng-số-từng-đoạn: không đổi giữa các tick đã khởi tạo, nhảy bậc tại các tick.
Đối với một nhà tạo lập thị trường, đây là mô hình tư duy đúng đắn: hồ sơ tổng hợp của pool chính là tương đương DEX của độ sâu sổ lệnh (order-book depth). Trong khi độ sâu CLOB được báo theo đơn vị trên mỗi mức giá, độ sâu AMM là trên mỗi tick — và phép chuyển đổi chính xác là đẳng thức ở trên.
Tick: một lưới giá cách đều theo thang logarit
Các khoảng không thể bắt đầu và kết thúc tại giá tùy ý; chúng phải khớp vào tick. Tick tương ứng với giá
nên mỗi tick cách các tick lân cận một điểm cơ bản (basis point) — không phải theo giá trị tuyệt đối, mà theo giá trị tương đối. Cách chia theo thang log này là có chủ đích: một lưới cộng tính cố định sẽ thô đến mức phi lý đối với một token giao dịch ở mức $0.0001 và mịn đến mức phi lý ở mức $100{,}000, trong khi một lưới hình học cho độ phân giải 1 bp đồng đều ở mọi thang giá. Thư viện TickMath chuyển đổi cả hai chiều — getSqrtRatioAtTick và getTickAtSqrtRatio — bằng cách thao tác bit trên các hằng số được tính sẵn thay vì gọi hàm mũ. Chỉ số tick bị chặn bởi MIN_TICK = -887272 và MAX_TICK = 887272, bao phủ khoảng giá : đủ rộng cho bất kỳ cặp token nào có thể tồn tại trong số học uint256.
Không phải tick nào cũng dùng được. Mỗi mức phí (fee tier) áp đặt một tick spacing, và các vị thế chỉ được dùng những tick chia hết cho nó:
| Mức phí | Tick spacing | Độ rộng khoảng tối thiểu | Ứng dụng điển hình |
|---|---|---|---|
| 0.01% | 1 | ~1 bp | stable/stable |
| 0.05% | 10 | ~10 bp | cặp stable, ETH/stable |
| 0.30% | 60 | ~60 bp | các đồng lớn (majors) |
| 1.00% | 200 | ~2% | ngoại lai / biến động mạnh |
Các mức 0.05%/0.30%/1% ra mắt cùng lúc phát hành vào tháng 5 năm 2021; mức 0.01% được thêm vào qua một cuộc bỏ phiếu quản trị vào tháng 11 năm 2021. Tick spacing thô hơn trên các pool phí cao giữ cho số lượng tick có khả năng bị băng qua ở mức thấp và chi phí gas swap bị chặn trên.
Một cái bẫy về decimals mà ai cũng dính một lần: giá thô on-chain là số token1 trên mỗi token0 tính theo đơn vị cơ sở. Trong pool USDC/WETH trên mainnet, token0 là USDC (6 decimals) và token1 là WETH (18 decimals), nên một mức giá con người là $3{,}000 mỗi ETH tương ứng với giá thô WETH-wei trên mỗi USDC-unit, và nó nằm ở tick
với . Nếu bảng theo dõi của bạn hiển thị một tick gần 196k cho USDC/WETH, giờ bạn đã biết tại sao — và rằng giá con người là đảo lại khi cần.
Lượng token từ (L, khoảng giá): các công thức chính xác
Sản phẩm cốt lõi của phần này: cho một vị thế với thanh khoản trên và giá hiện tại , nó nắm giữ gì? Tích phân các phương trình swap trên toàn khoảng cho ta ba trường hợp:
Giá dưới khoảng () — vị thế 100% là token0:
Giá trên khoảng () — vị thế 100% là token1:
Giá trong khoảng ():
Đây chính xác là những gì SqrtPriceMath.getAmount0Delta và getAmount1Delta tính toán (với làm tròn có hướng — hợp đồng luôn làm tròn theo hướng bất lợi cho người dùng, một chi tiết quan trọng nếu bạn tái hiện điều này trong một backtester và thắc mắc về những sai lệch ở cấp độ wei).
from math import sqrt
def position_amounts(L: float, pa: float, pb: float, P: float):
"""Token amounts held by a v3 position (float model; core uses Q96 ints)."""
sa, sb, sp = sqrt(pa), sqrt(pb), sqrt(P)
if P <= pa:
return L * (1/sa - 1/sb), 0.0
if P >= pb:
return 0.0, L * (sb - sa)
return L * (1/sp - 1/sb), L * (sp - sa)
Ví dụ minh họa: ETH/USDC, khoảng [2500, 3500]
Giả sử ETH giao dịch ở USDC và bạn muốn nạp 1 ETH vào khoảng . Căn bậc hai: , , .
Vế ETH ghim chặt :
Vế USDC sau đó suy ra:
Vậy để mint vị thế này cần 1 ETH + 3,523.69 USDC, tổng giá trị $6,523.69, và lưu ý rằng hai vế không chia 50/50 — tỷ lệ phụ thuộc vào vị trí của bên trong khoảng (một khoảng bất đối xứng chính là cách bạn thể hiện một quan điểm định hướng, hoặc đặt một "range order" thuần túy với chỉ một token).
Bây giờ hãy đưa giá về các biên:
- Tại vị thế đã chuyển đổi hoàn toàn thành ETH: ETH, trị giá $5,716.68. Bạn đã mua 1.2867 ETH trên đường giá đi xuống ở mức giá trung bình gia quyền theo thanh khoản là 3523.69 / 1.2867 \approx \2{,}739$.
- Tại nó đã chuyển đổi hoàn toàn thành USDC: USDC. Bạn đã bán ETH trên đường giá đi lên ở mức trung bình $3,240.
Đó là cách đọc theo range order được cụ thể hóa: vị thế chính là một cái thang lệnh mua từ 3000 xuống 2500 và lệnh bán từ 3000 lên 3500, với khối lượng trên mỗi tick tỷ lệ thuận với . Và sự tập trung chính là thứ bạn được trả công: một vị thế kiểu v2 phủ toàn dải với cùng $6,523.69 vốn sẽ có — vị thế tập trung báo giá độ sâu lớn hơn 12.4 lần trên mỗi tick, và (khi còn trong khoảng) thu phí ở tốc độ gấp 12.4 lần trên mỗi đô-la vốn.
Kế toán phí: feeGrowthGlobal và feeGrowthInside
Phí trong v3 không gộp lãi vào vị thế (một sự phá vỡ có chủ đích so với v2, nơi phí được tái đầu tư vào ). Chúng tích lũy song song, theo từng token, và cơ chế kế toán là một kiệt tác nhỏ của việc ghi sổ O(1), đáng để hiểu bởi mọi pipeline phân tích LP đều phải hiện thực lại nó.
Pool duy trì hai bộ tích lũy toàn cục, feeGrowthGlobal0X128 và feeGrowthGlobal1X128: phí tích lũy trên mỗi đơn vị thanh khoản kể từ khi pool ra đời, ở định dạng cố định Q128.128. Trên mỗi swap, lượng phí (lấy từ token đầu vào) được chia cho đang trong khoảng ở hiện tại rồi cộng vào bộ tích lũy. Đây là cơ chế đứng sau quy tắc vàng của kinh tế học v3: phí chỉ tích lũy cho phần thanh khoản đang trong khoảng tại thời điểm swap. Các vị thế ngoài khoảng không chỉ đơn thuần là tồn kho bất động — chúng thu về đúng bằng không khi không hoạt động.
Để quy đúng phần tăng trưởng toàn cục cho một khoảng hữu hạn, mỗi tick đã khởi tạo lưu feeGrowthOutside0/1X128 — phần tăng trưởng phí xảy ra ở phía bên kia của tick so với giá hiện tại (giá trị này đảo cách diễn giải mỗi lần tick bị băng qua, chính điều đó khiến cơ chế đạt O(1)). Khi đó, với một vị thế trên và tick hiện tại :
Mỗi vị thế lưu một ảnh chụp feeGrowthInsideLast, và phí chưa thu đơn giản là
được cập nhật một cách lười biếng mỗi khi vị thế được chạm tới. Hai hệ quả thực tiễn. Thứ nhất, các delta của feeGrowthInside là cách duy nhất trung thực để đo thu nhập phí của một vị thế — lấy mẫu khối lượng ở cấp độ pool và phân bổ theo tỷ lệ TVL của bạn sẽ sai bất cứ khi nào giá dạo quanh các biên khoảng của bạn. Thứ hai, vì phí nằm ở dạng tokensOwed thay vì gộp lãi, lợi nhuận LP thực hiện được có một khoản lực cản tiền mặt (cash drag) mà v2 không có; các vault tự động gộp lãi tồn tại chính là để arbitrage đi khoản này (trừ đi phí của chính chúng).

Khoản chi trả: một short straddle mà bạn được trả tiền để nắm giữ (có thể)
Bên trong khoảng, giá trị vị thế như một hàm của giá là
Lõm theo — số hạng nói lên toàn bộ câu chuyện. Bên ngoài khoảng nó trở nên tuyến tính: độ dốc ở phía dưới (bạn đang long một túi ETH cố định), độ dốc 0 ở phía trên (bạn đang flat bằng USDC). Chạy ví dụ minh họa của chúng ta trên toàn khoảng và so sánh với việc chỉ đơn giản nắm giữ các token đã nạp:
| (USDC/ETH) | Giá trị LP | Giá trị HODL | Chênh lệch |
|---|---|---|---|
| 2500 | 5,716.68 | 6,023.69 | −307.02 |
| 2750 | 6,200.4 | 6,273.69 | −73.3 |
| 3000 | 6,523.69 | 6,523.69 | 0 |
| 3250 | 6,706.3 | 6,773.69 | −67.4 |
| 3500 | 6,764.06 | 7,023.69 | −259.63 |
LP kém hiệu quả hơn HODL ở cả hai chiều và chỉ bằng HODL đúng tại giá mint. Chặn phía trên, khuếch đại phần tham gia ở phía dưới, giá trị tương đối cực đại tại strike... đây là khoản chi trả của một short straddle (chính xác hơn, khi hai đuôi tuyến tính được bù trừ với chuẩn HODL, đó là một vị thế short trên một dải các quyền chọn có strike rải khắp ). Dòng phí là phần premium. Việc thu hẹp khoảng chính là chọn các strike sát nhau hơn: nhiều premium hơn trên mỗi đơn vị thời gian khi còn trong khoảng, phân kỳ nhanh hơn và sâu hơn khi giá dịch chuyển. Mọi LP v3 đều là một trader short-volatility, dù họ có chọn điều đó hay không — người anh em on-chain của sự đánh đổi rủi ro tồn kho mà Avellaneda–Stoikov hình thức hóa cho các nhà tạo lập thị trường sổ lệnh, với độ rộng khoảng đóng vai trò của spread được báo giá.

Tên gọi truyền thống cho khoảng chênh trong bảng đó là tổn thất tạm thời (impermanent loss, hay divergence loss), nhưng IL-so-với-HODL là một chuẩn đối chiếu thiếu sót: nó trộn lẫn khoản lỗ bạn gánh chịu với tư cách nhà cung cấp thanh khoản với rủi ro thị trường mà bạn dù sao cũng đã mang. Sự phân rã sắc bén hơn đến từ Milionis, Moallemi, Roughgarden và Zhang (2022), "Automated Market Making and Loss-Versus-Rebalancing" (arXiv:2208.06046). Hãy đối chiếu LP không phải với HODL mà với một danh mục tái cân bằng (rebalancing portfolio) nắm giữ, tại mọi thời điểm, cùng lượng token như pool — nhưng giao dịch ở mức giá thị trường bên ngoài không ma sát thay vì giao dịch với các arbitrageur. Sự khác biệt chính là LVR ("lever"): thành phần trong P&L của LP thuần túy là lựa chọn bất lợi, được trả cho các arbitrageur, những người "nhặt" các báo giá cũ kỹ của AMM sau mỗi lần giá dịch chuyển. Dưới một quá trình giá chuyển động Brown hình học với biến động , LVR tích lũy ở tốc độ tức thời
— một "công thức Black–Scholes cho AMM", như các tác giả diễn đạt, với là độ sâu cận biên của pool tại giá hiện tại. Đối với pool tích-hằng-số phủ toàn dải, nó thu về con số nổi tiếng trên giá trị pool mỗi đơn vị thời gian: ở mức biến động 5% mỗi ngày, khoảng 3.1 bp của pool rỉ máu về tay các arbitrageur mỗi ngày, có phí hay không có phí. Sự tập trung nhân lên — ví dụ độ sâu gấp 12.4 lần ở trên cũng là một cỗ máy LVR gấp ~12.4 lần khi còn trong khoảng. Phí phải chạy nhanh hơn khoản rỉ máu đó thì vị thế mới có +EV, và LVR (khác với "IL") là một chi phí vận hành có thể hedge được, có thể dự đoán được, đó chính là điều khiến nó trở thành đơn vị hạch toán đúng đắn. Toàn bộ phép tính lợi nhuận — fee APR so với LVR so với biến động thực hiện, khi nào làm LP thắng thế short option đã delta-hedge — là chủ đề của bài phân tích sâu về impermanent loss và LVR sắp tới, và cơ chế hedging được xử lý riêng trong chiến lược LP và hedging cho v3.
Còn một chi phí nữa nằm sâu hơn một lớp: bởi vì báo giá của AMM chỉ cập nhật khi có ai đó giao dịch, mọi vị thế LP cũng phơi bày trước những trò chơi mempool diễn ra xung quanh các giao dịch đó — tấn công sandwich nhằm vào những người swap trả phí cho bạn, và các bundle arbitrage hiện thực hóa LVR của bạn theo từng block. Hệ sinh thái đó được vẽ ra trong bài viết về MEV và tấn công sandwich.
Đọc trạng thái pool on-chain
Mọi thứ ở trên đều quan sát được từ ba lệnh gọi rẻ tiền tới hợp đồng pool.
slot0() đóng gói phần trạng thái nóng vào một storage slot duy nhất: sqrtPriceX96, tick hiện tại, các chỉ số quan sát oracle, và các cờ protocol-fee/lock. liquidity() trả về tổng hợp đang trong khoảng ở hiện tại — lưu ý đây không phải TVL; nó là tham số độ sâu đang hoạt động tại tick hiện tại và nó nhảy gián đoạn khi giá băng qua một tick đã khởi tạo. ticks(int24) trả về trạng thái theo từng tick: liquidityGross (tổng tham chiếu tick), liquidityNet (lượng có dấu được cộng vào khi băng qua từ trái sang phải), và các bộ tích lũy feeGrowthOutside. Duyệt liquidityNet qua các tick đã khởi tạo (tickBitmap cho bạn biết những tick nào tồn tại mà không cần quét cả 1.7 triệu tick) sẽ tái dựng toàn bộ hồ sơ độ sâu — chính là ảnh chụp sổ lệnh của bạn.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider(RPC_URL))
pool = w3.eth.contract(address=POOL, abi=POOL_ABI) # USDC/WETH 0.05%
sqrt_price_x96, tick, *_ = pool.functions.slot0().call()
L = pool.functions.liquidity().call()
raw_price = (sqrt_price_x96 / 2**96) ** 2 # token1/token0, base units
eth_usdc = 1 / (raw_price * 10**(18 - 6)) # human USDC per ETH
depth_1tick = L * (1.0001**0.5 - 1) * (sqrt_price_x96 / 2**96)
Bản thân các vị thế nằm ở hai nơi. Pool lõi đánh khóa chúng theo (owner, tickLower, tickUpper) — một slot tổng hợp cho mỗi bộ ba owner-khoảng. Nhà đầu tư nhỏ lẻ và hầu hết các quỹ thay vào đó mint qua periphery NonfungiblePositionManager (mainnet: 0xC36442b4a4522E871399CD717aBDD847Ab11FE88), nó bọc mỗi vị thế trong một NFT ERC-721 và phơi bày positions(tokenId) trả về đầy đủ bộ tuple: các token, mức phí, khoảng, , feeGrowthInsideLast, và tokensOwed. Đối với việc theo dõi danh mục, chỉ một lệnh gọi đó cộng với feeGrowthInside hiện tại của pool (tính lại từ ticks() như trong phần trước) cho bạn giá trị mark-to-market và phí đã tích lũy mà không cần chạm tới một indexer — dù đối với bất cứ thứ gì mang tính lịch sử bạn sẽ cần replay các swap-event hoặc một subgraph, bởi vì fee growth phụ thuộc vào đường đi và chuỗi chỉ lưu các bộ tích lũy hiện tại.
Hai chi tiết vận hành đáng nội tâm hóa trước khi triển khai vốn. Thứ nhất, tick spacing lượng tử hóa không gian chiến lược của bạn: trên mức 0.05% bạn có thể đặt các khoảng rộng 10 bp và chạy thứ gì đó gần với một lệnh giới hạn thực thụ (mint ngay trên/dưới giá spot, thu phí, burn sau khi băng qua — whitepaper nêu rõ ràng trường hợp dùng "range order" này), trong khi trên mức 1% khoảng tối thiểu của bạn rộng ~2% và phép loại suy LOB trở nên thô. Thứ hai, một range order là một lệnh giới hạn không có quyền ưu tiên hủy: nếu giá băng qua khoảng của bạn rồi quay lại, bạn round-trip khoản tồn kho và trả lại phần edge (nhưng giữ lại phí). Các khoảng thụ động là những báo giá bạn không thể rút — chính vì thế mà câu hỏi về tần suất tái cân bằng, và lăng kính LVR để trả lời nó, lại quan trọng đến vậy.
Điều này đưa bạn tới đâu
Ngăn xếp v3, nén lại: giá là các tick trên một lưới hình học cơ số 1.0001; độ sâu là , chuyển đổi được sang lượng token bằng không gì khác ngoài các hiệu số của căn bậc hai; phí là một bộ tích lũy trên mỗi mà bạn lấy hiệu qua các biên khoảng của mình; và vị thế thu được là một range order short-vol mà chi phí vận hành của nó có một cái tên, một công thức, và một mã số arXiv. Với các nguyên thủy trong tay, những câu hỏi thú vị trở nên định lượng: rộng bao nhiêu, tái cân bằng thường xuyên đến mức nào, và liệu phí có vượt được LVR cho một pool và một chế độ nhất định hay không — đó chính xác là nơi loạt bài này sẽ đi tiếp.
Tài liệu tham khảo
- Adams, H., Zinsmeister, N., Salem, M., Keefer, R., Robinson, D. (2021). Uniswap v3 Core (whitepaper).
- Uniswap v3 core libraries:
TickMath.sol,SqrtPriceMath.sol,Position.sol,Tick.sol. - Milionis, J., Moallemi, C., Roughgarden, T., Zhang, A. L. (2022). Automated Market Making and Loss-Versus-Rebalancing. arXiv:2208.06046.
Tác Giả
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.