订单簿数据结构:价格优先与时间优先原则

做量化交易这些年,我见过不少新手一上来就急着写策略,结果连订单簿都搞不清楚。说白了,订单簿就是市场的「记账本」——谁想买、谁想卖、什么价格、多少量,全记在上面。今天咱们就把这个记账本的结构彻底讲透。

价格优先与时间优先原则

交易所撮合订单,靠的就是这两条铁律。我刚开始接触时觉得很简单,后来在实盘中发现,很多坑都出在对这个原则的理解不够深。

价格优先:买方出价高的优先成交,卖方出价低的优先成交。这很好理解——你出100块买,别人出99块,那肯定先满足你。

时间优先:价格相同时,先挂单的先成交。嗯,这里要注意:时间优先只在同一价格层级内有效。

举个实际例子:假设当前买一价是10.00元,挂单量1000股。你挂10.01元买入500股,另一个交易员也挂10.01元买入300股,但比你晚0.1秒。那么你的500股会先成交,他的300股排在你后面。

我曾经在回测中忽略了这个细节,结果策略在模拟中表现完美,实盘却频频滑点。后来一查,原来是订单簿的排队逻辑没写对。

订单簿的增删改查操作

订单簿本质上就是一个动态数据结构。我们需要支持四种基本操作:

  • 增加订单:新订单进入队列
  • 删除订单:撤单或成交后移除
  • 修改订单:改价格或数量(实际是撤单+重新挂单)
  • 查询订单:查看当前最优买卖价、深度等

这里有个坑:修改订单在交易所层面通常不是直接改,而是先撤单再重新挂单。这意味着你的时间优先级会丢失。我见过有人写策略时直接修改订单,结果排队位置变了,成交率大幅下降。

使用Python字典与列表模拟订单簿

好了,理论说完了,咱们直接上代码。我个人习惯用字典来存储价格层级,用列表来管理同一价格下的订单队列。

class OrderBook:
    def __init__(self):
        # 买盘:价格从高到低排序
        self.bids = {}  # 价格 -> 订单队列
        self.bid_prices = []  # 维护价格顺序
        
        # 卖盘:价格从低到高排序
        self.asks = {}
        self.ask_prices = []
    
    def add_order(self, side, price, quantity, order_id):
        """增加订单"""
        book = self.bids if side == 'buy' else self.asks
        prices = self.bid_prices if side == 'buy' else self.ask_prices
        
        if price not in book:
            book[price] = []
            prices.append(price)
            # 买盘降序,卖盘升序
            prices.sort(reverse=(side == 'buy'))
        
        book[price].append({
            'id': order_id,
            'price': price,
            'quantity': quantity,
            'timestamp': time.time()
        })
    
    def remove_order(self, side, price, order_id):
        """删除订单"""
        book = self.bids if side == 'buy' else self.asks
        if price in book:
            book[price] = [o for o in book[price] if o['id'] != order_id]
            if not book[price]:
                del book[price]
                prices = self.bid_prices if side == 'buy' else self.ask_prices
                prices.remove(price)
    
    def get_best_price(self, side):
        """查询最优价格"""
        prices = self.bid_prices if side == 'buy' else self.ask_prices
        if not prices:
            return None
        return prices[0]
    
    def get_depth(self, levels=5):
        """获取深度数据"""
        bids_depth = []
        for price in self.bid_prices[:levels]:
            total_qty = sum(o['quantity'] for o in self.bids[price])
            bids_depth.append((price, total_qty))
        
        asks_depth = []
        for price in self.ask_prices[:levels]:
            total_qty = sum(o['quantity'] for o in self.asks[price])
            asks_depth.append((price, total_qty))
        
        return {'bids': bids_depth, 'asks': asks_depth}

这段代码虽然简单,但已经能跑通基本的订单簿逻辑了。我在实际项目中,还会加上成交撮合的逻辑,不过那是后面章节的内容。

小技巧:生产环境中,建议用双向链表或跳表来维护价格层级,而不是用列表排序。列表排序在订单量大的时候性能会急剧下降。我曾在一次压力测试中,用列表排序处理10万笔订单,结果跑了3分钟还没跑完。

订单簿的核心结构图

下面这张图展示了订单簿的完整结构。你可以看到买盘和卖盘是如何组织的,以及价格优先和时间优先是如何体现在数据结构中的。

订单簿数据结构 买盘 (Bids) 价格从高到低排序 价格: 10.05 订单队列: [ID1, ID2, ID3] 时间优先: ID1 → ID2 → ID3 价格: 10.04 订单队列: [ID4, ID5] 时间优先: ID4 → ID5 价格: 10.03 订单队列: [ID6] 时间优先: ID6 价格: 10.02 订单队列: [ID7, ID8, ID9, ID10] 时间优先: ID7 → ID8 → ID9 → ID10 卖盘 (Asks) 价格从低到高排序 价格: 10.06 订单队列: [ID11, ID12] 时间优先: ID11 → ID12 价格: 10.07 订单队列: [ID13, ID14, ID15] 时间优先: ID13 → ID14 → ID15 价格: 10.08 订单队列: [ID16] 时间优先: ID16 价格: 10.09 订单队列: [ID17, ID18] 时间优先: ID17 → ID18 价格优先:买盘高价优先,卖盘低价优先 | 时间优先:同价格先到先得
注意:上面的代码示例是教学用的简化版本。在实际的高频交易场景中,订单簿的更新速度可能达到每秒数万次,这时候Python的字典和列表就不够用了。我建议你至少用collections.deque来管理订单队列,或者直接上C++扩展。

说实话,订单簿的数据结构看起来简单,但真正用好它需要理解市场微观结构。比如,为什么有些交易员喜欢在买一价挂大单?为什么撤单率高的股票往往流动性差?这些问题的答案,都藏在订单簿的增删改查里。

好了,这一章的内容就到这里。记住:订单簿是市场的脉搏,读懂它,你就能听懂市场在说什么。


专注资料整理