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股市场上试过这个指标,结果发现大部分股票的协方差都是正的——算出来的价差是虚数,根本没法用。

为什么会这样?

原因有三:

  1. 趋势太强:A股日内趋势明显,价格连续上涨或下跌,导致收益率正相关,掩盖了价差引起的负相关。
  2. 数据频率不对:用日线数据算,噪声太大。Roll模型本质上是高频模型,至少要用分钟级数据。
  3. 交易方向有持续性:大单拆分、算法交易会导致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估计量的完整逻辑链条:

Roll's Spread估计量:知识体系 输入:价格序列 P_t (tick/分钟/日) 核心计算 Cov(ΔP_t, ΔP_{t-1}) 输出:价差S S = 2√(-Cov) 三个改进版本 去趋势Roll 先去趋势,再算协方差 适用:有趋势的市场 高频协方差 用tick数据直接计算 适用:有tick数据 基于符号 用价格变化符号估计 适用:异常值较多 应用场景 流动性监测 交易成本估算 市场微观结构 流动性螺旋预警

8.7 实战建议:什么时候用Roll?

根据我的经验,Roll估计量最适合以下场景:

  • 高频数据可用:至少要有分钟级数据,tick数据更好
  • 市场相对有效:趋势不要太强,否则去趋势也救不了
  • 作为辅助指标:不要单独用Roll,要结合Amihud、买卖价差等一起看

小技巧:

我通常会把Roll估计量和实际买卖价差做对比。如果两者偏差超过20%,说明市场结构可能发生了变化——比如有新的做市商进入,或者交易机制改了。这时候就要警惕流动性风险。

好了,Roll's Spread估计量就讲到这里。下一章我们会聊另一个重要的流动性指标——基于订单簿的价差估计。那个更直接,但数据要求也更高。