3、订单簿数据解析:Level 1、Level 2、Level 3数据区别、Tick级数据流、增量更新与快照更新

做高频做市,说白了就是跟订单簿谈恋爱。你得懂它的脾气,知道它什么时候在撒谎,什么时候在说实话。今天这一章,我就带你扒开订单簿的底裤,看看Level 1、Level 2、Level 3到底差在哪,以及Tick级数据流和增量更新这些实战中绕不开的东西。

3.1 Level 1、Level 2、Level 3:数据深度的三级跳

很多人一开始接触订单簿数据,容易懵。什么Best Bid/Offer,什么Depth,什么Full Order Book。我当年刚入行时也踩过坑,以为Level 2就是全部了,结果策略一上线就亏钱。嗯,这里要注意,不同Level的数据,对应的是不同的市场视角。

3.1.1 Level 1:只看最表层

Level 1数据,也叫Top of Book。它只告诉你当前最优的买价和卖价,以及对应的挂单量。说白了,就是市场最表面的那层皮。

字段 含义
Bid Price 最高买入价
Bid Size 最高买入价上的挂单量
Ask Price 最低卖出价
Ask Size 最低卖出价上的挂单量

这种数据够用吗?说实话,做做普通交易勉强可以,但做高频做市,远远不够。你想想看,只看最优价,你根本不知道下面还有多少暗流涌动。我曾经见过一个策略,只看Level 1,结果一个大单砸下来,直接穿了好几层,策略瞬间爆仓。

3.1.2 Level 2:看到市场深度

Level 2数据,也叫Market Depth。它展示了多个价位的挂单情况,通常是前5档、前10档,甚至前20档。我个人的习惯是,做市策略至少要看前10档,否则你根本看不清对手的意图。

关键点:Level 2数据能让你看到「价格阶梯」上的挂单分布。比如买一、买二、买三...卖一、卖二、卖三...每一档都有对应的价格和数量。

举个例子,假设某股票当前Level 2数据如下:

买一: 100.00  1000股
买二: 99.99   2000股
买三: 99.98   1500股
卖一: 100.01  800股
卖二: 100.02  1200股
卖三: 100.03  1800股

看到这个,你就能判断出:买方在100.00这个价位有1000股的支撑,但卖方的压力在100.01只有800股。嗯,这里有个小技巧——如果卖一挂单量突然减少,而买一没变,那很可能是有人在撤单,准备拉升了。

3.1.3 Level 3:全量订单簿

Level 3数据,也叫Full Order Book。它包含了市场上所有的挂单信息,不仅仅是价格档位,还包括每个订单的ID、时间戳、委托类型等。说白了,就是市场的「全息影像」。

我在做高频做市时,Level 3数据是标配。为什么?因为只有看到全量数据,你才能做订单流分析,才能识别出大资金的真实意图。比如,一个大单拆成很多小单挂在不同价位,Level 2可能看不出来,但Level 3能让你看到这些订单的ID是同一个账户。

注意:Level 3数据量极大,处理不好容易把系统搞崩。我曾经有个同事,直接拿Level 3数据做实时计算,结果内存爆了,交易系统直接宕机。所以,处理Level 3数据时,一定要做好内存管理和数据压缩。

3.2 Tick级数据流:心跳与脉搏

Tick级数据流,就是市场每一次变化的数据。每一次成交、每一次挂单、每一次撤单,都会产生一个Tick。说白了,这就是市场的心跳。

我个人习惯把Tick数据分为两类:

  • 成交Tick:记录每一笔成交的价格、数量、时间。
  • 订单Tick:记录挂单、撤单、修改等操作。

为什么Tick级数据这么重要?因为高频做市拼的就是速度。你比别人早1毫秒知道市场变化,就能多赚一笔。我见过一个团队,他们专门优化Tick数据的解析速度,从10微秒优化到2微秒,就靠这个优势,年化收益提升了5%。

实战技巧:处理Tick数据时,建议使用环形缓冲区(Ring Buffer)来存储,避免频繁的内存分配。我自己的代码里,Tick数据的缓冲区大小通常设为1024,够用又不浪费。

3.3 增量更新与快照更新:两种更新策略

订单簿数据怎么更新?两种方式:快照更新和增量更新。很多人搞不清这两者的区别,我当年也迷糊过一阵子。

3.3.1 快照更新

快照更新,就是每隔一段时间,交易所把当前订单簿的全量数据发给你。比如每100毫秒发一次,每次都是完整的Level 2数据。

优点:简单,不容易出错。你拿到快照,直接覆盖本地数据就行。

缺点:数据量大,延迟高。你想想看,每次都要传输几千甚至几万条数据,网络带宽和CPU都扛不住。

3.3.2 增量更新

增量更新,就是交易所只告诉你「变化的部分」。比如,卖一的价格从100.01变成了100.02,或者买二上新增了500股。

优点:数据量小,延迟低。适合高频场景。

缺点:需要维护本地订单簿状态,一旦漏掉一个增量,整个订单簿就乱了。

我的建议:做高频做市,一定要用增量更新。但要做好「快照+增量」的混合策略。比如,每收到1000个增量后,强制请求一次快照,用来校验本地订单簿是否正确。我曾经就因为增量丢失,导致订单簿错位,白白亏了十几万。从那以后,我每次启动策略前,都会先拉一次快照做基准。

3.4 实战:解析一个Tick级增量数据

光说不练假把式。我写一段Python代码,演示如何解析一个典型的Tick级增量数据。

import json

# 模拟一个增量更新数据包
tick_data = {
    "type": "incremental",
    "timestamp": 1678901234567,
    "changes": [
        {"side": "bid", "price": 100.00, "size": 1500, "action": "add"},
        {"side": "ask", "price": 100.01, "size": 0, "action": "delete"},
        {"side": "bid", "price": 99.99, "size": 1800, "action": "modify"}
    ]
}

def apply_incremental(order_book, tick):
    """将增量更新应用到本地订单簿"""
    for change in tick["changes"]:
        price = change["price"]
        size = change["size"]
        side = change["side"]
        action = change["action"]

        if action == "add":
            order_book[side][price] = size
        elif action == "delete":
            if price in order_book[side]:
                del order_book[side][price]
        elif action == "modify":
            order_book[side][price] = size

# 初始化一个空订单簿
order_book = {"bid": {}, "ask": {}}

# 应用增量
apply_incremental(order_book, tick_data)

print("更新后的订单簿:")
print(json.dumps(order_book, indent=2))

这段代码很简单,但核心逻辑就在这。你想想看,如果每秒有几千个这样的增量,你的系统能不能扛住?我建议你用Cython或者Numba来优化这部分代码,能快不少。

3.5 避坑指南:我踩过的三个坑

最后,分享三个我亲身踩过的坑,希望能帮你省点学费。

  1. 坑一:时间戳精度不够。有些交易所的时间戳只到毫秒,但高频做市需要微秒级。我曾经因为时间戳精度不够,导致订单排序错误,策略逻辑全乱套。解决方案:用硬件时间戳,或者自己维护一个高精度时钟。
  2. 坑二:增量丢失不处理。网络波动时,增量数据可能丢失。如果不做校验,订单簿会越来越偏。我后来加了一个序列号机制,每收到一个增量就检查序列号是否连续,不连续就请求快照。
  3. 坑三:Level 3数据解析太慢。Level 3数据量大,解析慢会导致延迟。我建议用零拷贝(Zero-Copy)技术,直接从网络缓冲区解析,减少内存复制。

一句话总结:Level 1看表面,Level 2看深度,Level 3看全貌。Tick数据是心跳,增量更新是灵魂。做高频做市,这三样缺一不可。

好了,这一章就到这里。下一章我会讲订单簿的微观结构,包括价差、深度、订单流不平衡这些实战指标。到时候见。