返回 Expert 笔记
Expert Day 84

DEX 微观结构 — Uniswap V2/V3 数学

X·Y=K 推导、Uni V3 集中流动性数学、sqrtPriceX96 / tick / liquidity 关系、virtual reserves

2026-07-24
Phase 2 - 市场微观结构与做市 (Day 75-88)
DEXUniswapAMMConcentratedLiquidityTickMath

日期: 2026-07-24 方向: 量化 / 微观结构 / 做市 阶段: Phase 2 - 市场微观结构与做市 (Day 75-88) 标签: #DEX #Uniswap #AMM #ConcentratedLiquidity #TickMath


今日目标

类型内容
学习X·Y=K 推导、Uni V3 集中流动性数学、sqrtPriceX96 / tick / liquidity 关系、virtual reserves
实操Python 实现 V3 swap simulator、tick crossing、liquidity 数学全套
产出uni_v3_math.py (~700 行):精确复刻 Uniswap V3 合约数学

这是 Phase 2 最关键一天。把 V3 数学每个公式推到位,下两天 (JIT、CL strategy) 才能讲透。


一、Uniswap V2 — Constant Product

1.1 不变式

池中两资产 reserves (x, y),commit invariant: $$ \boxed{x \cdot y = k} $$

每次 swap (Δx_in 加入): $$ y_{new} = \frac{k}{x + \Delta x_{in}}, \quad \Delta y_{out} = y - y_{new} = y - \frac{k}{x + \Delta x_{in}} $$

1.2 价格

瞬时价格(marginal price): $$ P = \frac{y}{x} $$

平均成交价(average execution price): $$ P_{avg} = \frac{\Delta y_{out}}{\Delta x_{in}} = \frac{k - y \Delta x / (x+\Delta x)}{\Delta x} \cdot \frac{1}{...} $$

简化:P_avg = y / (x + Δx) = 新 spot price。

1.3 滑点

$$ \text{slippage} = \frac{P - P_{avg}}{P} = \frac{\Delta x}{x + \Delta x} $$

Δx ≪ x 时 ≈ Δx / x。10% pool drain → 10% slippage。

1.4 fee

V2 收 swap 0.3% (= 30 bps):实际 (1 − fee) Δx_in 进入恒等式:

$$ y_{new} = \frac{k}{x + (1−\text{fee}) \Delta x_{in}} $$

收下的 fee 留在池中(增加 reserves),相当于 LP 直接分。

1.5 LP value(无常损失基础)

LP 持仓 = α (x_0, y_0)。如果价格从 P_0 变到 P_1,reserves 调整成 (x_1, y_1) with x_1 y_1 = k_0y_1/x_1 = P_1

LP 价值(用 token0 计量): $$ V_{LP}(P) = x + y/P = 2\sqrt{k/P} $$

vs HODL: $$ V_{HODL}(P) = x_0 + y_0/P = x_0 + (P_0 x_0)/P $$

定义 IL: $$ \boxed{IL(P/P_0) = \frac{V_{LP} - V_{HODL}}{V_{HODL}} = \frac{2\sqrt{r}}{1+r} - 1, \quad r = P/P_0} $$

rIL
0.5-5.7%
0.75-1.3%
1.00%
1.5-2.0%
2.0-5.7%
4.0-20%

二、Uniswap V3 — Concentrated Liquidity

2.1 关键创新

V2 把流动性均匀分布在 (0, ∞)。V3 让 LP 选 [P_a, P_b] 区间——只在该区间提供流动性。区间外 = 100% 单边 token(如同已经"卖完")。

2.2 数学骨架

定义 sqrt price √P。LP 在 [√P_a, √P_b] 提供 liquidity L。

不变式(在区间内): $$ \boxed{(x + L/\sqrt{P_b})(y + L \sqrt{P_a}) = L^2} $$

或等价地,定义 virtual reserves: $$ x_{virtual} = x + L/\sqrt{P_b}, \quad y_{virtual} = y + L \sqrt{P_a} $$

x_virtual · y_virtual = L²。这就是 V2 形式的 V3——Uniswap 用 virtual reserves 把 V3 数学化简成 V2 形式。

2.3 token amounts in the range

给定区间 [P_a, P_b] 和当前 P,LP 持有:

当 P ≤ P_a(价格在区间下方):100% token0 $$ x = L \cdot \left(\frac{1}{\sqrt{P_a}} - \frac{1}{\sqrt{P_b}}\right), \quad y = 0 $$

当 P ≥ P_b(价格在区间上方):100% token1 $$ x = 0, \quad y = L \cdot (\sqrt{P_b} - \sqrt{P_a}) $$

当 P_a ≤ P ≤ P_b(区间内活跃): $$ \boxed{ x = L \cdot \left(\frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_b}}\right), \quad y = L \cdot (\sqrt{P} - \sqrt{P_a}) } $$

2.4 mint 时计算 L

LP 想存 Δx, Δy,计算需要的 L: $$ L_x = \Delta x \cdot \frac{\sqrt{P} \sqrt{P_b}}{\sqrt{P_b} - \sqrt{P}}, \quad L_y = \frac{\Delta y}{\sqrt{P} - \sqrt{P_a}} $$

实际 L = min(L_x, L_y)(两侧 token 都不超 user 提供)。

2.5 Tick System

V3 用 ticks 离散化价格。每个 tick i 对应: $$ P(i) = 1.0001^i $$

tick spacing 由 fee tier 决定:

Fee tiertick spacing最小价格步长
0.01%11 bps
0.05%1010 bps
0.30%60~60 bps
1.00%200~200 bps

LP 位置只能从 tick i_loweri_upper,且必须是 spacing 倍数。

2.6 sqrtPriceX96

合约用 Q64.96 fixed-point 存 √P: $$ \sqrt{P}_{X96} = \sqrt{P} \cdot 2^{96} $$

tick → sqrtPriceX96: $$ \sqrt{P}_{X96}(i) = \sqrt{1.0001^i} \cdot 2^{96} = 1.0001^{i/2} \cdot 2^{96} $$

合约用查找表 + bit shift 算 getSqrtRatioAtTick(i),O(1) 速度。

2.7 swap 内部循环

合约 swap 逻辑:

1. 找 current tick / sqrtPrice
2. 找下一个 active tick(流动性边界)
3. 计算到下一个 tick 的 amount_in / amount_out
4. 如果 amount_in 不够穿越 tick:
     - 在当前流动性 L 内 partial swap
     - 更新 sqrtPrice(不到 tick 边界)
5. 否则穿越 tick:
     - 全部 swap 到 tick 边界
     - 在 tick boundary 更新 L(加 / 减 net liquidity at this tick)
     - 进入下一个 tick range,goto 3
6. 直到 amount 全部 swap

每个 active tick range 内的数学就是 V2 (virtual reserves)。穿越 tick 时 L 变化。


三、Swap 数学详细

3.1 在区间内 swap (token0 → token1)

输入 Δx,新 √P: $$ \sqrt{P_{new}} = \frac{L \cdot \sqrt{P}}{L + \Delta x \cdot \sqrt{P}} $$

输出 Δy: $$ \Delta y = L \cdot (\sqrt{P} - \sqrt{P_{new}}) $$

3.2 swap (token1 → token0)

$$ \sqrt{P_{new}} = \sqrt{P} + \frac{\Delta y}{L} $$ $$ \Delta x = L \cdot \left(\frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{new}}}\right) $$

3.3 到 tick 边界的 amount

如果要 swap 到 √P_target(边界): $$ \Delta x_{to_target} = L \cdot \left(\frac{1}{\sqrt{P_{target}}} - \frac{1}{\sqrt{P}}\right) $$

如果用户输入 < 这个,partial swap;否则穿越。


四、代码实现:uni_v3_math.py

"""
uni_v3_math.py — Uniswap V3 完整数学实现
依赖:仅 math/decimal(与合约一致)
"""
from decimal import Decimal, getcontext
import math
getcontext().prec = 80

Q96 = 2**96
Q128 = 2**128

# ----------------------------------------------------------
# 1. tick <-> sqrtPriceX96
# ----------------------------------------------------------
def tick_to_sqrt_price_x96(tick: int) -> int:
    """合约 getSqrtRatioAtTick 的 Python 复刻"""
    return int(math.sqrt(1.0001 ** tick) * Q96)

def sqrt_price_x96_to_tick(sqrtP_x96: int) -> int:
    """合约 getTickAtSqrtRatio"""
    sqrtP = sqrtP_x96 / Q96
    return int(math.floor(math.log(sqrtP**2) / math.log(1.0001)))

def sqrt_price_x96_to_price(sqrtP_x96: int, decimals_diff=0) -> float:
    """price = (sqrtP_x96 / Q96) ** 2 * 10^(decimals_diff)"""
    sqrtP = sqrtP_x96 / Q96
    return (sqrtP ** 2) * (10 ** decimals_diff)

def price_to_tick(price: float) -> int:
    return int(math.floor(math.log(price) / math.log(1.0001)))

# 真实 ETH/USDC 0.05% 池示例
# tick = 200000 ≈ ETH price ~$2000/ETH (decimals 6/18 调整后)
# 测试
def test_tick_math():
    tick = 200000
    sqrtP = tick_to_sqrt_price_x96(tick)
    back = sqrt_price_x96_to_tick(sqrtP)
    print(f"tick {tick} -> sqrtPriceX96 {sqrtP}")
    print(f"  -> back to tick {back}, price={1.0001**tick:.4f}")

# ----------------------------------------------------------
# 2. token amounts from L and range
# ----------------------------------------------------------
def amounts_from_liquidity(L: int, sqrtP_x96: int,
                            sqrtP_lower_x96: int,
                            sqrtP_upper_x96: int):
    """
    返回 (amount0, amount1) — LP 在当前 P 下的 token amounts
    """
    if sqrtP_x96 <= sqrtP_lower_x96:
        amount0 = L * Q96 * (sqrtP_upper_x96 - sqrtP_lower_x96) // (sqrtP_lower_x96 * sqrtP_upper_x96)
        amount1 = 0
    elif sqrtP_x96 >= sqrtP_upper_x96:
        amount0 = 0
        amount1 = L * (sqrtP_upper_x96 - sqrtP_lower_x96) // Q96
    else:
        amount0 = L * Q96 * (sqrtP_upper_x96 - sqrtP_x96) // (sqrtP_x96 * sqrtP_upper_x96)
        amount1 = L * (sqrtP_x96 - sqrtP_lower_x96) // Q96
    return amount0, amount1

def liquidity_from_amounts(amount0: int, amount1: int, sqrtP_x96: int,
                           sqrtP_lower_x96: int, sqrtP_upper_x96: int):
    """ mint 时计算 L """
    if sqrtP_x96 <= sqrtP_lower_x96:
        L = amount0 * sqrtP_lower_x96 * sqrtP_upper_x96 // (Q96 * (sqrtP_upper_x96 - sqrtP_lower_x96))
        return L
    elif sqrtP_x96 >= sqrtP_upper_x96:
        L = amount1 * Q96 // (sqrtP_upper_x96 - sqrtP_lower_x96)
        return L
    else:
        L0 = amount0 * sqrtP_x96 * sqrtP_upper_x96 // (Q96 * (sqrtP_upper_x96 - sqrtP_x96))
        L1 = amount1 * Q96 // (sqrtP_x96 - sqrtP_lower_x96)
        return min(L0, L1)

# ----------------------------------------------------------
# 3. swap 数学
# ----------------------------------------------------------
def get_next_sqrt_price_from_amount0(sqrtP_x96: int, L: int, amount0_in: int):
    """ token0 进入,价格下移 (P 是 token1/token0, x 增 → P 跌) """
    if amount0_in == 0: return sqrtP_x96
    numerator = L * sqrtP_x96
    denominator = L + (amount0_in * sqrtP_x96 // Q96)
    return numerator * Q96 // (numerator + amount0_in * sqrtP_x96)

def get_next_sqrt_price_from_amount1(sqrtP_x96: int, L: int, amount1_in: int):
    """ token1 进入,价格上移 """
    return sqrtP_x96 + amount1_in * Q96 // L

def compute_swap_step(sqrtP_x96: int, sqrt_target_x96: int,
                      L: int, amount_remaining: int, fee_pips: int,
                      zero_for_one: bool):
    """
    单步 swap:在当前 tick range 内,要么 swap 完,要么 hit 边界
    返回 (new_sqrtP, amount_in, amount_out, fee_amount)
    """
    if zero_for_one:
        # token0 -> token1, sqrtP 下降, 朝 lower 边界
        amount_in_max = max_amount_0_in(sqrtP_x96, sqrt_target_x96, L)
        amount_in_after_fee = amount_remaining * (1_000_000 - fee_pips) // 1_000_000
        if amount_in_after_fee >= amount_in_max:
            # 穿越到 target
            new_sqrtP = sqrt_target_x96
            amount_in = amount_in_max
            amount_out = max_amount_1_out(sqrtP_x96, sqrt_target_x96, L)
        else:
            # partial swap
            new_sqrtP = get_next_sqrt_price_from_amount0(sqrtP_x96, L, amount_in_after_fee)
            amount_in = amount_in_after_fee
            amount_out = L * (sqrtP_x96 - new_sqrtP) // Q96
        fee = (amount_in * fee_pips) // (1_000_000 - fee_pips)
        return new_sqrtP, amount_in, amount_out, fee
    else:
        # token1 -> token0, sqrtP 上升
        amount_in_max = max_amount_1_in(sqrtP_x96, sqrt_target_x96, L)
        amount_in_after_fee = amount_remaining * (1_000_000 - fee_pips) // 1_000_000
        if amount_in_after_fee >= amount_in_max:
            new_sqrtP = sqrt_target_x96
            amount_in = amount_in_max
            amount_out = max_amount_0_out(sqrtP_x96, sqrt_target_x96, L)
        else:
            new_sqrtP = get_next_sqrt_price_from_amount1(sqrtP_x96, L, amount_in_after_fee)
            amount_in = amount_in_after_fee
            amount_out = L * Q96 * (new_sqrtP - sqrtP_x96) // (sqrtP_x96 * new_sqrtP)
        fee = (amount_in * fee_pips) // (1_000_000 - fee_pips)
        return new_sqrtP, amount_in, amount_out, fee

def max_amount_0_in(sqrtP_x96, sqrt_target, L):
    return L * Q96 * abs(sqrtP_x96 - sqrt_target) // (sqrtP_x96 * sqrt_target)

def max_amount_1_in(sqrtP_x96, sqrt_target, L):
    return L * abs(sqrt_target - sqrtP_x96) // Q96

def max_amount_1_out(sqrtP_x96, sqrt_target, L):
    return L * abs(sqrtP_x96 - sqrt_target) // Q96

def max_amount_0_out(sqrtP_x96, sqrt_target, L):
    return L * Q96 * abs(sqrt_target - sqrtP_x96) // (sqrtP_x96 * sqrt_target)

# ----------------------------------------------------------
# 4. 完整 swap loop(穿越 ticks)
# ----------------------------------------------------------
class Pool:
    """简化 pool: 多个 tick 边界,每个有 net liquidity ΔL"""
    def __init__(self, sqrtP_x96, current_tick, fee_pips, tick_spacing,
                 active_L, tick_liquidity):
        self.sqrtP_x96 = sqrtP_x96
        self.tick = current_tick
        self.fee = fee_pips
        self.spacing = tick_spacing
        self.L = active_L                  # liquidity at current tick
        self.tick_liquidity = tick_liquidity  # dict: tick -> ΔL net

    def swap(self, amount_in: int, zero_for_one: bool):
        """完整 swap:穿越 tick"""
        amount_remaining = amount_in
        amount_total_out = 0
        fee_total = 0
        steps = []
        while amount_remaining > 0:
            # 找下一个 active tick
            if zero_for_one:
                next_tick = max(t for t in self.tick_liquidity if t < self.tick) \
                    if any(t < self.tick for t in self.tick_liquidity) else -887272
            else:
                next_tick = min(t for t in self.tick_liquidity if t > self.tick) \
                    if any(t > self.tick for t in self.tick_liquidity) else 887272
            sqrt_target = tick_to_sqrt_price_x96(next_tick)
            new_sqrtP, ain, aout, fee = compute_swap_step(
                self.sqrtP_x96, sqrt_target, self.L,
                amount_remaining, self.fee, zero_for_one)
            amount_remaining -= (ain + fee)
            amount_total_out += aout
            fee_total += fee
            self.sqrtP_x96 = new_sqrtP
            steps.append({"new_sqrtP": new_sqrtP, "amount_in": ain,
                          "amount_out": aout, "fee": fee, "tick": next_tick})
            # 是否穿越 tick
            if new_sqrtP == sqrt_target:
                self.tick = next_tick
                self.L += self.tick_liquidity.get(next_tick, 0) * (1 if not zero_for_one else -1)
            else:
                self.tick = sqrt_price_x96_to_tick(new_sqrtP)
                break
        return amount_total_out, fee_total, steps

# ----------------------------------------------------------
# 5. 演示:ETH/USDC 0.05% 池
# ----------------------------------------------------------
if __name__ == "__main__":
    test_tick_math()

    # 一个 ETH/USDC 0.05% 池模拟
    # 假设当前 ETH = $2000,tick ≈ -200000(取决于 token order)
    # 简化:单 tick range
    sqrtP = tick_to_sqrt_price_x96(0)        # P = 1
    pool = Pool(
        sqrtP_x96=sqrtP, current_tick=0, fee_pips=500, tick_spacing=10,
        active_L=10**21,
        tick_liquidity={-100: 10**21, 100: -10**21}   # range [-100, 100]
    )

    # swap 100 token0 -> token1
    out, fee, steps = pool.swap(amount_in=10**18, zero_for_one=True)
    print(f"\nSwap 1e18 token0 -> token1: out={out}, fee={fee}")
    print(f"  Steps: {len(steps)}")
    for s in steps:
        print(f"   {s}")
    print(f"  New sqrtPriceX96: {pool.sqrtP_x96}, tick: {pool.tick}")

    # 测试 amounts_from_liquidity
    L = 10**21
    sqrtP_l = tick_to_sqrt_price_x96(-100)
    sqrtP_u = tick_to_sqrt_price_x96(100)
    a0, a1 = amounts_from_liquidity(L, sqrtP, sqrtP_l, sqrtP_u)
    print(f"\nLP at tick=0 with L={L} in range [-100,100]:")
    print(f"  amount0 = {a0}, amount1 = {a1}")

预期输出

tick 200000 -> sqrtPriceX96 ...
  -> back to tick 199999 or 200000, price=485165195.4
Swap 1e18 token0 -> token1: out=996...
  ...
LP at tick=0 with L=1e21 in range [-100,100]:
  amount0 = ...
  amount1 = ...

输出数值取决于精度;与 Uniswap 链上结果对比误差应 < 1 wei。


五、真实数据接入

Uniswap V3 Subgraph (官方)

endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3
query example:
{
  pools(first: 5, orderBy: totalValueLockedUSD, orderDirection: desc) {
    id
    token0 { symbol decimals }
    token1 { symbol decimals }
    feeTier
    sqrtPrice
    tick
    liquidity
    totalValueLockedUSD
  }
}

返回示例:

{
  "data": {
    "pools": [
      {
        "id": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
        "token0": {"symbol":"USDC","decimals":"6"},
        "token1": {"symbol":"WETH","decimals":"18"},
        "feeTier": "500",
        "sqrtPrice": "1842234820392310459203...",
        "tick": "201234",
        "liquidity": "13245678901234567890",
        "totalValueLockedUSD": "189345678.21"
      }
    ]
  }
}

Direct on-chain via JSON-RPC

from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com"))
pool_address = "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640"  # USDC/WETH 0.05%
abi = [...]   # IUniswapV3Pool ABI
pool = w3.eth.contract(address=pool_address, abi=abi)
slot0 = pool.functions.slot0().call()
# slot0 = (sqrtPriceX96, tick, observationIndex, ...)
liquidity = pool.functions.liquidity().call()

Uniswap V3 swap event 链上数据

event Swap(address sender, address recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)

每次 swap 都 emit 完整状态——重建数学几乎无损。


六、CEX vs DEX 对比

维度CEX LOBUniswap V2Uniswap V3Curve / StableSwap
流动性形式离散挂单x·y=k 全区间x·y=k 在 [P_a,P_b]类似 V2 但 invariant 不同
滑点看簿Δx/(x+Δx)同 V2 但用 virtual reserves极小(peg 内)
LP 收益N/Afee 固定 0.3%fee tier + 集中fee + boost
IL 公式N/A见 §1.5区间内类 V2、区间外 capped极小(peg 内)
价格连续度tick (1c BTC)连续 ($)离散 tick连续
更新机制每 ms每 swap每 swap + tick crossing每 swap

V3 vs V2 是 LP 的"集中下注"

V2: liquidity 全部"白白"在 [0, ∞],包括极不可能价格。 V3: 选 [1900, 2100] 集中 100x 流动性 → 同样 capital,区间内 spread 是 V2 的 1/100。但价格出区间 → 0 fee。

数学等价: $$ L_{V3,在区间内} = L_{V2} \cdot \frac{\sqrt{P_b}}{\sqrt{P_b} - \sqrt{P_a}} \cdot \frac{1}{\sqrt{P_a}} $$

集中度 = √(P_b/P_a) / (√(P_b/P_a) − 1)。±5% 区间集中度 ≈ 41x。


七、风险与陷阱

  1. 整数除法精度 合约用 Solidity uint256,无小数。Python 实现要用整数除法 //,浮点会有微小偏差导致 swap 数额不匹配。

  2. Tick spacing 不对齐 mint 时 (tickLower, tickUpper) 必须是 spacing 倍数,否则 revert。

  3. 流动性边界外 0 收益 V3 LP 在区间外不赚 fee,且 100% 单边 token——相当于"已经卖出",未来反弹也不在仓。

  4. Pricing direction confusion tick = log(price) / log(1.0001) 但 price 是哪个 token / 另一个?取决于 token0/token1 的字典序。USDC/WETH 池 token0=USDC、token1=WETH,price = WETH/USDC = ~2000,但 sqrtPrice 表达的是 token1/token0 = USDC per WETH。需要根据 decimals 调整。

  5. Sandwich/MEV 影响 swap math 公开 mempool → MEV bot 在你的 swap 前后插单。你的 effective price 会差 50-200 bps。Day 85 详细。

  6. L 在 tick crossing 时跳变 一个池可能有多个 LP positions 重叠在 tick 上。穿越 tick 时 L 加 / 减的量是该 tick 上所有 LP 的 net liquidity。


八、关键速查

V2

x · y = k
P = y/x
Δy = y - k/(x + (1-fee)Δx)
slip = Δx/(x+Δx)
IL(r) = 2√r/(1+r) - 1

V3

x_v · y_v = L²,  x_v = x + L/√P_b,  y_v = y + L√P_a

In-range amounts:
  x = L (1/√P − 1/√P_b)
  y = L (√P − √P_a)

mint:
  L = min( Δx · √P√P_b / (√P_b−√P), Δy / (√P−√P_a) )

tick:
  P(i) = 1.0001^i
  sqrtPriceX96(i) = √(1.0001^i) · 2^96

swap step (token0 in):
  √P_new = (L · √P) / (L + Δx · √P)
  Δy_out = L (√P − √P_new)
swap step (token1 in):
  √P_new = √P + Δy/L
  Δx_out = L (1/√P − 1/√P_new)

Tick spacing per fee tier

0.01% → 1     (stable pairs)
0.05% → 10    (ETH/USDC)
0.30% → 60    (default)
1.00% → 200   (exotic)

九、面试题

Q1: 推导 V3 在区间内 LP 持有的 token amounts。

见 §2.3。从 virtual reserves x_v y_v = L² 与 marginal price 关系 P = y_v/x_v 出发,解出 x_v = L/√P, y_v = L√P。然后 x = x_v − L/√P_by = y_v − L√P_a,代入得公式。

Q2: 为什么 sqrtPriceX96 要用 √P 而不是 P?

A: 让 swap 数学线性化。Δy = L · Δ(√P)Δx = L · Δ(1/√P)——L 是常数(在 range 内),所以 swap step 是 √P 域的线性变换。如果用 P 直接,会得到 Δy = (L/2)(P + P_new)/√P 之类的复杂表达,且无法在 fixed-point 下精确。

Q3: 用 ±10% 集中区间 vs V2 整池,资金效率提升多少? $$ \text{factor} = \frac{\sqrt{1.10}}{\sqrt{1.10} - \sqrt{0.90}} = \frac{1.0488}{0.0997} \approx 10.5x $$

A: 约 10x。±5% 约 20x,±1% 约 100x。代价是出区间风险与 IL 放大。

Q4: V3 LP 在区间外为什么 100% 单边?

A: 假设当前 P > P_b。V3 数学要求池中持有 token0 = L(1/√P − 1/√P_b)。但 P > P_b → 1/√P < 1/√P_b → 公式给负,意味着 LP 持有 token0 = 0。所有价值在 token1 = L(√P_b − √P_a) 上(这是 token1 的"上限量")。LP 已经"卖光" token0,相当于在 P_b 完成了 buy-low/sell-high。

Q5: 集中流动性是不是 LP 必胜?

A: 不是。集中度提高 fee 收入 c 倍,但 IL 在区间内也乘 c。out-of-range 时间 + IL 通常导致 V3 高 fee tier 的 LP 多数亏 USDC vs HODL(Uniswap labs 自家研究)。只有 能预测/实时调整区间 的 active LP 才赚。Day 86 详细策略。


明日预告

Day 85:JIT 流动性 — 单笔 block 内 mint+swap+burn 的高级 LP 玩法。Just-In-Time LP 是 V3 集中流动性的"极端版"——把流动性恰好放在某 swap 的入口区间,赚 fee 但不持仓。同时讨论 sandwich attacks 的对偶——JIT 是"善意 MEV",sandwich 是"恶意"。链上案例分析。