21. 订单簿动态建模:订单到达过程、随机模型与模拟

各位同学,今天我们聊一个非常硬核的话题——订单簿的动态建模。

说白了,就是要把订单簿这个“活的”东西,用数学和代码给它建个模。你想想看,订单簿每时每刻都在变化,有人挂单、有人撤单、有人吃单。如果我们能把这个过程描述清楚,那很多策略就有了理论根基。

我个人习惯把订单簿建模分成三个层次:

  • 订单到达过程——订单是怎么来的?
  • 订单簿的随机模型——整个簿子怎么演化?
  • 模拟订单簿——怎么在电脑里跑起来?

好,我们一个一个来拆。

21.1 订单到达过程:泊松过程

先问一个问题:订单到达交易所,是随机的还是有规律的?

答案是:既有规律,也有随机性。从微观角度看,单个订单的到达时间几乎是随机的。而泊松过程,就是描述这种“随机到达”最经典的模型。

核心定义:在泊松过程中,单位时间内到达的订单数量服从泊松分布。到达间隔时间服从指数分布。

数学上长这样:

P(N(t) = k) = (λt)^k * e^(-λt) / k!

其中 λ 是到达率,表示单位时间内平均到达的订单数。

我在项目中遇到过一个问题:直接用简单泊松过程建模,发现模拟出来的订单簿跟真实数据对不上。为什么呢?因为真实市场的订单到达率不是恒定的——早盘和尾盘明显更活跃,新闻出来时更是爆发式增长。

我的经验:实际项目中,我更推荐使用非齐次泊松过程(NHPP)。让 λ 随时间变化,比如用日内U型曲线来拟合。这样模拟出来的订单簿才更像真的。

还有一种更精细的模型——自激点过程(Hawkes Process)。它能刻画“订单聚集”现象:一个大单进来后,往往会引发更多订单。嗯,这个在闪崩场景下特别明显。

21.2 订单簿的随机模型

订单到达只是第一步。订单簿本身是一个复杂的随机系统。我们需要一个模型来描述:

  • 限价单怎么堆积
  • 市价单怎么吃掉流动性
  • 撤单怎么发生

我个人最常用的框架是零智能模型(Zero Intelligence Model)。名字听着玄乎,其实逻辑很简单:

  1. 假设交易者都是“随机行为”的
  2. 限价单以泊松过程到达,价格随机分布在当前买卖价附近
  3. 市价单以另一个泊松过程到达,直接吃掉最优价位
  4. 撤单也以泊松过程发生

你可能会问:这么简单的模型有用吗?

说实话,我第一次用的时候也觉得太粗糙。但后来发现,零智能模型在描述订单簿的统计特性上效果出奇的好——比如价差分布、深度分布,都能拟合得不错。

避坑指南:我曾经在零智能模型上栽过跟头——用它来预测短期价格变动,结果一塌糊涂。后来才明白,零智能模型擅长描述静态统计特性,但不适合做时序预测。记住这个边界。

更高级的模型还有:

  • AVI模型(Additive Volume Impact):把订单簿看成多个价位上的“队列”,每个队列独立演化
  • 连续时间随机模型:用随机微分方程描述订单簿的连续变化

下面这张图展示了订单簿动态建模的核心逻辑:

订单簿动态建模核心逻辑 订单到达过程 泊松过程 / Hawkes过程 订单簿随机模型 零智能模型 | AVI模型 | 连续时间随机模型 限价单堆积 + 市价单消耗 + 撤单 模拟订单簿 蒙特卡洛模拟 | 事件驱动模拟 应用场景 策略回测 做市商模型 关键参数 到达率 λ 撤单率 μ 从订单到达 → 随机模型 → 模拟输出,形成完整闭环 参数校准与反馈

21.3 模拟订单簿:从理论到代码

理论讲完了,咱们来点实际的。怎么在电脑里模拟一个订单簿?

我常用的方法是事件驱动模拟。核心思路:

  1. 维护一个事件队列,每个事件包含:时间、类型(限价单/市价单/撤单)、价格、数量
  2. 按照时间顺序处理事件
  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%,等你真正遇到瓶颈了,再往深挖也不迟。