集中流动性策略 (Concentrated Liquidity Strategy)
V3 LP 策略矩阵、re-balance 触发器、IL hedge via perp 数学、5 种策略对比
日期: 2026-07-26 方向: 量化 / 微观结构 / 做市 阶段: Phase 2 - 市场微观结构与做市 (Day 75-88) 标签: #DEX #UniswapV3 #LP #IL #Hedge #DeltaNeutral
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | V3 LP 策略矩阵、re-balance 触发器、IL hedge via perp 数学、5 种策略对比 |
| 实操 | 实现 Python V3 LP simulator + ETH/USDC 1 月回测、5 策略对比、IL hedge 优化 |
| 产出 | cl_strategy.py:完整 active V3 LP 策略 + 回测报告 |
把昨天的 V3 数学 + 之前的 GLFT 思路结合 → 真正可部署的 DEX 做市策略。这是 PM 简历上最有说服力的章节。
一、V3 LP 策略矩阵
| 策略 | range 宽度 | re-balance | IL hedge | capital efficiency | 适合 |
|---|---|---|---|---|---|
| V2 emul | (0, ∞) | 永不 | 无 | 1x | benchmark |
| V3 wide | ±50% | 永不 / 数月一次 | 无 | 2-3x | 入门 LP |
| V3 narrow | ±5% | 价格出区间触发 | 无 | 20x | 主动 LP |
| V3 active | ±5%, 滚动跟随 mid | 每日 / 每事件 | 无 | 20x + active | 半专业 |
| V3 delta-neutral | ±10% + perp hedge | 持续 hedge delta | perp short | 10-20x with σ-neutral | quant fund |
| JIT | 1-tick | 每 swap | 无 | 1000x | MEV bot |
下面我们实现策略 1-5 并回测。
二、V3 LP 价值与希腊字母
2.1 LP value(在区间内)
$$ V_{LP}(P) = x(P) + y(P) / P_0 \quad (\text{以 token0 计}) $$
其中: $$ x(P) = L (1/\sqrt{P} - 1/\sqrt{P_b}), \quad y(P) = L (\sqrt{P} - \sqrt{P_a}) $$
把 y 折成 token0: $$ V_{LP}(P) = L \left[\frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_b}}\right] + \frac{L (\sqrt{P} - \sqrt{P_a})}{P} $$
化简: $$ \boxed{V_{LP}(P) = \frac{2L \sqrt{P} - L(\sqrt{P_a} + P/\sqrt{P_b})}{P}} $$
(还可写成更对称形式)
2.2 LP delta
$$ \Delta_{LP} = \frac{\partial V_{LP}}{\partial P} $$
在区间内,简化的 delta(以 token0 单位):
$$ \Delta_{LP} = -\frac{L}{2 P^{3/2}} + \frac{L \sqrt{P_a}}{P^2} $$
直觉:当 P 接近 P_b(区间上界),LP 持有 100% token0,delta ≈ 0;当 P 接近 P_a(下界),LP 持有 100% token1,delta ≈ token1 持仓。
2.3 LP gamma(短 gamma)
$$ \Gamma_{LP} = \frac{\partial^2 V_{LP}}{\partial P^2} < 0 $$
V3 LP 是 short gamma(凹):mid 大幅波动时 LP 始终亏 vs HODL(IL)。这是要 hedge 的根本原因。
2.4 IL 公式(V3)
V3 IL 比 V2 复杂: $$ IL_{V3}(P; P_a, P_b, P_0) = \frac{V_{LP}(P) - V_{HODL}(P)}{V_{HODL}(P)} $$
数值结果:
- ±5% range,价格移动 5%(出区间):IL ≈ -0.6%(V2 同移动 -0.06%)
- ±10% range,移动 10%(出区间):IL ≈ -2.5%(V2 -0.6%)
- 集中度越高 IL 越大(因为 fee 也大,需要 break-even 分析)
三、Re-balance 决策
3.1 触发条件
| 触发器 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| Out-of-range | P 超出 [P_a, P_b] | 简单 | 已经 100% 单边,IL 已发生 |
| Time-based | 每 24h re-balance | 可预测 gas | 与市场无关 |
| Vol-based | σ 突变时调宽 / 调窄 | adapts | 需要 vol forecast |
| Distance-based | P 距区间中心 > X% | 主动 | 频繁触发 |
实务最优:combine:vol-based width + distance-based trigger。
3.2 再平衡成本
gas: ~$30-100 (mainnet) / ~$0.05-1 (L2)
slippage: 卖出"剩余的极少 token" 进 swap 触发滑点
fee 损失: re-mint 后初始 fee = 0
每月 re-balance < 5 次才能 cover gas。L2 上可以更频繁。
3.3 范围选择:Vol-based formula
σ_30d annualized → σ_24h ≈ σ_30d / √365
range_width = α · σ_24h · current_price · √(holding_period_days)
α = 1-3(取决于 confidence level)。例如 ETH σ_30d = 60%,holding 1 day:
- σ_1d ≈ 60% / √365 = 3.14%
- α=2 → range = ±6.3%
如果想更"积极赚 fee",α=1.0 → ±3%;保守 α=3 → ±10%。
四、Delta-Neutral 策略
4.1 思路
- Mint LP at [P_a, P_b]
- 计算瞬时 LP delta
Δ_LP - 在 perp 市场 short
Δ_LP单位 ETH - 价格变化 → LP delta 变化(V3 short gamma)
- 根据触发器 re-balance hedge size
理想下:
$$ \text{Net delta} = \Delta_{LP}(P) + \Delta_{perp} \approx 0 $$
但 V3 short gamma 意味着每个 P 移动后 Δ_LP 变化,hedge 也要调整 → gamma scalping 成本。
4.2 收支拆解
$$ \text{PnL}{neutral} = \text{Fee}{LP} + \text{Funding}{short} - \text{IL}{LP} - \text{Hedge cost} $$
各项典型值(30 day, ETH/USDC ±10%, 100k capital):
- Fee: $300-1500(依赖 vol,高 vol 收 fee 多)
- Funding: $10-100(perp short 大多正 funding)
- IL: -$50-300(hedge 后大幅减小)
- Hedge cost (rebalance gas + slip): -$50-200
净:$200-800 / month(机会成本 4-10% APY),超过 V2 LP 通常。
4.3 Delta hedge 频率
每秒 rebalance 太贵;放任 delta 漂移会 IL 累积。最优:
$$ |\text{Net delta}| > \text{threshold} \Rightarrow \text{rebalance} $$
threshold ≈ 5-10% 持仓。Pancake / Arrakis Vault 等专业 V3 manager 用此逻辑。
五、代码实现:cl_strategy.py
"""
cl_strategy.py — V3 LP 5 种策略 + 1 月 ETH/USDC 回测
依赖:numpy, pandas, requests, matplotlib
"""
import numpy as np, pandas as pd, requests
import matplotlib.pyplot as plt
from dataclasses import dataclass, field
from typing import Optional, List
# ----------------------------------------------------------
# 1. V3 helper functions(沿用 Day 84)
# ----------------------------------------------------------
def amounts_for_L(L, sqrtP, sqrtP_a, sqrtP_b):
if sqrtP <= sqrtP_a:
x = L * (1/sqrtP_a - 1/sqrtP_b); y = 0
elif sqrtP >= sqrtP_b:
x = 0; y = L * (sqrtP_b - sqrtP_a)
else:
x = L * (1/sqrtP - 1/sqrtP_b); y = L * (sqrtP - sqrtP_a)
return x, y
def L_from_amounts(x, y, sqrtP, sqrtP_a, sqrtP_b):
if sqrtP <= sqrtP_a:
return x * sqrtP_a * sqrtP_b / (sqrtP_b - sqrtP_a)
elif sqrtP >= sqrtP_b:
return y / (sqrtP_b - sqrtP_a)
else:
L_x = x * sqrtP * sqrtP_b / (sqrtP_b - sqrtP)
L_y = y / (sqrtP - sqrtP_a)
return min(L_x, L_y)
def lp_value(L, sqrtP, sqrtP_a, sqrtP_b):
"""以 token0(USDC) 计 LP value: V = x + y · P_token0_per_token1
这里 P = sqrtP², 即 1 token1 = P token0"""
x, y = amounts_for_L(L, sqrtP, sqrtP_a, sqrtP_b)
P = sqrtP ** 2
return x + y * P # token1 valued in token0
def lp_delta(L, sqrtP, sqrtP_a, sqrtP_b, eps=1e-4):
"""numerical delta: dV/dP"""
P = sqrtP ** 2
sqrtP_up = np.sqrt(P + eps)
sqrtP_dn = np.sqrt(P - eps)
V_up = lp_value(L, sqrtP_up, sqrtP_a, sqrtP_b)
V_dn = lp_value(L, sqrtP_dn, sqrtP_a, sqrtP_b)
return (V_up - V_dn) / (2 * eps) # in units of token1 (ETH)
# ----------------------------------------------------------
# 2. 策略定义
# ----------------------------------------------------------
@dataclass
class StrategyState:
capital_t0: float = 100_000.0 # USDC
sqrtP_a: float = 0.0
sqrtP_b: float = 0.0
L: float = 0.0
fee_accumulated: float = 0.0
perp_short_eth: float = 0.0 # 数量 (ETH)
funding_paid: float = 0.0
rebalance_count: int = 0
cash_residual: float = 0.0 # 未投入 LP 的 USDC
def init_lp(state: StrategyState, P_now: float, range_pct: float):
sqrtP = np.sqrt(P_now)
P_a = P_now * (1 - range_pct)
P_b = P_now * (1 + range_pct)
state.sqrtP_a = np.sqrt(P_a)
state.sqrtP_b = np.sqrt(P_b)
# 50/50 split
cap_per = state.capital_t0 / 2
x_target = cap_per
y_target = cap_per / P_now
state.L = L_from_amounts(x_target, y_target, sqrtP,
state.sqrtP_a, state.sqrtP_b)
used_x, used_y = amounts_for_L(state.L, sqrtP,
state.sqrtP_a, state.sqrtP_b)
state.cash_residual = state.capital_t0 - (used_x + used_y * P_now)
# ----------------------------------------------------------
# 3. fee accrual model
# ----------------------------------------------------------
def fee_accrued_in_step(state, P_now, P_prev, hourly_volume_usd, fee_tier):
"""简化:fee = swap_volume × fee_rate × in_range_share × L_share
in_range: 假设 P_prev, P_now 都在 range 内
L_share: state.L / pool_L_total(外部估计,简化为常数)"""
sqrtP = np.sqrt(P_now); sqrtP_p = np.sqrt(P_prev)
if sqrtP < state.sqrtP_a or sqrtP > state.sqrtP_b:
return 0.0 # 区间外不收 fee
pool_L = 1e22 # 假设池总 L (USDC equivalent)
share = state.L / (state.L + pool_L)
fee = hourly_volume_usd * fee_tier * share
state.fee_accumulated += fee
return fee
# ----------------------------------------------------------
# 4. 5 种策略主循环
# ----------------------------------------------------------
def run_strategy(prices: np.ndarray, hourly_vol: np.ndarray,
strat: str = "v3_narrow",
capital: float = 100_000,
rebalance_period_h: int = 24,
range_pct: float = 0.05,
fee_tier: float = 0.0005,
funding_per_h: float = 0.0001/8):
"""
prices: 每小时 ETH 价格序列
hourly_vol: 每小时该 pool 总成交量 (USDC)
"""
n = len(prices)
state = StrategyState(capital_t0=capital)
init_lp(state, prices[0],
range_pct if strat in ("v3_wide","v3_narrow","v3_active","delta_neutral")
else 0.99)
if strat == "delta_neutral":
delta = lp_delta(state.L, np.sqrt(prices[0]),
state.sqrtP_a, state.sqrtP_b)
state.perp_short_eth = delta
history = []
for i in range(1, n):
P = prices[i]; P_prev = prices[i-1]
# fee
fee_accrued_in_step(state, P, P_prev, hourly_vol[i], fee_tier)
# funding
if state.perp_short_eth != 0:
state.funding_paid -= state.perp_short_eth * P * funding_per_h
# rebalance condition
sqrtP_now = np.sqrt(P)
out_of_range = (sqrtP_now < state.sqrtP_a or sqrtP_now > state.sqrtP_b)
time_to_rebalance = (i % rebalance_period_h == 0) and strat == "v3_active"
delta_drift = False
if strat == "delta_neutral":
cur_delta = lp_delta(state.L, sqrtP_now, state.sqrtP_a, state.sqrtP_b)
net = cur_delta - state.perp_short_eth
if abs(net) / (cur_delta + 1e-9) > 0.10:
delta_drift = True
if out_of_range or time_to_rebalance or delta_drift:
# 估算当前 LP value + cash + perp PnL
V_lp = lp_value(state.L, sqrtP_now, state.sqrtP_a, state.sqrtP_b)
perp_pnl = state.perp_short_eth * (prices[0] - P) # short profits if P drops
new_cap = V_lp + state.cash_residual + perp_pnl + state.fee_accumulated
state.capital_t0 = new_cap
state.fee_accumulated = 0
init_lp(state, P, range_pct)
if strat == "delta_neutral":
state.perp_short_eth = lp_delta(state.L, sqrtP_now,
state.sqrtP_a, state.sqrtP_b)
state.rebalance_count += 1
# log
V_lp = lp_value(state.L, sqrtP_now, state.sqrtP_a, state.sqrtP_b)
perp_pnl = state.perp_short_eth * (prices[0] - P)
total_v = V_lp + state.cash_residual + perp_pnl + state.fee_accumulated + state.funding_paid
history.append({"i":i, "P":P, "V_lp":V_lp, "fee":state.fee_accumulated,
"funding":state.funding_paid, "perp":state.perp_short_eth,
"total":total_v})
return pd.DataFrame(history), state
# ----------------------------------------------------------
# 5. 数据 + 比较
# ----------------------------------------------------------
def fetch_eth_prices_hourly(days=30):
"""Binance ETHUSDT 1h klines 作为 ETH 价格代理"""
r = requests.get("https://api.binance.com/api/v3/klines",
params={"symbol":"ETHUSDT","interval":"1h","limit":days*24}).json()
prices = np.array([float(x[4]) for x in r])
vols = np.array([float(x[7]) for x in r]) # quote vol
# USDC/WETH pool ~ 4-8% of total Binance vol(粗估)
pool_vol = vols * 0.06
return prices, pool_vol
def compare_strategies():
prices, pool_vol = fetch_eth_prices_hourly(days=30)
out = {}
strategies = {
"v3_wide": dict(strat="v3_wide", range_pct=0.30, rebalance_period_h=720),
"v3_narrow": dict(strat="v3_narrow", range_pct=0.05, rebalance_period_h=720),
"v3_active": dict(strat="v3_active", range_pct=0.05, rebalance_period_h=24),
"delta_neutral":dict(strat="delta_neutral", range_pct=0.10,
rebalance_period_h=24),
}
for name, kw in strategies.items():
df, st = run_strategy(prices, pool_vol, **kw)
final = df.iloc[-1]["total"]
out[name] = {
"final_value": final,
"PnL": final - 100_000,
"PnL_pct": (final-100_000)/100_000 * 100,
"rebalance_count": st.rebalance_count,
"fee_total": df["fee"].iloc[-1],
"max_drawdown": (df["total"].cummax() - df["total"]).max(),
}
return pd.DataFrame(out).T
if __name__ == "__main__":
rep = compare_strategies()
print(rep.to_string())
预期输出(30 day, ETH 大致波动 5-15%):
final_value PnL PnL_pct rebalance_count fee_total max_drawdown
v3_wide 100245.32 245.32 0.245 0 245.32 1234.5
v3_narrow 99543.10 -456.90 -0.457 5 891.20 2345.0
v3_active 100876.40 876.40 0.876 30 721.10 1102.4
delta_neutral 101203.45 1203.45 1.203 62 650.30 512.3
观察:
- delta_neutral 通常 win 在 vol 适中场景(fee + funding > IL + hedge cost)
- v3_narrow naive 因频繁 out-of-range 表现不如 wide
- v3_active 通过周期 rebalance 逼近 active management
- 实际数字依赖 ETH 该月走势(trend / vol)
六、真实数据接入
Uniswap V3 Subgraph
endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3
query (取 USDC/WETH 0.05% pool 1h 数据):
{
poolHourDatas(
first: 720, where: {pool:"0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640"},
orderBy: periodStartUnix, orderDirection: desc) {
periodStartUnix
volumeUSD
feesUSD
tvlUSD
sqrtPrice
liquidity
token0Price
token1Price
}
}
返回示例:
{
"data": {"poolHourDatas":[
{"periodStartUnix":"1700000000",
"volumeUSD":"5234567.89",
"feesUSD":"2617.28",
"tvlUSD":"189000000",
"liquidity":"13245678901234567890",
"token0Price":"2153.45","token1Price":"0.000464"}
]}
}
Hedge venue: GMX / dYdX / Hyperliquid
# Hyperliquid perp short hedge 示例
# POST /info
{
"type":"clearinghouseState",
"user":"0x..."
}
# POST /exchange (sign tx) - place market short
{
"action":{
"type":"order",
"orders":[{"a":4, "b":false, "p":"0", "s":"0.5", "r":false,
"t":{"market":{"slippage":0.005}}}]
}
}
七、CEX vs DEX 对比
| 维度 | CEX 做市 | V3 LP narrow | V3 LP delta-neutral |
|---|---|---|---|
| 报价机制 | A-S/GLFT 显式 | 区间隐式 | 区间 + perp hedge |
| revenue source | spread + maker rebate | swap fee | fee + funding |
| risk source | inventory mid drift | IL | residual delta + funding |
| capital cost | 1× (low) | 1× | 2× (LP + perp 抵押) |
| adjust frequency | µs | 12s-day | 12s-hour |
| gas cost / period | 0 | $50-300/month | $300-2000/month |
| typical Sharpe | 2-5 | 0-2 | 1-3 |
| scale | 显式 | 受 capital 限制 | 受双侧抵押限制 |
V3 vs CEX 做市的"效率上限"
CEX 做市可压 spread 到 0.5-1 bps(fee minus rebate)。V3 最低 fee tier 1 bps(0.01%)。但 V3 LP 不付 cancel/replace gas fee → 长期 holding 成本结构不同。
结论:
- 对短期 / 高频 做市,CEX 永远赢
- 对长期 / 大资本 流动性提供,V3 delta-neutral 提供独特无许可入口
- 顶级 fund(GSR / Wintermute)在两边都做:CEX 主战场,V3 作为多元化 + 链上品牌建设
八、风险与陷阱
-
Pool L 估计错误 把池子 active L 估成 1e22 实际是 1e21 → fee share 算错 → strategy gross profit 错估 10x。修复:从 subgraph 取实际历史 L_t 数据。
-
Range out-of-range 损失被低估 V3 LP 在区间外 100% 单边,等于"已经卖了"。如果 ETH 一直涨,out 在上方 = 100% USDC,错过 ETH 升值。回测必须计入。
-
Re-balance 滑点循环 re-balance 时 swap 一边 token 调比例 → 自身 swap 触发 IL → 再 IL → ...。修复:用 router aggregator (1inch) 或者 batch rebalance。
-
Funding rate 反向 ETH crash 时 funding 可极负 → perp short 付 funding -> 策略亏损。修复:funding < threshold 时减小 perp 头寸(接受 partial delta)。
-
Liquidation on perp perp 抵押不足 → liquidation。延续 delta-neutral 必须保持 maintenance margin。修复:抵押 50% buffer。
-
Subgraph 延迟 / down The Graph 偶尔 stale 几个 block → 决策基于错数据。修复:直接 RPC 调用 pool.slot0() 校验。
-
MEV 抢跑 rebalance rebalance tx 进 mempool → bot sandwich → re-mint 区间不准。修复:Flashbots private bundle。
九、关键速查
LP value (token0 unit):
V = x + y · P (P = token0 per token1, depends on direction)
LP delta (numerical):
Δ ≈ (V(P+ε) - V(P-ε)) / (2ε)
LP gamma:
Γ < 0 (always short gamma in V3)
IL formula (V3 in-range, simplified):
IL ≈ (1/2) · σ² · t · (集中度因子) · 单位
Range width vol-based:
width = α · σ_T · P, α ∈ [1, 3]
Re-balance triggers (combined):
out-of-range OR time > T_max OR |delta_drift| > 10%
Delta-neutral 关键平衡:
L · ∂(amount0+amount1·P)/∂P + perp_short = 0
经验值 (USDC/WETH 0.05% pool, 1 month)
v3 ±50% APY: 10-30% (passive, IL 不大)
v3 ±5% APY: 50-200% (高 fee 但出区间损失)
v3 ±5% active: 30-100%
v3 delta-neutral: 20-80% (最稳)
十、面试题
Q1: 为什么 V3 LP 总是 short gamma?
A: V3 LP 在区间内持有 token 数量随 P 变化:P↑ 时 token0↓, token1↑;P↓ 反之。这构成"buy low sell high"的反向交易行为。LP 价值函数 V(P) 是 P 的凹函数(二阶导 < 0)→ short gamma。等价于"卖了一份 straddle"——价格大动 LP 都吃亏,价格不动赚 fee(time decay 等价物)。
Q2: 比较 V3 LP delta-neutral 与 CEX 做市的 Sharpe 上限。
A: CEX 做市可达 Sharpe 3-5(顶级 desk)。V3 delta-neutral 通常 Sharpe 1-3。原因:(i) V3 fee tier 离散(最低 1 bps),CEX 可达 0.1-0.5 bps;(ii) V3 hedge 不完美(gamma scalp 成本);(iii) gas cost 占比高;(iv) 链上 latency 让 informed flow 更易 picked-off;(v) 但 V3 capital constraint 较松,可大量部署。
Q3: range 选择 ±5% vs ±10%,trade-off 是什么?
A: ±5%:集中度 ~20x,fee 收入 20x,但 IL 也接近 20x,且更容易 out-of-range(60% time)。±10%:集中度 ~10x,但 in-range 时间 80%。经验法则:选 width = 2 × σ_预期持仓期 × P。例如预计 hold 7 day、σ_7d ≈ 8% → width ≈ ±16%。如果有强 vol forecast 模型,可调小到 ±10%。
Q4: re-balance 触发 "out-of-range" vs "vol-based" 哪个更好?
A: out-of-range trigger:被动响应,等到 IL 已经发生才动;vol-based:可在 vol regime change 前调宽 range,预防 IL。但 vol-based 需要 vol forecast 准(实操难)。生产:组合两者:基线是 time-based daily rebalance(width = vol-based),紧急 trigger 是 out-of-range。
Q5: 如何对 delta-neutral V3 LP 做有效回测?
A: 关键步骤:(i) 用真实 sqrtPrice 历史(subgraph or RPC);(ii) 真实 pool TVL/L 历史(计算 fee share);(iii) 真实 hourly volume;(iv) 真实 perp funding rate;(v) 加入 re-balance gas cost (50 USDC mainnet, 0.5 USDC L2);(vi) 加入 perp basis vs spot;(vii) walk-forward 6 month;(viii) sensitivity to range, hedge threshold, vol estimate。
明日预告
Day 87:永续合约做市 — 转向 derivative markets:dYdX、GMX、Hyperliquid 三大 DEX perp 平台对比。Hyperliquid 是当前最 likely "DeFi version of Binance",订单簿 + 链上结算的混合架构。我们会拉真实 Hyperliquid 数据,看 perp funding rate、open interest、做市机会,并比较与 CEX perp 做市的差异。