价格冲击衰减:用成交后的价格回复曲线拆临时与永久冲击
一笔买单把价格推高 10bp,这 10bp 里有多少是市场真的学到了东西、有多少只是流动性暂时让步?答案藏在成交之后的价格路径里:永久冲击是回不来的台阶,临时冲击是会指数衰减的鼓包。本文用 κ + θ·exp(−t/τ) 的经典衰减模型模拟 2000 次买单事件,对平均回复曲线做非线性最小二乘,把三个参数几乎无偏地拿回来(κ̂≈4.0bp、θ̂≈6.0bp、τ̂≈5 bar)。核心洞见是执行含义:拆单、放慢只能省临时冲击那一层,永久冲击是信息的价格、怎么拆都省不掉——这条分界线就是所有执行算法成本模型的地基。诚实边界:事件平均要求冲击对订单流平稳、拆解对模型形式敏感、日线数据上 τ 几乎不可识别(中阶)。
问题:成交瞬间的 10bp,是真的贵还是暂时的贵#
你用市价单买入一只股票,成交价比下单前的中间价高了 10bp。这 10bp 是什么?
有两种完全不同的可能:
- 市场学到了东西。你的买单让别人推断”有人知道点什么”,报价整体上移,价格再也回不来。这 10bp 是你为暴露信息付的钱。
- 流动性暂时让步。你吃掉了卖一卖二的挂单,价格被暂时顶高,几分钟后新的卖单补进来,价格弹回去。这 10bp 里的大部分你其实没有真的付出——如果你不着急,等一等再买,价格会回来。
前面几篇里,Stoll 分解和 MRR 模型用成交价与报价的串行协方差在逐笔层面上拆这两种成分。这一篇换一个更直观、也更通用的视角:直接看成交之后价格怎么走。
逻辑很朴素。永久冲击是回不来的台阶,临时冲击是会消散的鼓包。把足够多同方向成交事件的后续价格路径对齐、平均,噪声抵消之后,剩下的那条曲线就是冲击的解剖图:
- :永久冲击——曲线最终收敛到的水平;
- :临时冲击的初始幅度——成交瞬间超出永久水平的部分;
- :衰减时间常数——临时冲击缩到 37% 所需的时间。
成交瞬间你付的总冲击是 ,但长期留在价格里的只有 。这个指数衰减形式最早可以追溯到 Hasbrouck (1991) 的脉冲响应框架,后来成为 Almgren-Chriss 一类执行成本模型里”临时冲击 + 永久冲击”二分法的经验基础。
机制:为什么临时冲击会衰减#
临时冲击的衰减不是玄学,背后是限价簿的供给恢复过程。
你的市价买单吃掉了卖一、卖二的挂单,最优卖价被顶高——这是机械的、簿本层面的位移。接下来发生的事情决定价格回不回来:
- 如果市场认为你的单子不含信息(比如像是指数基金的被动调仓),做市商和其他流动性提供者会把新的卖单挂回原来的价位附近。挂单补充需要时间,所以价格是逐渐回落而不是瞬间回落——这就是 的来源, 度量的本质是流动性供给的恢复速度。
- 如果市场认为你的单子含信息,没人愿意把卖单挂回原价——在更高的位置重新挂单才是理性的。这部分位移就成了永久冲击 。
所以 这个比例——永久冲击占总冲击的份额——是市场对”这类订单流的信息含量”的定价,和 MRR 里的 θ、Stoll 里的逆向选择成分是同一件事的不同测量。三条路殊途同归,这正是微观结构理论令人放心的地方。
模拟:2000 次买单事件的平均回复曲线#
真实数据里做这件事需要逐笔成交打好方向标签(又是 Lee-Ready 的老话题)。这里用模拟把机制讲干净:设定真实参数 κ=4bp、θ=6bp、τ=5 个 bar,在每次买单事件之后叠加市场噪声,看事件平均能不能把参数捞回来。
import numpy as np
from scipy.optimize import curve_fit
rng = np.random.default_rng(42)
# ---- 真实参数 ----
kappa, theta, tau = 4.0, 6.0, 5.0 # bp, bp, bar
H = 30 # 事件后观察 30 个 bar
t = np.arange(H + 1)
# 理论平均路径:成交瞬间 κ+θ,之后指数衰减到 κ
mean_path = kappa + theta * np.exp(-t / tau)
# ---- 模拟 2000 次买单事件 ----
n_events = 2000
# 每条路径 = 理论路径 + 随机游走噪声 + 观测噪声
drift_noise = rng.normal(0, 8.0, (n_events, H + 1)).cumsum(axis=1) * 0.15
obs_noise = rng.normal(0, 3.0, (n_events, H + 1))
paths = mean_path[None, :] + drift_noise + obs_noise
# ---- 事件平均 ----
emp = paths.mean(axis=0) # 平均回复曲线
se = paths.std(axis=0) / np.sqrt(n_events)
# ---- 非线性最小二乘拟合 ----
def impact_model(t, k, th, ta):
return k + th * np.exp(-t / ta)
popt, pcov = curve_fit(impact_model, t, emp, p0=[2, 5, 3])
k_hat, th_hat, ta_hat = popt
print(f"κ̂ = {k_hat:.2f}bp θ̂ = {th_hat:.2f}bp τ̂ = {ta_hat:.2f} bar")
# κ̂ ≈ 4.0 θ̂ ≈ 6.0 τ̂ ≈ 5.0python
平均曲线的形状和教科书一样:成交瞬间冲到 10bp 附近,然后单调回落,大约 15-20 个 bar 之后稳定在 4bp 的平台上。±2 标准误的带子说明 2000 个事件的平均已经把单事件层面的巨大噪声压得足够低——单看一笔成交的后续路径几乎什么都看不出来,事件研究的全部力量来自平均。
两层结构画开看更清楚:

红色地板是永久成分——信息的价格,时间抹不掉;蓝色鼓包是临时成分——流动性的暂时让步,τ=5 个 bar 意味着 5 个 bar 后只剩 37%,15 个 bar 后基本归零。
拟合结果几乎贴住真实值:

κ̂≈4.0、θ̂≈6.0、τ̂≈5.0,三个参数全部无偏拿回。当然这是模拟的仁慈——真实数据里事件之间会重叠、订单流有自相关、冲击对订单规模非线性,曲线不会这么干净。但”对齐-平均-拟合”这个流程本身是完全可移植的。
执行含义:只有临时冲击能省#
这个拆解不是学术洁癖,它直接决定执行算法怎么设计。
关键不对称在于:临时冲击可以靠放慢执行省下来,永久冲击不能。
- 把大单拆成小单、拉开时间距离(间隔超过 τ),每个子单只承受自己那一份临时冲击,不会叠加——拆得越碎,临时成本越低;
- 但永久冲击是市场对你订单流总量的信息定价。只要你最终买了那么多,信息就泄露了那么多,拆单改变不了总量,也就改变不了永久成本;
- 而放慢执行有代价:执行期间价格自己会动,时间风险随执行时长上升。
三条曲线放在一起,就是 Almgren-Chriss 框架的直觉版本:

临时成本随拆单数下降、时间风险随拆单数上升、永久成本是条救不动的水平线——总成本在中间某处取最小。最优执行速度的本质就是在临时冲击和时间风险之间找平衡点,而 τ 直接决定拆单的最小有效间隔:子单间隔远小于 τ,前一单的临时冲击还没消散,后一单就在被自己抬高的价格上成交,拆单等于白拆。
反过来,这个框架也给了一个实用的诊断工具:如果你的 TCA 显示成交后价格持续朝不利方向走、从不回复(θ≈0),说明你的订单流被市场读出了信息——问题不在执行速度,在信息泄露,换算法不如换下单模式。
已知边界#
诚实说清三件事:
- 事件平均要求冲击机制平稳。把三个月的成交事件平均在一起,隐含假设这三个月里 κ、θ、τ 没变。但冲击参数在财报日、高波动期会系统性变化,混在一起平均出来的是个模糊的加权平均。严格的做法是分状态估计,代价是每个状态内的样本变少。
- 拆解对模型形式敏感。单指数衰减是最简约的形式,但实证文献里冲击衰减常常更像幂律(衰减先快后极慢)。用指数模型去拟合幂律衰减的数据,κ 会被系统性高估——一部分衰得很慢的临时冲击被误记成永久冲击。换个模型形式,κ/θ 的分账比例就会动,报告拆解结果时必须同时报告模型假设。
- 数据频率决定 τ 的可识别性。τ 通常在秒到分钟量级,用日线数据估计冲击衰减,临时成分在一个采样间隔内就衰完了,你看到的全是”永久”冲击——不是市场没有临时冲击,是你的尺子量不到。这也是为什么 Amihud 这类日线冲击度量天然偏向永久成分。
小结#
价格冲击衰减模型把一笔成交的成本切成两层:回不来的台阶(κ,信息的价格)和会消散的鼓包(θ·exp(−t/τ),流动性的暂时让步)。测量方法是把大量同向成交事件的后续价格路径对齐、平均、拟合——单事件全是噪声,平均之后曲线自己浮出来。2000 次事件的模拟里三个参数都能无偏恢复。它最重要的输出不是参数本身,而是那条执行分界线:拆单和放慢只能省临时那一层,永久那一层是信息的价格,怎么拆都省不掉。下一步自然的问题是:临时冲击的恢复速度 τ 本身也是流动性的一个维度——盘口的弹性,这正是报价斜率度量要回答的。