订单簿模拟:订单簿数据结构、盘口深度模拟、订单簿更新逻辑、流动性模型

做市商策略的核心战场,就是订单簿。你想想看,我们所有的报价、撤单、吃单,最终都要反映在订单簿上。如果回测框架里的订单簿模拟不够真实,那策略表现再好也是空中楼阁。今天我们就来聊聊,怎么把这个核心模块做扎实。

订单簿数据结构:别小看这个基础

我个人习惯用价格优先、时间优先的原则来组织订单簿。说白了,买单价格高的排前面,卖单价格低的排前面;价格一样,先来的排前面。

数据结构上,我推荐用红黑树或者跳表来实现。为什么?因为订单簿需要频繁的插入、删除和查询操作,这些数据结构能在 O(log n) 时间内搞定。我在项目中遇到过用数组硬扛的,结果回测到一半内存爆了,订单量一上来直接卡死。

一个典型的订单簿结构长这样:

class OrderBook:
    def __init__(self):
        self.bids = SortedDict()  # 买单,键为价格,值为订单列表
        self.asks = SortedDict()  # 卖单,键为价格,值为订单列表
        self.order_map = {}       # 订单ID到订单对象的映射
        
    def add_order(self, order):
        # 根据订单方向插入到 bids 或 asks
        pass
        
    def cancel_order(self, order_id):
        # 从订单簿中移除指定订单
        pass
        
    def match_order(self, order):
        # 尝试与对手盘成交
        pass
小技巧: 我建议把订单簿的深度限制在 10 层以内。回测时太深的深度对策略影响不大,反而拖慢速度。真实交易中,超过 10 层的流动性往往也不可靠。

盘口深度模拟:让订单簿活起来

盘口深度,就是订单簿上每个价格档位的挂单量。模拟盘口深度时,不能简单用均匀分布。真实市场的深度分布是有规律的——越靠近中间价,流动性越集中

我常用的方法是指数衰减模型

def simulate_depth(mid_price, spread, depth_levels=10):
    depths = {}
    for i in range(1, depth_levels + 1):
        price = mid_price + i * spread / 2  # 卖盘
        volume = base_volume * math.exp(-i * decay_factor)
        depths[price] = volume
        
        price = mid_price - i * spread / 2  # 买盘
        volume = base_volume * math.exp(-i * decay_factor)
        depths[price] = volume
    return depths

嗯,这里要注意:decay_factor 这个参数很关键。我做过统计,BTC/USDT 的衰减因子大概在 0.3-0.5 之间,而一些山寨币可能只有 0.1-0.2。说白了,流动性越差的币,深度衰减得越慢。

订单簿更新逻辑:事件驱动的艺术

订单簿不是静态的,它每时每刻都在变化。更新逻辑主要处理三类事件:

  • 新增订单:插入到对应价格档位
  • 撤销订单:从订单簿中移除
  • 成交订单:减少对应档位的挂单量,如果量归零则删除该档位

我曾经踩过一个坑:回测时把订单簿更新和策略决策放在同一个时间步里。结果策略看到的价格和实际成交价格总是差那么一点点。后来我改成先更新订单簿,再让策略做决策,问题就解决了。

避坑指南: 我曾经在回测中忽略了「冰山订单」的处理。真实市场中,大单往往只显示一部分。如果你的回测框架不支持冰山订单,那模拟出来的盘口深度会严重失真。

流动性模型:从微观到宏观

流动性模型决定了订单簿的「生命力」。我把它分为三个层次:

层次 描述 实现方式
基础流动性 由做市商和散户提供的稳定挂单 泊松过程生成订单到达
事件驱动流动性 新闻、大单引发的流动性变化 事件触发 + 临时深度调整
自适应流动性 根据市场波动率动态调整 波动率越高,深度越薄

我个人最常用的是混合模型。基础流动性用泊松过程模拟,然后叠加事件驱动层。举个例子,当一个大买单进来时,我会临时增加卖盘深度,模拟做市商趁机出货的行为。

def update_liquidity(order_book, event):
    if event.type == 'large_buy':
        # 模拟做市商反应:增加卖盘深度
        for i in range(1, 5):
            price = order_book.asks[i].price
            order_book.asks[i].volume *= 1.2
    elif event.type == 'volatility_spike':
        # 波动率飙升时,整体深度缩减
        for level in order_book.asks:
            level.volume *= 0.8
        for level in order_book.bids:
            level.volume *= 0.8
核心观点: 流动性模型的好坏,直接决定了回测结果的可信度。我见过太多人用「完美流动性」假设来回测,结果实盘一跑就崩。记住:你的策略必须能在流动性不足的环境下生存

知识体系总览

下面这张图,是我对订单簿模拟整个知识体系的梳理。你可以把它当作一个检查清单,看看自己还有哪些地方没做到位。

订单簿模拟 数据结构 盘口深度模拟 更新逻辑 流动性模型 红黑树/跳表 订单ID映射 指数衰减模型 衰减因子调参 新增/撤销/成交 冰山订单处理 基础流动性 事件驱动流动性 自适应流动性

这张图把订单簿模拟的四个核心模块串起来了。你从数据结构出发,逐步构建盘口深度,然后通过更新逻辑让订单簿动起来,最后用流动性模型赋予它「生命力」。每一步都环环相扣,缺一不可。

我的建议: 刚开始做回测框架时,别追求完美。先用最简单的数据结构(比如 Python 的 dict + list),把核心逻辑跑通。等策略验证有效了,再回来优化性能。我见过太多人一开始就搞复杂的数据结构,结果框架没写完就放弃了。

专注资料整理