返回 Expert 笔记
Expert Day 83

滑点与冲击成本 — Almgren-Chriss 最优执行

A-C 1999 模型、临时/永久冲击、efficient frontier、TWAP/VWAP/POV 算法

2026-07-23
Phase 2 - 市场微观结构与做市 (Day 75-88)
最优执行AlmgrenChrissTWAPVWAP冲击成本

日期: 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 = Xx_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(风险中性)κ→0x(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 ShortfallA-C with explicit slippage minimizationA-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-CAMM swapAMM splitDEX LOB
冲击模型线性 η, γ_pΔp ≈ q/x · (x+q)/x(凸)同左但跨 block线性 η, γ_p
临时 vs 永久显式区分无(pool 状态全 persistent)同左显式
执行 venue 选择多交易所 SOR多 pool aggregator (1inch/Paraswap)跨 pool 切片单 venue
schedule秒级block-alignedblock-aligned秒级
gas/feemaker rebate / taker feeswap fee + gas + IL transfer同左 + multiple gasmaker / taker
MEV 风险极高(sandwich)同上但分散中(链下 sequencer)

AMM "execution" 的特殊问题

AMM 上"执行 1000 ETH" 不是切 1000 个 1 ETH 然后 TWAP。原因:

  1. Sandwich:每个 swap 都被 MEV bot 三明治(mempool 公开)
  2. Per-tx gas cost:100 个小 swap = 100 × gas($30-50 each)
  3. 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)。


八、风险与陷阱

  1. η 校准 bias 只观察自己 trade 的 impact 估 η 会被自己策略的执行偏差污染。修复:用大量第三方公开 trade 估计。

  2. A-C 假设线性冲击 实证大单 impact ∝ √(Q),非线性。Obizhaeva-Wang、Tóth 2011 给的"square-root model"更准。简单 fix:用迭代式 η(v) 估计。

  3. 临时 vs 永久区分困难 实证 5min 内冲击恢复 50-80%,剩余被认为永久。但市场 regime 影响残值。生产模型用 transient impact decay(如 Bouchaud propagator)。

  4. VWAP profile stale crypto 24/7 但有 region 高峰(美开 / 亚开)。用 7 day rolling profile 更稳,但黑天鹅日 profile 完全失真。

  5. Limit order 用于执行的 fill probability A-C 假设 100% market order 模式。实务用 limit order 减 spread cost 但 fill 率 < 1,opportunity cost 出现。需要 ML model 估 limit fill prob → 修正 A-C。

  6. 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 做市)的数学基础。