Level2行情数据解析:逐笔成交、逐笔委托、十档行情、快照与增量

做市交易这行,数据就是你的眼睛。而Level2行情,说白了就是给你配了一副高清夜视仪。

普通行情只能看到五档买卖盘,每秒刷新一次。Level2呢?十档行情、逐笔成交、逐笔委托,还有快照和增量数据。我刚开始接触时也觉得眼花缭乱,但用顺手了就会发现——这些数据里藏着太多赚钱的信号。

Level2到底多了什么?

先看一张对比表,你就明白了:

数据维度 普通Level1 Level2
买卖盘档位 5档 10档(部分市场支持更多)
刷新频率 约3秒一次 约0.5秒一次,甚至实时推送
逐笔成交 有(每笔交易的详细记录)
逐笔委托 有(每笔挂单的详细记录)
数据推送方式 全量快照 快照 + 增量更新

嗯,这里要注意。Level2的核心优势不是「多五档」,而是「逐笔」和「增量」。这两个东西,才是做市策略的灵魂。

逐笔成交:每一笔交易都逃不过你的眼睛

逐笔成交数据,记录的是每一笔实际成交的订单。它包含什么?

  • 成交时间:精确到毫秒甚至微秒
  • 成交价格:这笔交易的实际成交价
  • 成交数量:这笔交易成交了多少手
  • 成交方向:主动买还是主动卖(也就是常说的「主买」和「主卖」)
  • 成交编号:交易所给这笔交易分配的唯一ID

我在项目中遇到过一件事。有一次做回测,发现某个策略在模拟环境里跑得挺好,一上实盘就亏。后来查了半天,问题出在「成交方向」的判断上。模拟环境里我用的是Tick数据的买卖标识,但实盘里有些交易所的逐笔成交数据,方向标识是反的。你想想看,方向都搞反了,策略能赚钱才怪。

避坑指南:我曾经因为没仔细看交易所的文档,把「主动买」和「主动卖」搞反了,导致策略在实盘里反向交易了整整一天。后来我养成了一个习惯——拿到逐笔数据后,先手动算几笔,跟行情软件对比一下,确认方向逻辑没问题再上线。

逐笔委托:看透订单簿的「前世今生」

逐笔委托比逐笔成交更底层。它记录的是每一笔挂单、撤单、成交的完整生命周期。

举个例子。你在盘口看到卖一价10.00元挂了1000手,下一秒变成了800手。普通行情只告诉你「卖一数量变了」,但逐笔委托会告诉你:

  • 有人撤单了200手
  • 或者有人成交了200手
  • 甚至可能是有人修改了订单(先撤再挂)

逐笔委托的数据结构大概长这样:

{
  "order_id": "2024010109300012345",  // 委托单编号
  "time": 1704061800000,              // 委托时间(毫秒时间戳)
  "price": 10.00,                     // 委托价格
  "volume": 1000,                     // 委托数量
  "side": "sell",                     // 买卖方向
  "type": "limit",                    // 委托类型(限价/市价)
  "action": "new"                     // 操作类型:new/cancel/trade
}

为什么这个数据重要?因为你可以通过逐笔委托,还原出订单簿的「微观结构」。比如,某个价位突然出现大量挂单,然后又迅速撤单——这很可能是「虚假挂单」,用来诱导其他交易者跟风。做市商如果看不懂这个,很容易被「钓鱼」。

我的习惯:我一般会把逐笔委托数据缓存到本地,用滑动窗口的方式分析最近1秒内的委托行为。如果发现某个价位频繁出现「挂单-撤单」的循环,我会暂时避开这个价位,等它稳定了再进场。

十档行情:看得更远,才能跑得更快

十档行情,就是订单簿上买卖各10档的价格和数量。普通行情只有5档,Level2给你10档。

多这5档有什么用?

  • 判断支撑和压力:如果卖10档到卖6档全是密密麻麻的挂单,说明上方压力很大,价格很难突破。
  • 识别大单动向:某个价位突然出现巨量挂单,可能是机构在「筑墙」。
  • 计算市场深度:你可以算出,要推动价格变动1个tick,需要吃掉多少对手盘。

我记得有一次做ETH的做市策略,发现卖5档到卖8档之间有个「缺口」——中间几档几乎没有挂单。这意味着什么?一旦价格突破卖4档,会瞬间跳到卖9档,中间几乎没有阻力。我立刻调整了策略,在卖4档附近加大了挂单量,果然吃到了这波快速拉升的利润。

快照与增量:两种数据,两种用法

Level2行情推送有两种方式:快照和增量。

快照:每隔一段时间(比如500ms),交易所给你发一份完整的订单簿数据。包含所有10档的价格和数量。

增量:每次订单簿发生变化时,只推送变化的部分。比如「卖一价从10.00变成10.01,数量从1000变成800」。

为什么要有两种?

快照数据完整,但频率低。增量数据实时,但需要你自己维护订单簿状态。

我建议的做法是:

  1. 用快照数据做「基准」——每收到一次快照,就重置本地的订单簿状态。
  2. 用增量数据做「更新」——在两次快照之间,用增量数据实时更新订单簿。
  3. 定期校验——比如每10秒检查一次本地订单簿和快照是否一致,如果不一致,以快照为准重新同步。
核心要点:增量数据是实时性的关键,但快照数据是「安全网」。千万不要只依赖增量数据,因为网络丢包或数据延迟会导致你的本地订单簿「漂移」,跟真实市场越差越远。

一张图看懂Level2数据体系

下面这张图,是我自己整理的数据流转逻辑。你看一遍就能明白各个数据之间的关系:

Level2行情数据体系 交易所 逐笔成交 每笔交易的详细记录 逐笔委托 每笔挂单/撤单/成交 十档行情 买卖各10档价格数量 数据处理与订单簿维护 快照做基准 → 增量做更新 → 定期校验一致性 做市策略应用 识别虚假挂单 | 判断支撑压力 | 捕捉大单动向

实战:如何用Python解析Level2数据

假设你从交易所拿到了Level2的增量数据,格式是JSON。下面这段代码,是我常用的解析方式:

import json
from collections import defaultdict

class OrderBook:
    def __init__(self):
        # 用字典存储买卖盘,key是价格,value是数量
        self.bids = defaultdict(int)  # 买盘
        self.asks = defaultdict(int)  # 卖盘
        self.last_snapshot_time = 0
    
    def apply_snapshot(self, snapshot_data):
        """应用快照数据,重置订单簿"""
        self.bids.clear()
        self.asks.clear()
        
        for bid in snapshot_data['bids']:
            self.bids[bid['price']] = bid['volume']
        for ask in snapshot_data['asks']:
            self.asks[ask['price']] = ask['volume']
        
        self.last_snapshot_time = snapshot_data['timestamp']
        print(f"快照更新完成,时间戳: {self.last_snapshot_time}")
    
    def apply_increment(self, inc_data):
        """应用增量数据,更新订单簿"""
        for change in inc_data['changes']:
            price = change['price']
            volume = change['volume']
            side = change['side']  # 'bid' 或 'ask'
            
            if side == 'bid':
                if volume == 0:
                    # 数量为0表示该价位已撤单
                    self.bids.pop(price, None)
                else:
                    self.bids[price] = volume
            elif side == 'ask':
                if volume == 0:
                    self.asks.pop(price, None)
                else:
                    self.asks[price] = volume
        
        # 每次增量更新后,可以触发策略逻辑
        self.check_strategy()
    
    def check_strategy(self):
        """简单的策略检查:如果买卖价差小于阈值,提示机会"""
        if not self.bids or not self.asks:
            return
        
        best_bid = max(self.bids.keys())
        best_ask = min(self.asks.keys())
        spread = best_ask - best_bid
        
        if spread < 0.01:  # 假设阈值是0.01
            print(f"价差收窄!买一: {best_bid}, 卖一: {best_ask}")
    
    def get_top_n(self, n=10):
        """获取前n档买卖盘"""
        sorted_bids = sorted(self.bids.items(), reverse=True)[:n]
        sorted_asks = sorted(self.asks.items())[:n]
        return sorted_bids, sorted_asks

# 使用示例
ob = OrderBook()

# 模拟收到快照
snapshot = {
    "timestamp": 1704061800000,
    "bids": [{"price": 10.00, "volume": 1000}, {"price": 9.99, "volume": 2000}],
    "asks": [{"price": 10.01, "volume": 1500}, {"price": 10.02, "volume": 800}]
}
ob.apply_snapshot(snapshot)

# 模拟收到增量更新
increment = {
    "changes": [
        {"price": 10.00, "volume": 800, "side": "bid"},  # 买一数量减少
        {"price": 10.01, "volume": 0, "side": "ask"}     # 卖一被吃掉
    ]
}
ob.apply_increment(increment)
一个小技巧:在实际项目中,我一般不会直接用Python的dict来存订单簿,因为高频场景下性能不够。我会用sortedcontainers库或者自己实现一个跳表(Skip List),这样插入和删除操作能快到微秒级。不过对于学习和原型验证,上面的代码完全够用了。

数据质量:最容易忽略的坑

Level2数据虽然好,但质量参差不齐。我踩过的坑包括:

  • 数据延迟:有些交易所的增量数据比实际成交慢了100毫秒以上。做高频策略时,这100毫秒足以让你亏钱。
  • 数据缺失:网络波动可能导致某些增量更新没收到。如果不做校验,你的本地订单簿会越来越「歪」。
  • 时间戳问题:有些交易所给的是「交易所时间」,有些给的是「本地接收时间」。做回测时一定要统一时间基准。

我曾经因为没处理好数据延迟,在实盘里连续吃了三天的亏。后来我加了一个「延迟监控」模块——每次收到增量数据,都计算一下当前时间和数据时间戳的差值。如果延迟超过200毫秒,就暂时停止策略,等数据恢复正常再启动。

嗯,数据这东西,说白了就是「垃圾进,垃圾出」。Level2行情再高级,如果解析和处理的环节出了问题,策略照样白搭。


专注资料整理