Stoll 已实现价差分解:把买卖价差拆成信息、库存与订单处理成本
报价价差 10 个 tick,做市商真能赚到 10 个 tick 吗?Stoll (1989) 给出了否定答案和一套识别方法:价差由三种成本构成——逆向选择(信息)、库存持有、订单处理,三者在成交后的价格行为上留下完全不同的指纹。信息成分让价格一去不回,库存成分让报价先偏移再均值回复,订单处理成分产生纯粹的 bid-ask bounce。本文推导「成交价串行协方差 + 报价中点串行协方差」这对识别工具,用可复现 Python 模拟验证:θ=0.45 的市场里,报价价差 0.10 元中做市商真正落袋的已实现价差只有 0.06 元。并给出三个落地应用:横截面毒性排序、TCA 里有效价差与已实现价差的正确用法、以及为什么高换手做市策略要盯已实现价差而不是报价价差(中阶)。
报价价差是纸面富贵#
一只股票买一 99.95、卖一 100.05,报价价差 0.10 元。天真的算术:做市商一买一卖,每轮赚 0.10 元。
现实是赚不到。你在 100.05 卖给对手之后,如果这笔买单是知情交易——价格接着涨到 100.20,你要么高价补回库存,要么干挂着浮亏。成交后的价格移动会吃掉一部分甚至全部价差。
那么问题来了:报价价差里,多少是真能落袋的补偿,多少是要还给市场的「过路费」?Stoll (1989) 的回答成为交易成本分析(TCA)的基石:把价差拆成三种经济成本,每种成本在成交后的价格行为上留下不同的指纹,因此可以从数据里识别出来。
三种成本,三种指纹#
1. 订单处理成本(order processing):交易所费用、终端、人力——做市的固定开销。这部分是纯粹的服务费,与信息无关。指纹:成交价在 bid 和 ask 之间来回弹跳(bid-ask bounce),价格变化呈强负串行相关,但报价中点纹丝不动。
2. 库存持有成本(inventory holding):接了单边货之后承担的价格风险(Ho-Stoll 模型 的主题)。做市商的应对是偏移报价引导库存回归。指纹:报价中点先随成交方向移动、随后均值回复——买单把整套报价推高,但过一会儿会落回来,因为移动不是信息,只是库存压力。
3. 逆向选择成本(adverse selection):对手可能知道你不知道的事(Glosten-Milgrom 的主题)。指纹:报价中点随成交方向永久移动、不回复——买单之后中点上台阶再也不下来,因为做市商从订单流里学到了真信息。
Stoll 的核心识别思想:成交价的串行协方差和报价中点的串行协方差携带不同信息。
- 成交价变化的负自协方差 = bounce(订单处理)+ 库存回复,两种暂时成分都会造成反转;
- 报价中点变化的负自协方差 = 只有库存回复(订单处理不动中点,信息成分不回头);
- 两者之差,识别出订单处理成分;中点协方差识别库存成分;剩下的就是信息。
已实现价差:做市商真正赚到的#
把这套逻辑落到可计算的指标上,就是 TCA 里的标准三件套。设 q_t = +1 为买方发起、−1 为卖方发起,m_t 为成交前报价中点:
有效价差(你付出的全部):
价格冲击(信息成分,市场拿走的):
已实现价差(做市商落袋的):
τ 通常取 5 分钟或若干笔成交之后。恒等式 有效价差 = 价格冲击 + 已实现价差 就是 Stoll 分解的现代形态:付出的总成本,一部分变成永久价格移动(补偿逆向选择),一部分留给流动性提供者(补偿库存与订单处理)。
Python 模拟:从指纹里把成本估回来#
构造一个三成本齐全的市场:每笔成交让有效中点永久移动 θ·S/2(信息),让报价整体偏移 w·S/2 后按 0.7 的速度均值回复(库存),成交本身发生在中点 ± S/2(订单处理):
import numpy as np
def simulate(n, S, w_info, w_inv, seed):
r = np.random.default_rng(seed)
half = S / 2
mid, inv_shift = 100.0, 0.0
trades, prices, qmids = np.zeros(n), np.zeros(n), np.zeros(n)
for i in range(n):
qm = mid + inv_shift # 成交前报价中点
p_buy = 0.5 - 0.35 * np.tanh(inv_shift / half)
q = 1 if r.random() < p_buy else -1 # 库存偏移抑制同向成交
trades[i], prices[i], qmids[i] = q, qm + q * half, qm
mid += q * w_info * half # 信息:永久
inv_shift = (inv_shift + q * w_inv * half) * 0.7 # 库存:暂时
mid += 0.01 * r.standard_normal() # 公共信息噪声
return trades, prices, qmids
S, tau = 0.10, 5
trades, prices, qmids = simulate(100_000, S, w_info=0.45, w_inv=0.25, seed=31)
es = (2 * trades * (prices - qmids)).mean() # 有效价差
rs = (2 * trades[:-tau] * (prices[:-tau] - qmids[tau:])).mean() # 已实现价差
print(f"有效价差 {es:.4f} = 价格冲击 {es-rs:.4f} + 已实现价差 {rs:.4f}")
# 有效价差 0.1000 = 价格冲击 0.0398 + 已实现价差 0.0602python报价价差 0.10 元,其中约 40% 被信息成分拿走,做市商真正落袋 0.06 元——还没扣库存风险的资金占用。

三种成本结构下的成交价路径肉眼可辨:

纯订单处理市场的成交价在水平带内密集弹跳、串行协方差深度为负;信息为主的市场价格大步游走、反转微弱;库存为主的市场介于两者之间——有波动但总被拉回。
Stoll 的识别核心也直接可验证:

成交价变化的串行协方差 −0.00109(bounce + 库存回复都在里面),报价中点的串行协方差 −0.00015(只剩库存回复)。两者的差距就是订单处理成分的贡献。
信息占比 θ 越高,已实现价差被侵蚀得越狠:

θ 超过 0.7 之后,已实现价差逼近零——报价价差再宽,做市商也只是在给知情交易者搬运价格。
三个落地应用#
一、横截面毒性排序。 对一篮子股票分别估「价格冲击 / 有效价差」的比值,就得到订单流毒性的横截面排名。毒性高的股票,被动做市和挂单捕获策略要么撤、要么把报价挂得离中点更远;执行算法则相反——毒性高说明价差里信息占比大,慢拆单省下的价差会被信息泄露吃回去,该快就快。
二、TCA 别只看有效价差。 有效价差衡量「你付了多少」,已实现价差衡量「对手赚了多少」。如果你的母单执行后有效价差正常、但对手的已实现价差持续为负——说明你的订单流毒性高(对手总在你成交后被价格碾过),券商和做市商迟早给你拉宽报价。监控自己订单流的「对手已实现价差」,是机构维护执行质量的隐蔽但关键的指标。
三、做市策略的盈亏基准。 高换手做市的期望收益不是报价价差乘以成交量,而是已实现价差乘以成交量减库存成本。回测做市策略时用报价价差算收益,是新手最常见的高估来源,θ=0.45 的市场里直接虚增 66%。
诚实的边界#
- 方向判定误差。 真实数据用 Lee-Ready 类算法给成交定方向,错标率 10-15%,会同时低估有效价差和价格冲击,且偏差方向不对称;
- τ 的选择是自由参数。 τ 太短,库存回复没走完,已实现价差被低估;τ 太长,无关的公共信息噪声进来。5 分钟是惯例不是真理,日内不同时段最优 τ 不同;
- 三成本并非正交。 库存偏移本身会被其他参与者学习(我的库存暴露也是信息),Stoll 分解假设三者可加可分,现实中信息与库存成分互相纠缠;
- 报价中点未必是公允价。 碎片化市场里 NBBO 中点滞后于真实价格发现(期货、暗池领先),用滞后中点算冲击会把跨市场价格发现误记为逆向选择。
价差分解不是精确科学,是一套让你把「交易成本」从一个数字拆成三个可归因来源的语言。知道钱被谁拿走了,才知道下一步该优化什么。
参考文献#
- Stoll, H. R. (1989). Inferring the Components of the Bid-Ask Spread: Theory and Empirical Tests. Journal of Finance, 44(1), 115-134.
- Huang, R. D., & Stoll, H. R. (1997). The Components of the Bid-Ask Spread: A General Approach. Review of Financial Studies, 10(4), 995-1034.
- Glosten, L. R., & Harris, L. E. (1988). Estimating the Components of the Bid/Ask Spread. Journal of Financial Economics, 21(1), 123-142.