订单簿数据结构:Level 1/2/3数据、订单簿快照与增量更新、Tick级数据流、数据存储与回放

做量化交易这些年,我越来越觉得——订单簿就是市场的「心跳」。你听懂了它,就能感知到资金流向的脉搏。今天咱们就聊聊订单簿的数据结构,这东西看着基础,但真用起来,坑不少。

Level 1/2/3 数据:你到底需要多深?

交易所给的数据,通常分三个层级。我刚开始做的时候,以为越深越好,结果被数据量淹没了。

层级 包含内容 典型用途
Level 1 最优买卖价、最新成交价、成交量 简单盯盘、散户策略
Level 2 前N档买卖盘口(通常5-10档) 短线交易、盘口分析
Level 3 全部挂单、逐笔委托数据 高频做市、订单流分析

我个人习惯,做日内策略至少用Level 2。Level 3呢?说实话,大部分散户用不上。我曾经在某个项目里接了Level 3数据,结果服务器带宽直接被打满,回放时磁盘IO成了瓶颈。嗯,这里要注意——数据深度和你的硬件能力要匹配。

订单簿快照与增量更新

你想想看,如果每次都要全量拉取订单簿,那网络开销得多大?所以交易所通常提供两种方式:

  • 快照(Snapshot):某一时刻的完整订单簿状态。一般每隔几秒或几十毫秒推送一次。
  • 增量更新(Incremental Update):只推送变化的部分,比如「价格100.5元,买一数量增加200手」。

实际应用中,我们通常这样配合:

# 伪代码示例:维护本地订单簿
class OrderBook:
    def __init__(self):
        self.bids = {}  # 买盘,价格->数量
        self.asks = {}  # 卖盘,价格->数量
    
    def apply_snapshot(self, snapshot):
        self.bids = snapshot['bids']
        self.asks = snapshot['asks']
    
    def apply_update(self, update):
        # 增量更新:价格、方向、数量
        price = update['price']
        side = update['side']  # 'bid' or 'ask'
        size = update['size']
        
        if side == 'bid':
            if size == 0:
                self.bids.pop(price, None)  # 删除该价位
            else:
                self.bids[price] = size
        else:
            if size == 0:
                self.asks.pop(price, None)
            else:
                self.asks[price] = size
避坑指南:我曾经遇到过增量更新丢失的情况,导致本地订单簿和交易所不一致。后来我加了个定时校验机制——每30秒拉一次快照做全量同步。记住,增量更新再快,也要有快照兜底。

Tick级数据流:毫秒间的博弈

Tick级数据,说白了就是每一笔成交或每一次订单簿变化。它的特点是:

  • 高频:A股大概每秒几十到几百笔,期货能到上千笔,加密货币更是夸张
  • 带时间戳:精确到毫秒甚至微秒
  • 包含细节:成交价、成交量、买卖方向、订单编号等

我建议你关注几个关键字段:

{
  "timestamp": 1699000000123,  // 毫秒时间戳
  "price": 100.50,             // 成交价
  "size": 200,                 // 成交量
  "side": "buy",               // 主动买还是主动卖
  "trade_id": "abc123"         // 成交编号
}

为什么会关注买卖方向?因为主动买代表买方更急切,主动卖代表卖方更急切。这是判断资金流向的核心。

数据存储与回放:让历史告诉你答案

做策略回测,数据存储是基本功。我见过有人把Tick数据存成CSV,结果一个月的文件就几十GB,查询慢得要命。这里分享我的经验:

  • 列式存储:比如Parquet格式,压缩率高,查询快
  • 按时间分区:每天一个文件或表,方便增量读取
  • 索引优化:对时间戳、价格字段建索引

回放时,核心逻辑是模拟时间流逝:

# 回放引擎伪代码
def replay(data_stream, start_time, end_time):
    for tick in data_stream:
        if tick.timestamp < start_time:
            continue
        if tick.timestamp > end_time:
            break
        # 更新订单簿
        order_book.apply_update(tick)
        # 触发策略逻辑
        strategy.on_tick(tick)
小技巧:回放时我习惯加个「加速因子」,比如10倍速回放。这样既能快速验证策略,又能发现数据中的异常点。有一次我回放时发现某个时间段的成交量突然归零,后来排查发现是交易所数据源出了问题。

知识体系总览

下面这张图,是我自己梳理的订单簿数据结构核心逻辑。你看一眼,基本就明白各模块怎么配合了。

订单簿数据结构核心体系 交易所数据源 Level 1/2/3 数据 更新方式 快照 增量更新 Tick级数据流 逐笔成交/订单变化 数据存储与回放 列式存储 时间分区 回放引擎

核心要点

  • Level 1 看趋势,Level 2 看博弈,Level 3 看细节
  • 快照保证一致性,增量保证实时性,两者缺一不可
  • Tick数据是原始素材,存储和回放是加工工具

说实话,订单簿数据结构这块,没有太多花哨的东西。但就是这些基础,决定了你策略的上限。我见过太多人一上来就搞复杂模型,结果连数据都处理不对。嗯,先把地基打牢,后面的楼才能盖得高。

专注资料整理