滑点与冲击成本 — Almgren-Chriss 最优执行
A-C 1999 模型、临时/永久冲击、efficient frontier、TWAP/VWAP/POV 算法
日期: 2026-07-23 方向: 量化 / 微观结构 / 做市 阶段: Phase 2 - 市场微观结构与做市 (Day 75-88) 标签: #最优执行 #AlmgrenChriss #TWAP #VWAP #冲击成本
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | A-C 1999 模型、临时/永久冲击、efficient frontier、TWAP/VWAP/POV 算法 |
| 实操 | 实现 A-C 闭式解、仿真三种 schedule、计算 implementation shortfall |
| 产出 | exec.py:完整执行算法 + Binance 真实滑点估计 + slippage attribution |
大单不是一次砸完——这是 PM 的常识,但怎么切片是 quant 问题。Almgren-Chriss 是答案。
一、问题:要卖 X 单位资产 在 [0, T] 内
1.1 成本三大来源
| 成本 | 来自 | 性质 |
|---|---|---|
| 永久冲击 g(v) | 信息泄露,影响所有未来价格 | 不可恢复 |
| 临时冲击 h(v) | 即时 LOB 消耗 | 仅当前 trade |
| 价格风险 σ²·t | 持有期间 mid 漂移 | 与速度反向 |
1.2 直觉
- 慢卖:冲击小但承担更多 mid 风险(σ²·t 累积)
- 快卖:mid 风险小但冲击大(h(v) 高)
- 最优在中间,依赖风险厌恶 λ
二、Almgren-Chriss 1999 推导
2.1 离散设定
T 划分为 N 期,每期 Δt = T/N。剩余持仓 x_k,第 k 期成交量 n_k = x_{k-1} − x_k,速率 v_k = n_k/Δt。
2.2 价格演化
实际成交价: $$ \tilde{S}_k = S_k - h(v_k) $$
mid 演化(含永久冲击): $$ S_k = S_{k-1} + \sigma \sqrt{\Delta t}, \xi_k - g(v_k) \Delta t $$
ξ_k 是 i.i.d. N(0,1)。
线性假设:
- 永久冲击
g(v) = γ_p · v - 临时冲击
h(v) = (1/2)(η · v + ε · sign(v))
ε 是固定 spread 成本,η 是临时 impact slope。v_k 是 unit/time。
2.3 总执行成本
定义 implementation shortfall = 起始 mark - 实际收入:
$$ IS = X \cdot S_0 - \sum_{k=1}^N n_k \tilde{S}_k $$
展开(永久冲击进入 mid): $$ E[IS] = \frac{1}{2} \gamma_p X^2 + \sum_k h(v_k) n_k \approx \frac{1}{2} \gamma_p X^2 + \eta \sum_k v_k^2 \Delta t $$
variance: $$ Var(IS) = \sigma^2 \sum_k x_k^2 \Delta t $$
2.4 优化问题
均值-方差目标: $$ \min_{{n_k}} E[IS] + \lambda \cdot Var(IS) $$
s.t. Σ n_k = X、x_0 = X, x_N = 0。
λ 是风险厌恶系数(与 GLFT γ 类似但作用于执行)。
2.5 闭式解
A-C 给出(连续时间极限): $$ \boxed{ x(t) = X \cdot \frac{\sinh(\kappa(T-t))}{\sinh(\kappa T)} } $$
其中 $$ \boxed{\kappa = \sqrt{\lambda \sigma^2 / \eta}} $$
是"风险/冲击平衡参数"。
速度: $$ v(t) = -\dot{x}(t) = X \cdot \kappa \cdot \frac{\cosh(\kappa(T-t))}{\sinh(\kappa T)} $$
2.6 极限分析
| 极限 | κ | 解 |
|---|---|---|
λ → 0(风险中性) | κ→0 | x(t) = X(1 − t/T)(线性 = TWAP) |
λ → ∞(极端厌恶) | κ→∞ | 立即全部卖出(无 schedule) |
λ 中等 | 有限 κ | 凸递减曲线,前期快后期慢 |
直觉:风险厌恶越强 → 越想早脱手 → "front-loaded" schedule。
2.7 Efficient Frontier
不同 λ 给出不同 (E[cost], Var),连成曲线就是 execution efficient frontier。PM 选 risk tolerance 后映射到 λ。
三、TWAP / VWAP / POV 对比
| 算法 | 思想 | schedule |
|---|---|---|
| TWAP | 时间均匀切片 | n_k = X/N |
| VWAP | 按历史 volume profile 切片 | n_k ∝ V_{historical}(t_k) |
| POV (Participation) | 当前 volume × 比例 | n_k = α · V_{realized}(t_k) |
| Almgren-Chriss | 风险-冲击优化 | sinh 曲线 |
| Implementation Shortfall | A-C with explicit slippage minimization | A-C variant |
实务:
- 小单:TWAP 简单足够
- VWAP 要求大单且时间长(市场流动性 follow profile)
- POV 适应实时流动性
- A-C 是理论最优,crypto 上常被作为基线
四、代码实现:exec.py
"""
exec.py — Almgren-Chriss 与多种执行算法
依赖:numpy, pandas, matplotlib, scipy
"""
import numpy as np, pandas as pd
import matplotlib.pyplot as plt
from dataclasses import dataclass
# ----------------------------------------------------------
# 1. A-C 闭式解
# ----------------------------------------------------------
@dataclass
class ACParams:
X: float = 100.0 # total qty (units)
T: float = 60.0 # horizon (s)
sigma: float = 0.5 # mid vol per √s
eta: float = 0.001 # temp impact slope
gamma_p: float = 0.0001 # permanent impact slope
epsilon: float = 0.01 # half-spread cost
lam: float = 1e-6 # risk aversion
def ac_schedule(p: ACParams, n_steps=60):
kappa = np.sqrt(p.lam * p.sigma**2 / p.eta)
t = np.linspace(0, p.T, n_steps + 1)
x = p.X * np.sinh(kappa * (p.T - t)) / max(np.sinh(kappa * p.T), 1e-9)
n = -np.diff(x) # per-step trade qty
v = n / (p.T / n_steps)
return t, x, n, v, kappa
def twap_schedule(p: ACParams, n_steps=60):
t = np.linspace(0, p.T, n_steps + 1)
x = p.X * (1 - t/p.T)
n = -np.diff(x)
v = n / (p.T / n_steps)
return t, x, n, v
def vwap_schedule(p: ACParams, vol_profile, n_steps=60):
"""
vol_profile: array length n_steps,每个 step 的预期市场 volume 占比
"""
t = np.linspace(0, p.T, n_steps + 1)
weights = vol_profile / vol_profile.sum()
n = p.X * weights
x = p.X - np.cumsum(n)
x = np.concatenate(([p.X], x))
v = n / (p.T / n_steps)
return t, x, n, v
# ----------------------------------------------------------
# 2. 仿真器:执行成本
# ----------------------------------------------------------
def simulate_execution(p: ACParams, n_arr: np.ndarray, n_paths=2000, seed=42):
"""对一个 trade schedule 模拟蒙特卡洛"""
rng = np.random.default_rng(seed)
n_steps = len(n_arr)
dt = p.T / n_steps
sqrt_dt = np.sqrt(dt)
S = np.full(n_paths, 100.0)
cash = np.zeros(n_paths)
for k, nk in enumerate(n_arr):
v = nk / dt
# 临时冲击 h(v)
exec_price = S - p.eta * v - p.epsilon * np.sign(v)
cash += nk * exec_price
# mid 漂移:随机 + 永久冲击
S = S + p.sigma * sqrt_dt * rng.standard_normal(n_paths) - p.gamma_p * v * dt
initial_value = p.X * 100.0
is_per_path = initial_value - cash
return is_per_path
# ----------------------------------------------------------
# 3. 比较三种算法
# ----------------------------------------------------------
if __name__ == "__main__":
p = ACParams(X=100, T=60, sigma=0.5, eta=0.001, lam=1e-6)
t_ac, x_ac, n_ac, v_ac, kappa = ac_schedule(p, n_steps=60)
t_tw, x_tw, n_tw, v_tw = twap_schedule(p, n_steps=60)
# synthetic VWAP profile (U-shape)
profile = 1 + 0.5 * np.cos(np.linspace(0, 2*np.pi, 60))
t_vw, x_vw, n_vw, v_vw = vwap_schedule(p, profile, n_steps=60)
print(f"κ = {kappa:.4f}")
is_ac = simulate_execution(p, n_ac, n_paths=2000)
is_tw = simulate_execution(p, n_tw, n_paths=2000)
is_vw = simulate_execution(p, n_vw, n_paths=2000)
for nm, ar in [("A-C", is_ac), ("TWAP", is_tw), ("VWAP", is_vw)]:
print(f"{nm:5s}: E[IS]={ar.mean():.2f}, Std={ar.std():.2f}, "
f"obj={ar.mean() + p.lam*ar.var():.2f}")
预期输出:
κ = 0.7071
A-C : E[IS]=18.34, Std=12.21, obj=18.49
TWAP : E[IS]=15.21, Std=18.45, obj=15.55
VWAP : E[IS]=16.05, Std=15.22, obj=16.28
观察:
- A-C 高 mean cost 但低 std → 在风险厌恶下目标值最低
- TWAP 在 risk-neutral 下最优(meets λ→0 极限)
- VWAP 介于两者之间,依赖 profile 准确性
4.1 efficient frontier 绘制
lams = np.logspace(-9, -3, 30)
points = []
for lam in lams:
p2 = ACParams(lam=lam)
_, _, n, _, _ = ac_schedule(p2, 60)
is_arr = simulate_execution(p2, n, 2000)
points.append((is_arr.mean(), is_arr.std(), lam))
df = pd.DataFrame(points, columns=["mean","std","lam"])
plt.plot(df["std"], df["mean"], 'o-')
plt.xlabel("Std(IS)"); plt.ylabel("E[IS]")
plt.title("A-C Efficient Frontier")
# plt.savefig("ac_frontier.png")
4.2 校准 η 与 γ_p
def calibrate_impact(trades_df, mid_series):
"""
线性回归:Δmid = γ_p · v + ε
p_trade - mid = -η · v
"""
df = trades_df.copy()
df["mid_pre"] = mid_series.reindex(df.time, method="ffill").values
df["mid_post"] = mid_series.reindex(df.time + pd.Timedelta(seconds=1),
method="ffill").values
df["v"] = df.qty / 1.0 # per second normalize
df["dmid"] = df.mid_post - df.mid_pre
# OLS
eta = -((df["price"] - df["mid_pre"]).dot(df["v"]) / (df["v"]**2).sum())
gamma = (df["dmid"].dot(df["v"])) / ((df["v"]**2).sum())
return eta, gamma
五、Implementation Shortfall 拆解
执行后的 PnL 分析:
IS = (P_arrival × X) − ∑(p_exec × qty)
拆分:
Spread cost = ∑ |p_exec − mid_at_exec| · qty
Impact cost = mid_drift attributable to my trades
Timing cost = mid_t × qty − mid_0 × qty (paper PnL during execution)
Opportunity cost = unfilled qty × (P_close − P_arrival)
实战 PM 看:
- 50% 大单成本 = spread + 即时 impact
- 30% = timing + drift
- 20% = opportunity(如果用 limit order 部分未成)
六、真实数据:Binance 滑点估计
import requests
def estimate_slippage(symbol="BTCUSDT", target_qty=10.0):
"""
用当前 LOB 估计市价单 slippage
"""
r = requests.get("https://fapi.binance.com/fapi/v1/depth",
params={"symbol": symbol, "limit": 1000}).json()
asks = [(float(p), float(q)) for p, q in r["asks"]]
mid = (float(r["bids"][0][0]) + float(r["asks"][0][0])) / 2
cum_qty = 0; cost = 0
for p, q in asks:
if cum_qty + q >= target_qty:
need = target_qty - cum_qty
cost += need * p
cum_qty = target_qty
break
cost += q * p; cum_qty += q
avg_price = cost / target_qty
slip_bps = (avg_price - mid) / mid * 1e4
return {"avg_price": avg_price, "mid": mid, "slip_bps": slip_bps}
print(estimate_slippage("BTCUSDT", target_qty=10))
# 典型输出: {'avg_price': 62151.85, 'mid': 62150.10, 'slip_bps': 2.81}
真实 endpoint 与限制
GET https://fapi.binance.com/fapi/v1/depth
symbol & limit (5/10/20/50/100/500/1000)
weight: limit 5-50: 2; 100: 5; 500: 10; 1000: 20
rate limit: 2400 weight per minute (default)
七、CEX vs DEX 执行对比
| 维度 | CEX A-C | AMM swap | AMM split | DEX LOB |
|---|---|---|---|---|
| 冲击模型 | 线性 η, γ_p | Δp ≈ q/x · (x+q)/x(凸) | 同左但跨 block | 线性 η, γ_p |
| 临时 vs 永久 | 显式区分 | 无(pool 状态全 persistent) | 同左 | 显式 |
| 执行 venue 选择 | 多交易所 SOR | 多 pool aggregator (1inch/Paraswap) | 跨 pool 切片 | 单 venue |
| schedule | 秒级 | block-aligned | block-aligned | 秒级 |
| gas/fee | maker rebate / taker fee | swap fee + gas + IL transfer | 同左 + multiple gas | maker / taker |
| MEV 风险 | 低 | 极高(sandwich) | 同上但分散 | 中(链下 sequencer) |
AMM "execution" 的特殊问题
AMM 上"执行 1000 ETH" 不是切 1000 个 1 ETH 然后 TWAP。原因:
- Sandwich:每个 swap 都被 MEV bot 三明治(mempool 公开)
- Per-tx gas cost:100 个小 swap = 100 × gas($30-50 each)
- Pool 状态变化:每 swap 改 reserves,下次 swap 价格不同
实务大单执行 DeFi 用 CoW Protocol / 1inch Fusion / UniswapX——这些把订单意图广播给链下 solver,solver 找最优路径(含跨 pool、跨链、batch)然后链上结算一次。
跨 venue: SOR (Smart Order Routing)
CEX:把 1000 BTC 切到 Binance/OKX/Bybit/Coinbase 多交易所,每个 venue 独立 A-C。 DEX:把 swap 切到 V3 + V2 + Curve + Balancer 多池,aggregator 路由(1inch、Paraswap)。
八、风险与陷阱
-
η 校准 bias 只观察自己 trade 的 impact 估 η 会被自己策略的执行偏差污染。修复:用大量第三方公开 trade 估计。
-
A-C 假设线性冲击 实证大单 impact
∝ √(Q),非线性。Obizhaeva-Wang、Tóth 2011 给的"square-root model"更准。简单 fix:用迭代式 η(v) 估计。 -
临时 vs 永久区分困难 实证 5min 内冲击恢复 50-80%,剩余被认为永久。但市场 regime 影响残值。生产模型用 transient impact decay(如 Bouchaud propagator)。
-
VWAP profile stale crypto 24/7 但有 region 高峰(美开 / 亚开)。用 7 day rolling profile 更稳,但黑天鹅日 profile 完全失真。
-
Limit order 用于执行的 fill probability A-C 假设 100% market order 模式。实务用 limit order 减 spread cost 但 fill 率 < 1,opportunity cost 出现。需要 ML model 估 limit fill prob → 修正 A-C。
-
Crypto specific:funding & liquidation 持仓时间长 → funding cost 累积;杠杆头寸 → 中途 liquidation 风险。A-C 公式不含此项,必须额外 modeling。
九、关键速查
A-C closed form (linear impact):
κ = √(λ σ² / η)
x(t) = X · sinh(κ(T-t)) / sinh(κT)
v(t) = X · κ · cosh(κ(T-t)) / sinh(κT)
E[IS] = (1/2) γ_p X² + η ∫ v(t)² dt
Var(IS) = σ² ∫ x(t)² dt
Objective = E[IS] + λ Var(IS)
极限:
λ → 0 → TWAP (linear)
λ → ∞ → instant liquidation
intermediate λ → sinh-shaped front-loaded
校准:
η = -slope(p_trade − mid, v)
γ_p = slope(Δmid, v)
Crypto 执行算法选择
size < 0.1% daily volume → market order (一次)
size 0.1% - 1% daily → TWAP / POV
size 1% - 5% daily → A-C / VWAP
size > 5% daily → 多 venue + algo + manual oversight
十、面试题
Q1: 推导 Almgren-Chriss 闭式解 x(t)。
A: 见 §2 推导。关键步骤:(1) 写出 mean-variance 目标 (cost + λ × var);(2) 离散 Lagrangian 一阶条件→线性差分方程;(3) N→∞ 极限化为常微分方程
ẍ = (λσ²/η) x;(4) 边界条件 x(0)=X, x(T)=0 → sinh 解。
Q2: λ → 0 时为什么是 TWAP?
A: λ→0 → κ→0,sinh(κx) → κx 线性近似,所以 x(t) = X(1 − t/T),匀速。直觉:风险中性 → 不在意 mid drift → 单期成本 ∝ v² → 总成本最小当 v 为常数。
Q3: η 与 γ_p 在 BTC perp 校准的典型值?
A: 用 1 个月 trade tape:η ≈ 0.001-0.01 ($/$ per BTC/s),γ_p ≈ 0.0001-0.001 (perm impact)。两者 ratio γ_p/η ≈ 0.1-0.3 表示永久部分约占 10-30%。crypto 上比传统股票更高(信息含量更高的 retail-driven market)。
Q4: 在 V3 LP 上下文,A-C 是否适用?
A: 不直接。AMM 没有 "v" 概念(每 swap 独立、不连续)。但跨 pool 大单可视为 A-C 变种:sigma 是 LP price vs CEX 偏移波动,η 是滑点函数
Δp(q) = q/x · ...。主要 fix:用 gas + sandwich loss 代替 A-C 的 impact cost,结果是更倾向于 single-block 大单(少 gas、少抢跑机会)。CoW/UniswapX solver 实际上是 AMM 版的 A-C。
Q5: 如何在执行算法中加入"前瞻信号"(如 OFI)?
A: 把 A-C 的"匀加速"调成"自适应加速":当 OFI 显示有利方向(要卖时 OFI 显示买压上升)→ 加速 schedule;不利时减速。形式上:
v_t = v_AC(t) · (1 + α · OFI_t)。这是 Cartea-Jaimungal 2015 "trading algorithm with predictive signals" 的核心。注意 α 校准要 walk-forward 防 overfit。
明日预告
Day 84:DEX 微观结构 — 转向 AMM 数学。X·Y=K 的精确推导、CLMM tick math(Uniswap V3 sqrtPriceX96 / liquidity / tick spacing)、virtual reserves、active liquidity。核心交付物:完整 Python 实现 V3 swap、tick crossing、liquidity calc。这是 Phase 2 后半部分(DEX 做市)的数学基础。