最优执行 Almgren-Chriss:把大单拆成最优执行流
上一篇讲了 Almgren-Chriss(AC)怎么把冲击成本和波动风险写进同一个目标,解出一条由 γ 决定的轨迹。本篇换个角度把这套框架用透:①执行期限 T 怎么定——拖久冲击成本低但波动风险高,存在权衡;②最优『交易速率 v*(t)』长什么样,为什么越怕波动越前置;③VWAP 这种成交量加权基准和 AC 最优差在哪;④调度偏一点要多付多少。附完整 Python、连续时间闭式解与六类真实陷阱(高阶)。
上一篇《Almgren-Chriss 最优执行》把核心讲清了:把永久冲击、临时冲击、持仓波动风险写进一个目标函数,解出来不是匀速、也不是一股脑清仓,而是一条由「你多怕波动」决定的轨迹。那篇聚焦 TWAP 对比、Pareto 前沿和蒙特卡洛。
本篇不重复那些,而是把同一套框架往四个工程角度打透——这是实盘落地真正要回答的问题:
- 执行期限 T 该取多少天? 一天卖完和一个月卖完,成本结构天差地别。
- 最优的「交易速率」长什么样? 不是「每步卖多少股」,而是「每秒钟卖多少股」——这才是算法交易真正下单的东西。
- VWAP / TWAP 这种成交量加权基准,和 AC 最优差在哪? 经纪商最爱推 VWAP,但它是最优的吗?
- 调度偏了一点点,要付出多少代价? 你算好的轨迹,真到了盘口不可能精确执行,抖一点会怎样?
一、连续时间闭式解:交易速率 v*(t)#
回到 AC 设定。要在期限 T 内卖出总量 X,分 N 步(N 很大时就是连续时间)。价格受永久冲击 η·v 和临时冲击 τ·v 影响,持仓 x(t) 暴露在市场波动 σ 下。目标是
min_{v(·)} E[cost] + (γ/2)·Var[cost]plaintextAlmgren & Chriss (2001) 在连续时间下给出漂亮闭式解:
x*(t) = X · sinh(κ·(T−t)) / sinh(κ·T) # 库存轨迹
v*(t) = − dx*/dt = X·κ·cosh(κ·(T−t)) / sinh(κ·T) # 卖出速率
κ = sqrt( γ·σ² / (2τ) ) # 由风险厌恶+波动+临时冲击决定plaintextκ 是整条曲线的「形状旋钮」:κ 越大(越怕波动),速率 v*(t) 越前置(开头猛卖、后面收尾);κ→0 时 v*(t)→X/T,就是匀速。注意 x*(T)=0、Σv*·dt=X——保证到期清仓。
import numpy as np
X = 1_000_000.0 # 要卖 100 万股
S0 = 100.0
eta = 8e-8 # 永久冲击系数 ($/股^2)
tau = 5e-7 # 临时冲击系数 ($/股^2)
gamma = 2e-7 # 风险厌恶
N = 50 # 离散步数(连续近似)
daily_vol = 0.02
def ac_optimal(T):
"""连续时间最优速率 v*(t); 返回库存 x[0..N] 与卖出 n[1..N]"""
dt = T / N
sigma = daily_vol * S0 / np.sqrt(T) # 期内日波动
kappa = np.sqrt(gamma * sigma ** 2 / (2 * tau)) + 1e-12
t = np.arange(1, N + 1) * dt
x_mid = X * np.sinh(kappa * (T - t)) / np.sinh(kappa * T)
x = np.concatenate([[X], x_mid]) # 长度 N+1, 末尾≈0
n = x[:-1] - x[1:] # n_k = x_{k-1} - x_k
n = np.clip(n, 0, None)
n = n / n.sum() * X # 严格归一: Σn = X
x = np.concatenate([[X], X - np.cumsum(n)])
return x, n, dt, sigma, kappa
def cost_stats(x, dt, sigma):
n = x[:-1] - x[1:]
E = 0.5 * eta * X ** 2 + tau * np.sum(n ** 2) # 期望冲击成本
Var = sigma ** 2 * dt * np.sum(x[1:] ** 2) # 持仓暴露的方差
return E, Varpython二、执行期限 T 的权衡:拖久≠更便宜#
第一直觉可能是「慢慢卖冲击小,所以 T 越长越好」。错。T 越长,每步卖得越少、临时冲击 τ·Σn_k² 确实下来,但你的仓位在更长时间里暴露在波动下,波动风险 σ²·Σx_j² 反而涨上去。两个力反向,于是 T 和成本之间是权衡不是单调。
Ts = np.array([0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0])
E_T, S_T = [], []
for T in Ts:
x, _, dt, sigma, _ = ac_optimal(T)
E, V = cost_stats(x, dt, sigma)
E_T.append(E / (X * S0) * 1e4) # 期望冲击 (bps)
S_T.append(np.sqrt(V) / (X * S0) * 1e4) # 波动风险 (bps)python
实测(bps):T=0.5 天冲击 5.0、风险 110.7;T=8 天冲击 5.8、风险 71.7。冲击成本是缓缓下降的,但波动风险在 T 极短时极高(仓位久拖不决),存在一个「既不太急也不太久」的平衡点。结论:T 不是越长越好,它和你的风险预算 γ 一起决定——γ 大(怕波动)就别拖,γ 小(不在乎)可以慢慢卖。
三、最优交易速率的形状:越怕波动越前置#
把不同 T 下的最优速率 v*(t) 和库存 x(t) 画出来:
for T, col in [(0.5, "orange"), (2.0, "blue"), (8.0, "green"), (32.0, "purple")]:
x, n, dt, _, _ = ac_optimal(T)
rate = n / dt # 交易速率 (股/步)
# 左图: rate vs 进度; 右图: x vs 进度python
两条规律很清楚:
- 库存轨迹 x(t) 越短越陡:T=0.5 天那条几乎一条直线砸到 0(一天内清仓),T=32 天那条缓坡下行(慢慢磨)。
- 速率 v(t) 全都是「前置型」的*:开头速率最高、结尾趋于 0。这是因为
cosh(κ(T−t))/sinh(κT)在 t 小的时候最大。换句话说,AC 最优天然是「早卖早脱手库存」——它宁可开头吃点临时冲击,也要尽快把暴露在波动里的库存卸掉。
注意:上一篇里 γ→0 退化为匀速 TWAP;本篇的连续解在 γ 很小时同样平滑成匀速,但只要有 γ>0,曲线就一定前置。这是 AC 框架的固有结构,不是调参凑出来的。
四、VWAP 基准 vs AC 最优:谁更优?#
经纪商推的 VWAP(成交量加权平均价)策略,本质是「按市场成交量分布拆单」——开盘收盘成交量大就多卖、午间少卖。这合理吗?和 AC 最优比一比(取 T=2 天):
def intraday_shape(N):
k = np.arange(N)
u = 0.5 - 0.5 * np.cos(2 * np.pi * (k + 0.5) / N) # U 形日内节律
return (0.4 + 0.6 * u) / (0.4 + 0.6 * u).mean()
def vwap_schedule(T):
w = intraday_shape(N); w = w / w.sum()
n = X * w
x = np.concatenate([[X], X - np.cumsum(n)])
return x, n, T / N
x_ac, n_ac, dt_ac, sigma_ac, _ = ac_optimal(2.0)
x_vw, n_vw, dt_vw = vwap_schedule(2.0)python
成本拆解(T=2 天,bps):
| 策略 | 期望冲击 | 波动风险 |
|---|---|---|
| AC 最优 | 5.0 | 103.1 |
| VWAP | 5.1 | 117.9 |
VWAP 的波动风险比 AC 高约 14bps——因为它按成交量而非风险排布,午间成交量低时它还捏着库存,而这些库存正暴露在波动里。AC 最优则纯粹按「风险最小」排布,所以风险更低,冲击成本几乎打平。一句话:VWAP 是「跟着流动性走」的工程启发式,AC 是「跟着风险走」的优化解;当你的目标就是控风险时,AC 严格更优。
(实际中 VWAP 仍有存在价值:它把「市场冲击」和「被对标基准」两件事一起解决了——很多考核就是看你跑赢 VWAP 多少。这是另一层目标,不在 AC 风险框架内。)
五、调度偏一点,代价多少?#
算好的轨迹,盘口不可能精确执行。用单参数家族 n_k ∝ exp(λ·k)(λ=0 匀速,λ>0 前置,λ<0 后置)扫描偏离,看目标函数怎么变:
lams = np.linspace(-2.0, 2.0, 41)
dt = 2.0 / N; sigma = daily_vol * S0 / np.sqrt(2.0)
obj = []
for lam in lams:
w = np.exp(lam * np.arange(N)); n_f = X * w / w.sum()
x_f = np.concatenate([[X], X - np.cumsum(n_f)])
E_f, V_f = cost_stats(x_f, dt, sigma)
obj.append(E_f + 0.5 * gamma * V_f)python
曲线在 AC 最优点(λ≈最优前置斜率)处取到明显的最小值,往任意一侧偏都立刻抬升。这是 AC 框架的「最优性」最直接的证据:你算出的轨迹不是众多可行解里的一个,而是帕累托前沿上唯一的最优点;偏一点就多付一点。 这也解释了为什么生产环境要用执行算法(POV/IS 等)持续把实际成交拉回最优轨迹,而不是算一次就撒手。
六、真实陷阱(别直接照抄)#
-
线性冲击是最大简化。真实冲击非线性:大单冲击会加速恶化(常加
n_k^γ, γ>1)。线性模型系统性低估大单成本,最优轨迹因此「卖得太猛」。生产要用非线性/分段冲击校准。 -
σ 被当成常数且期限均摊。本文
σ = daily_vol·S0/√T假设波动在 T 内平稳,但盘中波动有 U 形(开盘收盘大)、危机骤升。若把波动当常数,优化会在其实很危险的时段还慢悠悠卖。进阶用时变波动或盘口深度动态调轨迹。 -
没有流动性约束。模型假设想卖多少就能卖多少(只是贵)。真实盘口有深度上限,单笔吃穿档位触发更狠冲击甚至挂不进。要加
n_k ≤ 盘口可容量约束。 -
VWAP 不等于 AC 最优,但也不该被神化。VWAP 赢在「对标基准」和「隐藏意图」,AC 赢在「风险最优」。选哪个取决于你的考核目标——若考核是「跑赢 VWAP」,那 AC 最优反而不是最优(它不优化基准差)。两层目标别混为一谈。
-
γ 和冲击参数都要校准且会漂移。η、τ 随标的流动性、市场状态变;γ 随风险预算变。用历史冲击回归定期重估,别拿常数用一年。
-
调度误差不可忽略但可对冲。图 4 证明偏一点就多付,所以生产环境必须有「执行偏差监控 + 闭环修正」,并叠加一个方向判断层(认为要跌就前置更狠)——但那是决策层,别和成本优化双重计数。
七、小结#
Almgren-Chriss 的美,在于它把「执行」从拍脑袋变成一道有闭式解的优化:永久冲击、临时冲击、持仓波动风险写进一个目标,解出来是一条由 κ(=√(γσ²/2τ))决定的交易速率 v*(t)。本文把它往四个工程角度打透:
- T 不是越长越好——冲击↓但波动风险↑,和 γ 一起权衡;
- 最优速率天然前置——早卖早卸库存,这是框架固有结构;
- VWAP 是流动性启发式,AC 是风险最优解——目标不同,别混;
- 最优点是唯一的——偏一点就多付,所以必须闭环执行。
从「怎么选权重」(鲁棒优化)、「怎么把仓位变成交」(本篇)、到「怎么分混沌与秩序」(熵率状态识别)——量化的每一环,都是在给「看不见的市场结构」找一个可计算的替身。