halo 的技术博客

返回

你做高频或微观结构研究,第一件事往往是问:这笔交易的真实成本是多少? 要价差,大家本能去拉 L2 订单簿——买卖一档差多少、挂单量多少。但订单簿只在「现在」有,历史上大部分时段的逐笔成交(tape)里,只有成交价序列,没有簿。Roll (1984) 给了一个惊艳的办法:完全不看簿,只从「一段连续的成交价差分」里,把隐含的有效价差算出来。

结论先放这:如果真实价格是随机游走,而每笔成交价 = 真实价 ± 半个价差(买主动加、卖主动减),那么相邻两笔的成交价差分之间,会出现一个负的协方差,而且这个负协方差的大小,正好等于「半个价差的平方」。于是有效(全额)价差可以写成:

s = 2 · √{ −Cov(Δp_t,  Δp_{t-1}) }
plaintext

一句话:Roll 度量的全部信息量就是——成交价序列里「相邻差分负相关」的程度,量化了市场多难成交。负相关越强(买卖来回弹得越狠),隐含价差越大、流动性越差;若差分近似不相关(真实价格剧烈波动盖过了价差弹跳),Roll 反而测不出成本,甚至会偏置。它在我们的合成里表现得很诚实:价差主导时几乎精确(应激段 0.0601 vs 0.0600),噪声主导时被向上偏置(平静段 0.030 vs 0.020),而一旦成交方向带自相关,它就系统性低估真实成本。

Roll 协方差度量:成交价围绕中间价上下弹跳(价差即弹跳幅度)


一、直觉:为什么「买完卖、卖完买」会让差分负相关#

设真实(有效)中间价为 mtm_t,它是个随机游走,没有自相关。每笔成交价 ptp_t 不是落在 mtm_t 上,而是落在「中间价 ± 半个价差」里——买方主动成交时加半价差,卖方主动时减半价差:

p_t = m_t + (s/2)·z_t ,   z_t ∈ {+1(买主动), −1(卖主动)}
plaintext

ptp_t 取一阶差分:

Δp_t = Δm_t + (s/2)·(z_t − z_{t-1})
plaintext

关键看第二项 (ztzt1)(z_t − z_{t-1})。如果这笔和上一笔成交方向相同(都买、或都卖),ztzt1=0z_t − z_{t-1}=0,差分只含真实价变动;如果方向相反(上笔买、这笔下,或反过来),ztzt1=±2z_t − z_{t-1} = ±2,差分被「硬拽」了一下一个半价差的量。

而相邻两期差分做协方差时,Δmt\Delta m_tΔmt1\Delta m_{t-1} 不相关(游走),zz 序列若近似独立,唯一跨期耦合就发生在「方向相反」的那一步——它会让 Δpt\Delta p_tΔpt1\Delta p_{t-1} 反向联动,于是协方差为负,且大小恰好:

−Cov(Δp_t, Δp_{t-1}) ≈ (s/2)²
plaintext

这就是 Roll 公式 s=2Covs = 2·√{−Cov} 的来历。它 elegant 的地方在于:完全不需要知道谁在买、挂单在哪,只要「成交方向在相邻笔间频繁翻转」这个微观结构事实成立,价差就从差分的相关结构里自己浮出来了。


二、从零实现:纯 numpy 复刻滚动 Roll 估计#

我们用纯 numpy 合成一段数据:真实中间价随机游走 + 含价差 Tick 的成交价,再把 Roll 度量滚动跑出来,和「已知真实成本」对照(这是检验一切隐性度量的金标准——你得先有 ground truth,才知道它测没测对)。

两个工程上不能省的点:

  • 非负协方差被丢弃:当窗口内真实价波动盖过了价差弹跳,Cov0\text{Cov} \ge 0,此时 Roll 公式「无定义」(根号里是负数)。实务里直接标 nan 跳过——这步看似无害,其实正是后面「低 SNR 偏置」陷阱的根源(第五节①详拆)。
  • 滚动窗口 K:K 太小→估计噪声大、抖得厉害;K 太大→跟不上价差 regime 变化(比如应激时刻价差瞬间扩大,大窗口会把它「平均掉」)。本文用 K=50 笔做演示。

合成设定:4000 笔成交,中间价波动率对应日频约 0.013,平静段价差 2 tick(=0.02 美元),应激段(第 2000~3000 笔)价差扩到 6 tick(=0.060 美元)。滚动估计结果对照真实成本:

Roll 估计(绿)对照真实成本(灰):价差主导时几乎精确,噪声主导时被向上偏置

区间真实全额价差Roll 估计偏差
平静段(2 tick)0.02000.0299+49.1%
应激段(6 tick)0.06000.0601+0.2%

这张表是全文最重要的诚实结论:Roll 度量不是「无偏金尺」。价差相对噪声越大(应激段),它越准;价差相对噪声越小(平静段、低波动标的),它越偏。为什么?下一节用对照实验把机制拆开。


三、对照实验:成交方向自相关 ρ 越大,Roll 越低估成本#

Roll 公式有个隐藏前提——相邻成交方向近似独立。但真实市场里,买方主动的单子常常是「一连串」(算法扫单、冰山单),卖单也常常成簇,于是 ztz_t 带自相关 ρ>0\rho>0。这会直接腐蚀那个负协方差:当同向单黏在一起,方向相反翻转变少,「价差弹跳」信号被稀释,算出来的 −Cov 偏小,于是 ss低估

我们用一个干净对照(固定真实价差 0.02 美元)验证:ρ 从 0 逐步加大,看 Roll 估计怎么塌。

def roll_estimate(rho, spread_dollars, n=2000, seed=7):
    rng = np.random.default_rng(seed)
    mid = 50.0 * np.exp(np.cumsum(rng.normal(0, 0.0008, n)))
    # 带自相关的成交方向 z_t(AR(1))
    u = np.zeros(n); u[0] = rng.normal()
    for t in range(1, n):
        u[t] = rho * u[t-1] + np.sqrt(1-rho**2) * rng.normal()
    z = np.where(u >= 0, 1, -1)
    obs = mid + spread_dollars/2 * z          # 连续成交价
    dp = np.diff(obs)
    cov = np.mean(dp[1:]*dp[:-1]) - np.mean(dp[1:])*np.mean(dp[:-1])
    return 2*np.sqrt(-cov) if cov < 0 else np.nan
python

跑出来的对照(真实价差恒为 0.02):

真实陷阱:成交方向自相关 ρ↑ → Roll 低估成本(ρ=0 才精确还原)

方向自相关 ρRoll 估计相对真实偏差
0.00.0205+2.6%
0.10.0180−10.0%
0.30.0167−16.7%
0.50.0123−38.4%
0.70.0084−58.2%

结论很硬:ρ=0(独立方向,Roll 假设成立)时几乎精确(+2.6%,就是采样噪声);ρ 每升一档,低估就加深一档,ρ=0.7 时真实成本被砍掉近六成。这意味着——凡是「大单黏性、算法扫单、做市商刷量」明显的市场(加密、小盘股、盘中最活跃时段),Roll 度量会系统性低估你的真实摩擦成本。用它做「交易成本归因」前,务必先估一下成交方向自相关,或者用后面的修正方案。


四、它到底能干嘛:三个落地场景#

  1. 历史 tape 的成本重建:没有 L2 存档的老数据,用 Roll 从成交价序列反推「那段市场大概多贵」——比如比较 2015 股灾前后、或某币上线做市商前后的隐含成本变化。
  2. 流动性监控:滚动 Roll 估计如果突然跳升,往往是价差扩大 / 流动性枯竭的先行信号,比等盘口数据更及时(因为 tape 永远有)。
  3. 策略成本归因:一个高频策略「理论赚 3 bps、实盘只赚 1 bps」,用 Roll 估计出这段的隐含成本,就能把缺口拆成「理论滑点 vs 真实摩擦」,而不是笼统说「执行不好」。

五、诚实拆穿:四类真实陷阱#

① 低 SNR 偏置:非负协方差被丢弃,导致「平静段高估、应激段才准」。 本文平静段 Roll 测出 0.030 而真实仅 0.020(+49%)。机制:窗口里只要真实价波动盖过价差弹跳,Cov 就非负、被丢成 nan;而留下来的「负相关窗口」恰恰是价差信号相对强的少数,对它们求均值,自然把估计往上拽。这解释了为什么「价差越大越准、越小越偏」——它不是 bug,是 estimator 的固有偏置。修正:用 Hasbrouck (2009) 的「把非负协方差也纳入、用全样本二阶矩」的改进版,或改用 Corwin-Schultz(见同专栏)这种基于高低价的度量,对低波动段更稳。

② Tick 取整污染:本文主图用了连续成交价,真实逐笔是离散取整的。 真实世界成交价落在 tick 网格上(0.01 美元一档),当价差只有 1~2 tick 时,取整会让 ztz_t 的 ±半价差被「量化噪声」吃掉,Roll 估计进一步失真。本文为贴合 Roll 的连续假设用了连续价,若换真实逐笔,平静段偏差只会更大。实务:价差 < 3 tick 的标的,Roll 基本不可用,直接上 L2 或 Corwin-Schultz。

③ 趋势 / 漂移市失效。 Roll 假设真实价是「无漂移随机游走」。若中间价有趋势(Δmt\Delta m_t 带符号),趋势项会和价差弹跳项混进协方差,把负协方差「填平」甚至反号,估计直接变 nan 或严重偏小。所以 Roll 只适合「短期近似平稳」的微观窗口,不能拿去测一段有方向的行情。

④ 它不是「执行成本」的全集。 Roll 测的是价差(做市商报价的静态摩擦),不含你的市场冲击(price impact)——你下一笔大单把价格推走的那部分。对机构大单,impact 往往比价差大一个量级。Roll 低估了真实总成本,这一点和「ρ 自相关低估」叠加,意味着实盘摩擦成本永远 ≥ Roll 估计值,用它做容量规划必须留安全垫。


六、落到量化:你的日常工作流#

  1. 先有 ground truth 再谈度量:像本文这样,先用已知价差合成数据验证你的 Roll 实现——很多「Roll 测出来是 0」的 bug,其实是窗口 K 太小、或数据里混了分红拆细导致的假跳。
  2. 永远配合「方向自相关」一起看:跑 Roll 前先算 ρ=Corr(zt,zt1)\rho = \text{Corr}(z_t, z_{t-1}),ρ>0.2 就标记「此段 Roll 低估」,别直接当真值填进成本模型。
  3. 低波动 / 亚 3-tick 标的换工具:用 Corwin-Schultz(基于日高低价)或干脆拉 L2;Roll 在这类场景是装饰品。
  4. 把它当「流动性温度计」而非「成本标尺」:监控滚动 Roll 的跳变比盯绝对值更有用——跳升 = 流动性警报。

真实落地时,逐笔成交价序列(券商 / 交易所 tape)直接喂进 roll_spread 即可;若只有分钟级 OHLC,Roll 基本失效(差分里价差信号被时间聚合冲掉),改用 Corwin-Schultz。本文纯 numpy 合成只为把「差分负相关 → 隐含价差」的每一步摊开。记住:Roll 度量优雅,但它是把「尺子」——只量得准价差、量不准冲击,且偏好高波动高流动性的应激段;平静段和自相关段,它会温柔地骗你。

Roll 协方差度量买卖成本:从逐笔成交价的「负自相关」反推隐含价差
https://blog.halo26812.eu.org/blog/roll-covariance-measure
Author halo
Published at 2026年7月22日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨