4. 订单簿动态特征:订单到达率、撤销率、成交率的统计建模
做量化交易这些年,我越来越觉得订单簿不是静态的快照,而是一个活生生的、不断在呼吸的系统。你盯着盘口看,价格在跳,挂单在变,这背后其实是三股力量在博弈:新订单不断涌进来,老订单被撤掉,还有一部分订单成交了。这三者的节奏,决定了市场的微观结构。
我个人习惯把订单簿的动态特征拆成三个核心指标:到达率、撤销率和成交率。说白了,就是搞清楚「来了多少」、「走了多少」和「成了多少」。你想想看,如果只盯着价格看,你永远不知道这波上涨是因为买盘真的在涌入,还是因为卖单被撤了造成的假象。
核心观点:订单簿的动态特征,是区分「真实流动性」和「虚假流动性」的关键。到达率反映市场参与热情,撤销率反映订单的耐心程度,成交率则直接告诉你市场的摩擦成本。
4.1 订单到达率的建模
订单到达率,就是单位时间内新挂单的数量。我在项目中遇到过一个问题:用简单的泊松过程去拟合,结果误差大得离谱。为什么?因为真实市场的订单到达是聚集性的——一波行情来了,订单像潮水一样涌进来;行情平淡时,半天没几个单子。
所以,我建议用自激励点过程(Hawkes Process)来建模。它的核心思想是:一个事件的发生,会提高后续事件发生的概率。这很符合直觉——一个大买单进来,往往会引发更多买单跟进。
# 一个简单的 Hawkes 过程模拟
import numpy as np
def simulate_hawkes(mu, alpha, beta, T):
"""
mu: 基础到达率
alpha: 自激励强度
beta: 衰减速度
T: 模拟时长
"""
events = []
t = 0
while t < T:
# 计算当前强度
lambda_t = mu + alpha * sum(np.exp(-beta * (t - s)) for s in events if t - s < 10)
# 生成下一个事件
u = np.random.uniform()
t += -np.log(u) / lambda_t
if t < T:
events.append(t)
return events
这段代码虽然简单,但已经能捕捉到订单到达的「聚集效应」了。实际应用中,我还会加入日内季节性因子——早盘开盘和尾盘收盘时的到达率明显高于午盘。嗯,这里要注意,如果你直接用原始数据去拟合,季节性会掩盖掉真正的自激励效应。
实战技巧:拟合 Hawkes 模型时,建议先对数据做「日内时间规整化」,把不同时间段的到达率拉到同一基准上。我曾经因为没做这步,拟合出来的参数完全没法用。
4.2 撤销率的统计特征
撤销率,很多人不重视,觉得不就是撤单嘛。其实不然。撤销率的高低,直接反映了订单的「虚假程度」。我记得有一次分析某只小盘股,发现它的撤销率高达70%以上——也就是说,每挂10个单子,有7个是撤掉的。这种市场,你进去做高频交易,基本就是给交易所送手续费。
撤销率的建模,我推荐用生存分析的方法。把每个订单的存活时间看作一个随机变量,撤销就是「死亡事件」。常用的模型有:
- 指数分布:假设撤销率恒定,简单但不太准
- Weibull 分布:可以刻画撤销率随时间变化——比如刚挂单时容易撤,挂久了反而更可能留着
- Cox 比例风险模型:可以加入价格偏离度、订单大小等协变量
我个人比较喜欢 Weibull 分布。为什么?因为它有一个形状参数 k:
- k < 1:撤销率随时间递减(订单越老越不容易撤)
- k = 1:撤销率恒定(退化为指数分布)
- k > 1:撤销率随时间递增(订单越老越容易撤)
你想想看,这能告诉我们什么?如果 k < 1,说明市场参与者比较有耐心,挂单是经过深思熟虑的。如果 k > 1,说明大家都在玩「挂单试探」的游戏——先挂上去看看,不行就撤。
避坑指南:我曾经用生存分析建模撤销率时,忽略了「成交」这个竞争风险。订单被成交了,它就不再有机会被撤销。如果不做竞争风险处理,撤销率的估计会严重偏大。解决办法是用「累积发生率函数」替代传统的 Kaplan-Meier 估计。
4.3 成交率的条件建模
成交率,说白了就是你的订单挂上去,到底能不能成交。这取决于两个因素:一是你的订单价格好不好(价格优势),二是市场有没有足够的反向流动性。
我建议用逻辑回归或者随机森林来建模成交概率。特征可以包括:
| 特征类别 | 具体特征 | 说明 |
|---|---|---|
| 价格特征 | 价格偏离度、价差位置 | 离最优价格越近,成交概率越高 |
| 流动性特征 | 订单簿深度、宽度 | 深度越大,成交越容易 |
| 动态特征 | 近期到达率、撤销率 | 市场活跃时成交概率更高 |
| 时序特征 | 距上次成交时间、日内时段 | 不同时段成交率差异很大 |
这里有个坑,我踩过好几次。成交率模型很容易过拟合,因为成交事件本身是稀疏的——大部分订单其实都没成交。你想想看,如果100个订单里只有5个成交了,你用这5个正样本和95个负样本去训练,模型很容易学偏。
# 处理样本不平衡的简单方法
from sklearn.utils import class_weight
# 计算类别权重
weights = class_weight.compute_class_weight(
'balanced',
classes=[0, 1],
y=y_train
)
# 在模型训练时传入权重
model.fit(X_train, y_train, sample_weight=weights[y_train])
我的经验:除了用类别权重,还可以考虑用「下采样」或「SMOTE过采样」。但我个人更推荐用「下采样」——把负样本随机抽到和正样本差不多数量。虽然会损失一些信息,但模型更稳健,不容易被噪声带偏。
4.4 三率联动的微观结构
到达率、撤销率、成交率不是孤立的。它们之间存在着复杂的反馈关系。我画了一张图来说明这个联动机制:
从这张图你能看到,三率之间形成了一个闭环。高到达率会同时推高撤销率和成交率——这取决于订单的性质。如果是「真实需求」驱动的到达,成交率会更高;如果是「试探性」的到达,撤销率会更高。
我在实盘中发现一个有意思的现象:当到达率突然飙升,但成交率没有同步跟上时,往往预示着价格即将反转。因为这说明大量订单只是在「试探」,并没有真正的成交意愿。一旦这些试探性订单被撤销,流动性会瞬间枯竭。
实战应用:我开发过一个「三率背离指标」——当到达率处于历史90%分位以上,但成交率处于30%分位以下时,触发反转预警。这个信号在股指期货上表现尤其好,胜率大概在65%左右。
4.5 建模中的常见陷阱
最后,我想聊聊建模过程中容易踩的坑。这些教训都是用真金白银换来的。
- 数据频率不匹配:到达率用毫秒级数据,成交率用秒级数据,结果模型完全对不上。我建议统一到同一时间尺度,比如都用100ms的桶。
- 忽略微观结构噪声:订单簿数据里有很多「乒乓单」——同一个账户在买卖两边来回挂撤。这些数据会严重扭曲到达率和撤销率的估计。我曾经写过一个过滤器,专门识别这种模式。
- 过度依赖历史参数:市场状态会变。今天拟合出来的参数,明天可能就失效了。我建议用滚动窗口的方式,每5分钟重新估计一次参数。
- 把相关性当因果:到达率和成交率正相关,不代表到达率「导致」了成交率。也可能是某个共同因素(比如新闻事件)同时推高了这两者。
血的教训:我曾经用全量数据训练了一个非常漂亮的撤销率模型,回测表现完美。结果一上实盘,直接崩了。后来才发现,我的训练数据里包含了「收盘集合竞价」时段——那个时段的撤销率几乎为零,因为不能撤单。模型学到了这个模式,但在连续竞价时段完全不适用。从那以后,我每次建模前都会先做「时段分割」,不同时段分别建模。
嗯,关于订单簿动态特征的统计建模,我就讲这么多。核心就三句话:到达率看聚集性,撤销率看生存时间,成交率看条件概率。三者联动起来看,才能看清市场的真实面貌。