8、流动性螺旋的量化指标(二):Roll's Spread估计量
基于协方差的买卖价差估算,适用于高频数据的改进版本
8.1 从交易数据中“反推”价差
上一章我们聊了Amihud非流动性指标,它用价格变化除以成交额,说白了就是“每成交一块钱,价格会动多少”。这个指标很直观,但有个问题——它需要成交量的数据。有些市场、有些品种,成交量数据要么拿不到,要么质量很差。
那有没有办法,只用价格序列本身,就能估算出买卖价差?
有的。这就是Roll's Spread估计量。
我个人习惯把它叫做“协方差法”。它的核心思想很简单:如果市场存在买卖价差,那么价格序列就会表现出一种“来回震荡”的特征——买的时候价格往上跳一下,卖的时候价格往下跳一下。这种震荡会在价格的一阶差分(也就是收益率)中留下负的序列相关性。
嗯,说白了,就是利用“价格来回跳”这个现象,反推出隐含的价差。
8.2 经典Roll模型:数学推导
Roll在1984年提出了这个模型。假设真实价格(有效价格)服从随机游走,但观测到的价格会在买价和卖价之间来回切换:
P_t = P_t* + S/2 * Q_t
其中:
- P_t 是观测价格
- P_t* 是有效价格(随机游走)
- S 是买卖价差(我们要估计的)
- Q_t 是交易方向指示变量(+1表示买方发起,-1表示卖方发起)
关键假设:Q_t 是独立同分布的,且与有效价格变化不相关。也就是说,交易方向是随机的,没有持续的趋势。
在这个假设下,价格变化(收益率)的协方差为:
Cov(ΔP_t, ΔP_{t-1}) = -S²/4
所以,价差的估计量就是:
S_Roll = 2 * √(-Cov(ΔP_t, ΔP_{t-1}))
注意:这里要求协方差为负值。如果算出来是正的,说明模型不适用——可能是趋势太强,或者数据频率不对。
核心公式:
S_Roll = 2 × √( -Cov(r_t, r_{t-1}) )
其中 r_t = P_t - P_{t-1},即收益率序列。
8.3 我在实战中踩过的坑
Roll模型看起来简单,但用起来全是坑。我曾经在A股市场上试过这个指标,结果发现大部分股票的协方差都是正的——算出来的价差是虚数,根本没法用。
为什么会这样?
原因有三:
- 趋势太强:A股日内趋势明显,价格连续上涨或下跌,导致收益率正相关,掩盖了价差引起的负相关。
- 数据频率不对:用日线数据算,噪声太大。Roll模型本质上是高频模型,至少要用分钟级数据。
- 交易方向有持续性:大单拆分、算法交易会导致Q_t不是独立的,破坏了模型假设。
避坑指南:
我曾经用日线数据跑Roll模型,结果80%的股票算不出价差。后来改用5分钟数据,这个比例降到了30%。再后来用了改进版本,才真正稳定下来。
记住:经典Roll模型只适用于高频、无趋势、交易方向随机的情况。现实市场很少满足这些条件。
8.4 改进版本:高频数据下的Roll估计
为了解决上述问题,学术界和业界提出了多种改进方案。我个人最常用的是以下三种:
8.4.1 去趋势Roll估计
先对价格序列做去趋势处理,再计算协方差。常用的去趋势方法包括:
- 一阶差分去均值
- 移动平均去趋势
- HP滤波
代码实现:
def roll_spread_detrend(prices, window=20):
# 移动平均去趋势
ma = prices.rolling(window).mean()
detrended = prices - ma
# 计算收益率
returns = detrended.diff().dropna()
# 计算协方差
cov = returns.autocorr(lag=1) * returns.var()
if cov < 0:
spread = 2 * np.sqrt(-cov)
else:
spread = np.nan
return spread
8.4.2 高频协方差估计(用tick数据)
用逐笔成交数据,计算相邻两笔交易的价格变化协方差。这能最大程度保留高频信息:
def roll_spread_tick(tick_prices):
# tick_prices: 逐笔成交价格序列
delta_p = tick_prices.diff().dropna()
# 计算相邻变化的协方差
cov = (delta_p * delta_p.shift(1)).mean() - delta_p.mean() * delta_p.shift(1).mean()
if cov < 0:
spread = 2 * np.sqrt(-cov)
else:
spread = np.nan
return spread
8.4.3 基于符号的Roll估计
不直接用价格变化,而是用价格变化的符号(+1/-1)来估计。这个方法对异常值更稳健:
def roll_spread_sign(prices):
# 计算价格变化符号
delta_p = prices.diff().dropna()
signs = np.sign(delta_p)
# 计算符号序列的协方差
cov_sign = (signs * signs.shift(1)).mean() - signs.mean() * signs.shift(1).mean()
# 用符号协方差反推价差
if cov_sign < 0:
spread = 2 * np.sqrt(-cov_sign * delta_p.var())
else:
spread = np.nan
return spread
8.5 三种改进版本的对比
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 去趋势Roll | 有趋势的市场(如A股) | 消除趋势干扰 | 窗口参数敏感 |
| 高频协方差 | 有tick数据的市场 | 信息损失最小 | 数据要求高 |
| 基于符号 | 异常值较多的数据 | 稳健性强 | 精度略低 |
8.6 知识体系:Roll估计量的核心逻辑
下面这张图展示了Roll估计量的完整逻辑链条:
8.7 实战建议:什么时候用Roll?
根据我的经验,Roll估计量最适合以下场景:
- 高频数据可用:至少要有分钟级数据,tick数据更好
- 市场相对有效:趋势不要太强,否则去趋势也救不了
- 作为辅助指标:不要单独用Roll,要结合Amihud、买卖价差等一起看
小技巧:
我通常会把Roll估计量和实际买卖价差做对比。如果两者偏差超过20%,说明市场结构可能发生了变化——比如有新的做市商进入,或者交易机制改了。这时候就要警惕流动性风险。
好了,Roll's Spread估计量就讲到这里。下一章我们会聊另一个重要的流动性指标——基于订单簿的价差估计。那个更直接,但数据要求也更高。