1. 订单簿基础:什么是订单簿?

做高频交易的人,每天打交道最多的东西,就是订单簿。

说白了,订单簿就是一个实时更新的「买卖挂单账本」。交易所用它来记录所有交易者挂出来的限价单——谁想买、谁想卖、什么价格、多少数量,全都清清楚楚。

我刚开始接触这个领域时,觉得订单簿不就是个价格列表嘛,有啥好研究的?后来在实盘环境里被狠狠教育了一次,才发现这东西的深度远超想象。

1.1 限价单 vs 市价单

先搞清楚两个最基础的概念。你想想看,你下单的时候,交易所其实只给你两种选择:

  • 限价单(Limit Order):你指定一个价格,只有市场达到这个价格时才会成交。比如「我挂100元买10股」,不到100元就不买。
  • 市价单(Market Order):你不指定价格,直接说「我要买10股」,交易所按当前最优价格帮你成交。

嗯,这里要注意一个关键区别:限价单提供流动性,市价单消耗流动性。我在项目中遇到过不少新手,上来就狂发市价单,结果把盘口打穿,成交价格远不如预期。

核心区别总结:

特性 限价单 市价单
价格控制 完全控制 无控制
成交速度 可能等待 立即成交
流动性贡献 提供流动性 消耗流动性
滑点风险 无滑点 可能有滑点

1.2 订单簿的买卖盘口结构

订单簿长什么样?我画个图你就明白了。

订单簿盘口结构示意图 卖盘(Ask / Offer) 价格 数量 累计 10.05 200 200 10.04 150 350 10.03 300 650 10.02 100 750 10.01 ← 卖一价 500 1250 最新成交价:10.00 买盘(Bid) 价格 数量 累计 9.99 ← 买一价 800 800 9.98 400 1200 9.97 250 1450 9.96 180 1630 9.95 350 1980 价差 = 0.02

这张图里,左侧是卖盘(也叫 Ask 或 Offer),右侧是买盘(Bid)。中间那条虚线是当前最新成交价。卖一价和买一价之间的差距,我们叫它「价差」(Spread)。

我个人习惯把订单簿想象成一个「价格阶梯」。每一档价格上,都堆着一定数量的挂单。价格越往中间靠,挂单越密集。

1.3 盘口的关键指标

做高频数据压缩时,我们最关心的几个盘口指标:

  • 买一价(Bid1):最高的买入限价
  • 卖一价(Ask1):最低的卖出限价
  • 价差(Spread):Ask1 - Bid1,流动性越好的品种价差越小
  • 盘口深度(Depth):各档位的挂单数量
  • 买卖盘比(Bid/Ask Ratio):买盘总量 / 卖盘总量

实战小技巧: 我一般会同时监控前5档的累计深度。如果买一只有100手,但买二到买五有5000手,说明有大资金在下面托底。这种结构在数据压缩时,我会保留更多档位信息。

1.4 订单簿的更新机制

订单簿不是静态的。每一秒都有新订单进来,也有订单被撤掉。交易所会推送三种事件:

  1. 新增订单:有人挂了新的限价单
  2. 撤销订单:有人撤回了未成交的限价单
  3. 成交事件:限价单和市价单匹配成功

我曾经踩过一个坑:以为订单簿的更新是「全量快照」,每次推送都传全部数据。后来发现高频场景下,交易所发的是「增量更新」——只告诉你哪一档变了。如果不做增量压缩,带宽根本扛不住。

注意: 不同交易所的订单簿推送格式差异很大。有的用 JSON,有的用二进制。做数据压缩时,一定要先搞清楚原始数据的结构,否则压缩率会很难看。

1.5 一个简单的订单簿数据结构

用 Python 表示一个订单簿,我习惯这样写:

class OrderBook:
    def __init__(self):
        self.bids = {}  # 买盘:{价格: 数量}
        self.asks = {}  # 卖盘:{价格: 数量}
        self.last_price = 0.0
    
    def update(self, side, price, quantity):
        """更新订单簿"""
        if quantity == 0:
            # 数量为0表示撤销该档位
            if side == 'bid':
                self.bids.pop(price, None)
            else:
                self.asks.pop(price, None)
        else:
            if side == 'bid':
                self.bids[price] = quantity
            else:
                self.asks[price] = quantity
    
    def get_spread(self):
        """计算当前价差"""
        if not self.bids or not self.asks:
            return None
        best_bid = max(self.bids.keys())
        best_ask = min(self.asks.keys())
        return best_ask - best_bid
    
    def get_depth(self, levels=5):
        """获取前N档深度"""
        sorted_bids = sorted(self.bids.items(), reverse=True)[:levels]
        sorted_asks = sorted(self.asks.items())[:levels]
        return {
            'bids': sorted_bids,
            'asks': sorted_asks
        }

这段代码看着简单,但实际生产环境里,订单簿的更新速度能达到每秒几千次。我建议你在做数据压缩时,重点关注 update 方法的效率——字典操作虽然快,但频繁的插入删除也会产生内存碎片。

1.6 为什么订单簿对数据压缩重要?

你想想看,高频交易的数据量有多大?一个交易所每秒可能产生几万条订单簿更新。如果每条都完整记录,一天下来就是几个 GB。

但订单簿有个特点:大部分时间只有少数档位在变化。比如价差附近的几档变动频繁,而远离中间价的档位可能几分钟都不动一次。这就是压缩的突破口。

我个人习惯的做法是:只记录变化的部分,然后用增量编码压缩。具体怎么实现,后面的章节会详细讲。

一句话总结: 订单簿是高频数据的「心脏」。不理解它的结构,后面的压缩算法全是空中楼阁。


公众号:蓝海资料掘金营,微信deep3321