Gibbs 抽样估计买卖价差:用贝叶斯 MCMC 从收盘价里捞出交易成本
Roll 估计量有个著名死穴:一旦价格变化的序列协方差算出正数,开根号直接失败——低信噪比下这能废掉三成样本。Hasbrouck (2009) 的解法是换武器:把 Roll 模型原封不动搬进贝叶斯框架,用 Gibbs 抽样把「每笔交易是买是卖」这个隐变量和价差参数一起从后验里抽出来。本文从 Roll 模型的状态空间形式推到三步 Gibbs 循环(抽方向 → 抽价差 → 抽波动率),全程只用 numpy 手写采样器。500 笔模拟实测:真实半价差 0.05,后验均值 0.0494,95% 可信区间 [0.048, 0.051];隐藏的交易方向恢复准确率 96.8%;低信噪比场景下 Roll 三成样本算不出数且幸存样本偏高 24%,Gibbs 每次都给出正的估计和完整的不确定性刻画。灵魂在于截断先验:价差被约束为非负,协方差为正的样本不再是灾难而只是「后验更宽」。诚实边界:模型仍是 Roll 的全部假设(方向独立、无逆向选择)、MCMC 收敛需检查、计算量是矩估计的千倍(中阶)。
问题:Roll 估计量的死穴#
用日线数据估买卖价差,教科书答案是 Roll (1984):价格在买价和卖价之间弹跳(bid-ask bounce)会让相邻价格变化呈现负的序列协方差,于是
一行代码,优雅。但用过的人都知道它有个著名死穴:样本协方差经常算出正数。正数开根号,估计直接不存在。
这不是罕见的边角情况。我用 Roll 模型自身生成数据(也就是说模型设定完全正确,没有任何模型误设)做了个实验:半价差 0.01、基本面日波动 0.05——这是低信噪比但完全现实的参数,对应一只价差万分之十、日波动 5% 的股票:
c=0.01, σ_u=0.05: 300 次模拟中 Roll 失败 88 次(29%)
幸存样本的均值 0.0124(向上偏 24%)text两个问题叠加:
- 三成样本直接没有估计值。实务面板回归里这些股票-月份只能扔掉,而被扔掉的恰恰是价差小、噪声大的样本——制造了系统性的样本选择偏差;
- 幸存者偏高。协方差是负得足够多才能活下来,活下来的样本天然高估价差 24%。
死穴的根源不在 Roll 模型本身,而在矩估计这个方法:它把一个本该非负的参数,交给了一个可以随机为负的样本矩。Hasbrouck (2009, Journal of Finance) 的解法是换武器:模型不动,把估计方法换成贝叶斯 MCMC。
Roll 模型的状态空间形式#
先把 Roll 模型写成生成过程。有效价格(真实价值) 是随机游走,成交价 在有效价格上加减半价差:
其中 是交易方向(买方发起 +1,卖方发起 −1),各 50% 概率独立抽取。两式相减消掉不可观测的 :
这是整个方法的枢纽方程。它说:如果你知道每笔交易的方向 ,估价差就是一个小学生级别的线性回归—— 对 回归,斜率就是 。
问题当然是你不知道 。日线数据里没有任何方向信息, 是彻底的隐变量。Roll 的矩估计绕开了它(用协方差把 积分掉),代价就是上面的死穴。Hasbrouck 的思路正相反:不绕开,把 当成未知参数一起估。
Gibbs 抽样:三步循环#
贝叶斯框架下,未知量是 ——两个参数加 个隐变量。联合后验没有解析形式,但 Gibbs 抽样只需要每个条件后验好抽,而这个模型的三个条件后验恰好都是教科书分布:
第 1 步:抽方向 。 固定价差和其他方向, 只出现在两个相邻的价格变化方程里( 和 )。两种取值各算一次似然,归一化成伯努利概率,抽一次硬币:
直觉:如果 比两侧邻居都高出约 ,那它大概率是一笔买单打在卖价上—— 的似然大。
第 2 步:抽价差 。 方向已知时枢纽方程就是线性回归,正态先验 + 正态似然 = 正态后验。关键的一手在这里:先验截断在 。价差不可能为负,这个约束直接写进先验里。这就是 Gibbs 方法根治 Roll 死穴的机制——协方差为正的样本在这里对应「后验质量堆在 0 附近」,估计变得不确定但永远存在、永远非负。
第 3 步:抽波动率 。 残差平方和喂进逆伽马分布,共轭抽样,一行代码。
循环这三步几百次,丢掉前面的 burn-in 段,剩下的抽样就是后验分布的样本。核心实现(完整代码见文末):
def gibbs_spread(p, n_iter=1000, burn=200, rng=None):
T = len(p); dp = np.diff(p)
q = rng.choice([-1, 1], size=T) # 初始化方向
c, sig2 = 0.01, np.var(dp) / 2
c_draws = np.empty(n_iter)
for it in range(n_iter):
# --- 1) 逐笔抽方向:两个相邻 Δp 的似然比 ---
for t in range(T):
ll = np.zeros(2)
for k, qt in enumerate((1.0, -1.0)):
s = 0.0
if t >= 1:
e = dp[t-1] - c * (qt - q[t-1]); s += -0.5*e*e/sig2
if t < T-1:
e = dp[t] - c * (q[t+1] - qt); s += -0.5*e*e/sig2
ll[k] = s
pr = np.exp(ll - ll.max()); pr /= pr.sum()
q[t] = 1 if rng.random() < pr[0] else -1
# --- 2) 抽价差:Δp 对 Δq 回归,截断正态(c >= 0)---
dq = np.diff(q.astype(float))
xx = dq @ dq
if xx > 0:
mu_c, var_c = (dq @ dp) / xx, sig2 / xx
draw = rng.normal(mu_c, np.sqrt(var_c))
while draw < 0: # 拒绝采样实现截断
draw = rng.normal(mu_c, np.sqrt(var_c))
c = draw
# --- 3) 抽波动率:逆伽马共轭 ---
resid = dp - c * dq
a = 2.0 + len(resid)/2
b = 1e-6 + 0.5 * (resid @ resid)
sig2 = b / rng.gamma(a)
c_draws[it] = c
return c_draws[burn:]python实测一:后验贴住真实值#
生成 500 笔 Roll 模型数据(真实半价差 ,),跑 1200 次迭代、丢弃前 200 次 burn-in:

左图是抽样轨迹:从初始值 0.01 出发,不到 50 次迭代就爬升到真实值附近,之后稳定地在 0.05 周围震荡——这是 MCMC 收敛的直观形态。右图是丢掉 burn-in 后的后验分布:
- 后验均值 0.0494,真实值 0.05,偏差不到 1.2%;
- 95% 可信区间 [0.048, 0.051]——这是矩估计给不了的东西:你不仅有点估计,还有完整的不确定性刻画,可以直接回答「价差大于 0.04 的概率是多少」这类问题。
实测二:隐藏方向被恢复了 96.8%#
Gibbs 抽样的副产品可能比价差本身更有意思:每次迭代都抽了一整套交易方向 ,对迭代取平均就得到每笔交易是买单的后验概率。拿它和模拟时的真实方向对账:

左图前 60 笔:背景色块是真实方向,蓝线是后验概率。整段 400 笔的方向判对率 96.8%——记住这是从只有价格、没有任何报价和成交量信息的序列里恢复出来的。右图揭示了它何时有效:方向恢复准确率随信噪比 单调上升,价差是噪声两倍时准确率 97%,价差只有噪声一半时跌到 72%。直觉很朴素:价差越大,bid-ask bounce 的「锯齿」在价格序列里越显眼,方向自然越容易认出来。
这个副产品的实务含义:Gibbs 方法给了你一个不依赖报价数据的成交方向分类器,可以在没有 quote 的历史数据上替代 Lee-Ready 算法做粗粒度的订单流分析。
实测三:和 Roll 的正面对比#
40 次独立模拟、5 档真实价差(0.01 到 0.12),两个估计量同台:

在这个中高信噪比设定下两者都贴住 45° 线,Roll 也全部存活——公平地说,信噪比高的时候 Roll 又快又好。分水岭在低信噪比:把参数换成 (日线数据的现实参数区间),Roll 的失败率飙到 29%、幸存样本偏高 24%,而 Gibbs 在每个样本上都给出正的、带可信区间的估计。Gibbs 的价值不是精度更高,而是在 Roll 交白卷的地方交出完整答卷——对需要全截面覆盖的资产定价研究(比如流动性因子构造),这是决定性的差异。这也是 Hasbrouck (2009) 用它构造 1926 年以来美股有效成本序列的原因:越往历史深处走,数据越粗糙,矩估计死得越多。
三个实务落地#
1. 低频流动性因子的全覆盖构造。 用日线构造价差因子时,Roll 的失败样本不是随机缺失——小价差、高波动的股票死得最多。Gibbs 保证每只股票每个月都有估计值,且可信区间宽度本身就是「估计质量」的度量,可以直接做加权。
2. 历史回测的成本参数。 回测 2005 年的 A 股策略,你找不到当年的逐笔报价,但有日线。Gibbs 后验均值可以作为成本假设的下限锚点(注意它继承 Roll 口径,估的是有效价差的一半),可信区间上界做压力测试。
3. 收敛诊断不能省。 轨迹图(上文左图)必须看:如果抽样在两个区域之间来回跳、或者 burn-in 后仍有趋势,说明链没收敛,加迭代或换初始值。多链起点不同、终点一致(Gelman-Rubin 诊断)是更严格的标准。 笔价格每次迭代要抽 个方向,计算量是 Roll 的千倍量级——500 笔×1200 次迭代在普通笔记本上要几十秒,面板应用需要向量化或并行。
诚实边界#
模型仍然是 Roll 的全部假设。 Gibbs 只是换了估计方法,没有换模型:交易方向 50/50 独立(真实订单流有强自相关,BVC 一文讨论过)、价差恒定、无逆向选择成分(价格冲击全是暂时的)。如果真实数据有永久冲击(知情交易),估出的 会把一部分信息成分错算进价差。Hasbrouck 原文的扩展版本加入了市场因子,实务中应优先用扩展版。
先验不是免费午餐。 截断先验根治了负估计,但当数据几乎无信息时(信噪比极低),后验主要反映先验——你得到的「估计」其实是先验的回声。可信区间会诚实地变宽,但如果只看后验均值就会被误导。
它估的是「有效价差的 Roll 口径」。 和 Effective Tick、Corwin-Schultz 一样,这是低频代理估计量家族的一员,横截面排序能力远好于绝对水平精度。拿它和逐笔数据算出的有效价差直接对数值,会有系统性差异。
完整可复现代码(模拟 + 采样器 + 三张图)约 200 行,核心逻辑已在上文列出。同系列文章:Roll 序列协方差估计量、有效 Tick 估计量、Corwin-Schultz 高低价价差。