3、订单簿数据结构:价格优先、时间优先原则,Level 1/2/3 数据层级

好,咱们今天聊聊订单簿的数据结构。这东西听起来有点枯燥,但说白了,它就是交易所用来撮合买卖双方的那张「记账本」。我做了这么多年量化,可以负责任地告诉你——不理解订单簿结构,你写的策略就是空中楼阁。

3.1 价格优先、时间优先:撮合的铁律

先讲规则。交易所撮合订单,遵循两个基本原则:

  • 价格优先:买单出价高的排前面,卖单出价低的排前面。
  • 时间优先:价格相同的情况下,先来的订单排前面。

嗯,就这么简单。但实际执行起来,细节挺多。

核心逻辑:买单从高往低排,卖单从低往高排。两边一碰,能成交的就立刻成交。

举个例子。假设当前订单簿是这样的:

方向 价格 数量 时间戳
卖单 100.05 200 09:30:01.001
卖单 100.04 100 09:30:00.500
卖单 100.03 150 09:30:00.200
—— —— —— ——
买单 100.02 80 09:30:00.100
买单 100.01 300 09:30:00.050
买单 100.00 500 09:29:59.800

这时候来了一个市价买单,要买300股。它会怎么成交?

先打掉最便宜的卖单——100.03的150股。还剩150股,继续打100.04的100股。还剩50股,再打100.05的50股。成交完毕。

你看,价格优先体现在:卖单里价格最低的先成交。时间优先体现在:如果两个卖单价格一样,先挂单的先成交。

我的经验:做高频策略时,时间优先这个细节特别重要。我曾经因为没处理好订单的时间戳精度,导致模拟盘和实盘结果差了十万八千里。后来才发现,交易所的时间戳是纳秒级的,而我用的数据只精确到毫秒。

3.2 订单簿的数据结构:用啥来存?

订单簿本质上是一个价格-数量映射。每个价格档位上,挂了多少单,总量是多少。

我个人习惯用红黑树来实现。为什么?

  • 插入和删除都是 O(log n) 复杂度
  • 天然有序,可以快速找到最优买卖价
  • 支持范围查询,比如查某个价格区间内的总挂单量

代码实现大概长这样:

// 用C++实现一个简单的订单簿
class OrderBook {
private:
    // 买单:价格从高到低排序
    std::map<double, OrderInfo, std::greater<double>> bids;
    // 卖单:价格从低到高排序
    std::map<double, OrderInfo, std::less<double>> asks;
    
public:
    void addOrder(Order order) {
        if (order.side == BUY) {
            bids[order.price].quantity += order.quantity;
        } else {
            asks[order.price].quantity += order.quantity;
        }
    }
    
    void removeOrder(Order order) {
        // 从对应价格档位减去数量
        // 如果数量归零,删除该价格档位
    }
    
    double getBestBid() {
        return bids.empty() ? 0 : bids.begin()->first;
    }
    
    double getBestAsk() {
        return asks.empty() ? 0 : asks.begin()->first;
    }
};

当然,Python里可以用 sortedcontainers 库,或者直接用 heapq 维护优先队列。但说实话,做高频交易的话,还是C++更靠谱。

注意:千万别用普通的哈希表来存订单簿。虽然查询快,但你要频繁获取「最优价格」,哈希表没法直接做到有序遍历。我曾经见过有人用Python的dict硬搞,结果每次都要排序,性能惨不忍睹。

3.3 Level 1/2/3 数据层级:你到底需要多细?

交易所提供的数据,一般分三个层级。我刚开始做量化时,傻傻分不清,后来吃了不少亏。

Level 1:最粗的行情快照

只包含:

  • 最新成交价
  • 最高价、最低价
  • 开盘价、收盘价
  • 成交量、成交额
  • 最优买卖价(即买一、卖一)

说白了,就是给你看一眼当前市场的大致情况。做长线策略够用了,但做短线?嗯,远远不够。

Level 2:深度行情

这个就详细多了。它包含:

  • 买卖各档位的价格和挂单量(通常是5档、10档或50档)
  • 逐笔成交数据
  • 市场统计信息

我大部分策略都用Level 2数据。它能让你看到「订单簿的轮廓」——比如买一挂了1000手,但买二只有50手,说明这个支撑位其实很脆弱。

Level 3:全量订单簿

这是最细的数据。它包含:

  • 所有未成交订单的详细信息
  • 每个订单的ID、价格、数量、时间戳
  • 订单的增删改操作流

说白了,Level 3能让你重建整个订单簿的完整历史。每一笔挂单、撤单、成交,你都能追踪到。

关键区别:Level 2告诉你「当前价格档位上有多少单」,Level 3告诉你「这些单是谁挂的、什么时候挂的、有没有撤单」。Level 3的数据量是Level 2的几十倍甚至上百倍。

我举个例子。假设你看到买一挂了1000手,但下一秒突然撤单了。用Level 2数据,你只能看到「买一消失了」。用Level 3数据,你能看到「某个机构账户在09:30:00.001挂了1000手,又在09:30:00.500撤单了」——这信息量完全不一样。

避坑指南:我曾经为了省带宽费,只订阅Level 2数据做高频策略。结果发现策略在模拟盘上跑得好好的,一上实盘就亏钱。后来排查才发现,有些大单在Level 2里根本看不到——它们挂在更深的档位,但Level 2只给你看前5档。从那以后,做高频策略我至少用Level 2+逐笔成交,有条件就上Level 3。

3.4 三种层级的对比

特性 Level 1 Level 2 Level 3
数据量 极小 中等 极大
更新频率 秒级 毫秒级 微秒级
包含信息 最优买卖价 多档深度 全量订单
适用策略 长线、低频 中频、短线 高频、做市
成本 免费或极低 中等 昂贵

3.5 一张图看懂订单簿结构

下面我用SVG画了一张图,把订单簿的核心逻辑展示出来。你看完应该就明白了。

订单簿数据结构核心逻辑 买单 (Bids) 价格从高到低排序 买一: 100.02 80股 买二: 100.01 300股 买三: 100.00 500股 卖单 (Asks) 价格从低到高排序 卖一: 100.03 150股 卖二: 100.04 100股 卖三: 100.05 200股 撮合引擎 价格优先: 买单最高价 vs 卖单最低价 时间优先: 同价格下先到先成交 数据层级 Level 1: 最优买卖价 Level 2: 多档深度 Level 3: 全量订单

这张图把整个流程串起来了。买单和卖单分别按价格排序,撮合引擎在中间做匹配。数据层级从粗到细,你根据自己的策略需求选就行。

3.6 实际开发中的选择建议

说了这么多,到底该用哪个层级?我根据经验给个建议:

  • 做日频、周频策略:Level 1就够了。别浪费钱买深度数据。
  • 做日内短线:至少Level 2,最好加上逐笔成交。你能看到订单簿的「呼吸」。
  • 做高频、做市商:必须上Level 3。没有全量订单簿,你就是在盲人摸象。

再次提醒:Level 3数据不是你想买就能买的。很多交易所要求你提供做市商资质,或者最低交易量。而且数据带宽消耗巨大,我见过有人买了Level 3数据,结果服务器带宽被打满,反而丢了行情。嗯,这又是一个坑。

好了,订单簿的数据结构就聊到这儿。记住一句话:数据结构决定了你能看到什么,数据层级决定了你能看到多细。选对了,事半功倍;选错了,事倍功半。


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