21. 订单簿动态建模:订单到达过程、随机模型与模拟
各位同学,今天我们聊一个非常硬核的话题——订单簿的动态建模。
说白了,就是要把订单簿这个“活的”东西,用数学和代码给它建个模。你想想看,订单簿每时每刻都在变化,有人挂单、有人撤单、有人吃单。如果我们能把这个过程描述清楚,那很多策略就有了理论根基。
我个人习惯把订单簿建模分成三个层次:
- 订单到达过程——订单是怎么来的?
- 订单簿的随机模型——整个簿子怎么演化?
- 模拟订单簿——怎么在电脑里跑起来?
好,我们一个一个来拆。
21.1 订单到达过程:泊松过程
先问一个问题:订单到达交易所,是随机的还是有规律的?
答案是:既有规律,也有随机性。从微观角度看,单个订单的到达时间几乎是随机的。而泊松过程,就是描述这种“随机到达”最经典的模型。
核心定义:在泊松过程中,单位时间内到达的订单数量服从泊松分布。到达间隔时间服从指数分布。
数学上长这样:
P(N(t) = k) = (λt)^k * e^(-λt) / k!
其中 λ 是到达率,表示单位时间内平均到达的订单数。
我在项目中遇到过一个问题:直接用简单泊松过程建模,发现模拟出来的订单簿跟真实数据对不上。为什么呢?因为真实市场的订单到达率不是恒定的——早盘和尾盘明显更活跃,新闻出来时更是爆发式增长。
我的经验:实际项目中,我更推荐使用非齐次泊松过程(NHPP)。让 λ 随时间变化,比如用日内U型曲线来拟合。这样模拟出来的订单簿才更像真的。
还有一种更精细的模型——自激点过程(Hawkes Process)。它能刻画“订单聚集”现象:一个大单进来后,往往会引发更多订单。嗯,这个在闪崩场景下特别明显。
21.2 订单簿的随机模型
订单到达只是第一步。订单簿本身是一个复杂的随机系统。我们需要一个模型来描述:
- 限价单怎么堆积
- 市价单怎么吃掉流动性
- 撤单怎么发生
我个人最常用的框架是零智能模型(Zero Intelligence Model)。名字听着玄乎,其实逻辑很简单:
- 假设交易者都是“随机行为”的
- 限价单以泊松过程到达,价格随机分布在当前买卖价附近
- 市价单以另一个泊松过程到达,直接吃掉最优价位
- 撤单也以泊松过程发生
你可能会问:这么简单的模型有用吗?
说实话,我第一次用的时候也觉得太粗糙。但后来发现,零智能模型在描述订单簿的统计特性上效果出奇的好——比如价差分布、深度分布,都能拟合得不错。
避坑指南:我曾经在零智能模型上栽过跟头——用它来预测短期价格变动,结果一塌糊涂。后来才明白,零智能模型擅长描述静态统计特性,但不适合做时序预测。记住这个边界。
更高级的模型还有:
- AVI模型(Additive Volume Impact):把订单簿看成多个价位上的“队列”,每个队列独立演化
- 连续时间随机模型:用随机微分方程描述订单簿的连续变化
下面这张图展示了订单簿动态建模的核心逻辑:
21.3 模拟订单簿:从理论到代码
理论讲完了,咱们来点实际的。怎么在电脑里模拟一个订单簿?
我常用的方法是事件驱动模拟。核心思路:
- 维护一个事件队列,每个事件包含:时间、类型(限价单/市价单/撤单)、价格、数量
- 按照时间顺序处理事件
- 每次事件更新订单簿状态
下面是一个简化版的Python实现思路:
import numpy as np
from collections import defaultdict
class OrderBookSimulator:
def __init__(self, lambda_limit, lambda_market, mu_cancel):
self.lambda_limit = lambda_limit # 限价单到达率
self.lambda_market = lambda_market # 市价单到达率
self.mu_cancel = mu_cancel # 撤单率
self.bids = defaultdict(float) # 买盘
self.asks = defaultdict(float) # 卖盘
self.time = 0.0
def generate_events(self, T):
"""生成T时间内的所有事件"""
events = []
# 生成限价单事件
t = 0
while t < T:
t += np.random.exponential(1/self.lambda_limit)
if t < T:
price = self._random_price()
side = 'bid' if np.random.random() < 0.5 else 'ask'
qty = np.random.randint(1, 100)
events.append((t, 'limit', side, price, qty))
# 生成市价单事件(类似逻辑)
# ...
# 按时间排序
events.sort(key=lambda x: x[0])
return events
def run(self, T):
events = self.generate_events(T)
for event in events:
self._process_event(event)
def _process_event(self, event):
t, etype, side, price, qty = event
self.time = t
if etype == 'limit':
if side == 'bid':
self.bids[price] += qty
else:
self.asks[price] += qty
elif etype == 'market':
# 市价单逻辑:吃掉最优价位
# ...
# 撤单逻辑类似
我的建议:刚开始做模拟时,别追求太复杂。先跑通零智能模型,看看订单簿的价差分布、深度分布长什么样。等这些基础统计量跟真实数据对上了,再逐步加入更复杂的机制——比如订单聚集、价格跳跃等。
模拟订单簿有几个关键指标需要关注:
| 指标 | 含义 | 模拟中的验证方法 |
|---|---|---|
| 价差分布 | 买卖价差的统计分布 | 对比真实数据的价差直方图 |
| 深度分布 | 各价位上的挂单量 | 检查是否呈现“倒U型” |
| 订单到达间隔 | 相邻订单的时间差 | 验证是否服从指数分布 |
| 自相关函数 | 订单流的时序相关性 | 检查是否存在长记忆性 |
曾经踩过的坑:我刚开始做模拟时,直接把所有参数设成固定的。结果模拟出来的订单簿死气沉沉,价差几乎不变。后来才意识到,真实市场的参数是时变的——比如波动率高的时段,订单到达率会飙升。所以我现在做模拟,都会让参数随时间变化,或者加入随机波动。
最后说一句:订单簿模拟是个“越做越深”的领域。从最简单的泊松过程,到自激点过程,再到深度学习生成模型,每个层次都有不同的应用场景。我个人觉得,对于做策略的同学来说,先把零智能模型吃透,能解决80%的问题。
剩下的20%,等你真正遇到瓶颈了,再往深挖也不迟。