3. 订单簿的构建:限价单、市价单、订单簿数据结构

好,咱们进入正题。订单簿这东西,说白了就是交易所的「记账本」。它记录着所有交易者想买、想卖的意愿。我个人习惯把订单簿看作市场的骨架——价格发现、流动性、盘口博弈,全在这上面展开。

你想想看,如果没有订单簿,集合竞价就变成了瞎猜。谁愿意在什么价格买多少,谁愿意在什么价格卖多少,这些信息必须有个地方存着、算着。这个地方,就是订单簿。

3.1 限价单:市场的「钉子」

限价单,英文叫 Limit Order。它的核心就一句话:指定价格,等待成交

比如我说:「我出 10.00 元买 100 股茅台,低于这个价我不买。」这就是一个买入限价单。反过来,我说:「我 10.50 元卖 200 股,低于这个价我不卖。」这就是卖出限价单。

我在项目中遇到过一个问题:有人觉得限价单就是「挂单等成交」,其实没那么简单。限价单有两个关键属性:

  • 价格优先:买价高的先成交,卖价低的先成交。这是铁律。
  • 时间优先:同样价格,先挂单的先成交。这是公平。

嗯,这里要注意:限价单不一定能成交。如果你的买价挂得太低,或者卖价挂得太高,可能一天下来纹丝不动。这就是所谓的「流动性提供者」——你挂单,别人来吃。

避坑指南:我曾经在实盘里挂了一个限价单,价格只比当前最优价低了 1 分钱。结果行情波动没碰到我的单,收盘时订单还挂着。第二天开盘跳空低开,直接成交在了亏损位置。限价单不是「保险单」,它只是「条件单」。

3.2 市价单:市场的「推土机」

市价单,Market Order。它的逻辑更简单:不管价格,立即成交

你说:「我现在就要买 1000 股,多少钱都行。」系统就会从最优卖价开始,一路扫过去,直到买够 1000 股为止。

市价单的特点是:

  • 成交确定性高:只要盘口有足够的对手盘,几乎瞬间成交。
  • 价格不确定性高:如果流动性不足,你可能吃到很差的价位。

说白了,市价单是「流动性消耗者」。你冲进去,把别人的挂单吃掉。我见过不少新手喜欢用市价单,觉得「快」。但遇到大单或者流动性枯竭时,市价单会让你吃大亏。

警告:在集合竞价阶段,很多交易所不允许市价单参与。因为集合竞价的核心是「价格发现」,需要大家报出真实意愿。市价单会破坏这个机制。具体规则要看交易所的细则。

3.3 订单簿的数据结构

好,现在咱们聊聊怎么在代码里实现订单簿。这玩意儿看着简单,但坑不少。

一个标准的订单簿,通常包含两个「堆」:

  • 买盘(Bid):按价格从高到低排列。最高价在最上面。
  • 卖盘(Ask):按价格从低到高排列。最低价在最上面。

每个价格档位上,会记录该价格的总委托量。比如:

方向 价格 数量 订单数
10.05 500 3
10.04 200 1
10.03 800 5
10.02 600 4
10.01 300 2
10.00 1000 7

这个表格就是订单簿的「快照」。但实际运行时,我们需要一个能动态增删改查的数据结构。

我个人习惯用 红黑树跳表 来实现价格维度的排序。为什么?因为订单簿的核心操作是:

  • 插入一个新订单(O(log n))
  • 删除一个已成交或撤单的订单(O(log n))
  • 查询最优买卖价(O(1))

用数组?不行。插入和删除太慢。用哈希表?也不行。没法按价格排序。红黑树和跳表是工业界的主流选择。

下面是一个简化版的订单簿数据结构,用 Python 示意:

class OrderBook:
    def __init__(self):
        self.bids = {}  # 价格 -> 总量
        self.asks = {}  # 价格 -> 总量
        self.orders = {}  # 订单ID -> 订单详情

    def add_order(self, order):
        # 限价单逻辑
        if order.type == 'limit':
            if order.side == 'buy':
                self.bids[order.price] = self.bids.get(order.price, 0) + order.qty
            else:
                self.asks[order.price] = self.asks.get(order.price, 0) + order.qty
            self.orders[order.id] = order

    def remove_order(self, order_id):
        order = self.orders.pop(order_id, None)
        if order:
            if order.side == 'buy':
                self.bids[order.price] -= order.qty
                if self.bids[order.price] <= 0:
                    del self.bids[order.price]
            else:
                self.asks[order.price] -= order.qty
                if self.asks[order.price] <= 0:
                    del self.asks[order.price]

    def best_bid(self):
        return max(self.bids.keys()) if self.bids else None

    def best_ask(self):
        return min(self.asks.keys()) if self.asks else None
提示:上面的代码只是教学演示。实际生产环境中,你需要考虑并发安全、内存管理、性能优化。我见过用 C++ 写的订单簿,单线程每秒能处理百万级订单。Python 嘛,做做回测还行,实盘就别想了。

3.4 订单簿的核心逻辑:撮合

有了订单簿,下一步就是撮合。撮合的逻辑其实就一句话:当买价 ≥ 卖价时,成交

具体步骤:

  1. 新订单进来,判断是买还是卖。
  2. 如果是买单,看最优卖价(最低的 ask)。如果买单价格 ≥ 最优卖价,成交。
  3. 成交后,更新订单簿。如果买单还有剩余,继续与下一个卖价撮合。
  4. 直到买单价格 < 最优卖价,或者买单完全成交,停止。

卖单的逻辑对称,反过来就行。

这里有个细节:价格优先 + 时间优先。同一价格档位上,先挂的单先成交。所以订单簿里每个价格档位其实是一个队列(FIFO)。

我在做高频交易系统时,遇到过一个问题:如果两个订单同时到达,谁先谁后?这取决于交易所的撮合引擎。有的交易所按「接收时间」排序,有的按「系统时间戳」。嗯,这里水很深,咱们后面章节再细聊。

3.5 知识体系总览

下面我用一张图来总结本章的核心内容。这张图展示了订单簿的构建、订单类型、以及撮合逻辑之间的关系。

订单簿知识体系 限价单 指定价格 等待成交 价格优先 + 时间优先 市价单 立即成交 价格不确定 消耗流动性 订单簿数据结构 买盘(Bid) 卖盘(Ask) 红黑树 / 跳表 撮合逻辑 买价 ≥ 卖价 → 成交 价格优先 → 时间优先 → 逐层撮合 输出:成交记录 + 订单簿快照 用于价格发现、盘口分析

这张图把咱们今天讲的内容串起来了。限价单和市价单是输入,订单簿是存储结构,撮合逻辑是处理引擎,最终输出成交记录和订单簿快照。集合竞价的价格发现,就是在这个框架下完成的。

核心要点:订单簿不是简单的「价格-数量」映射。它是一个动态系统,需要高效的数据结构来支撑。限价单提供流动性,市价单消耗流动性。两者共同决定了市场的深度和宽度。

好了,这一章就到这里。订单簿的构建是后续所有内容的基础。下一章咱们会深入集合竞价的具体流程,看看订单簿在开盘前是怎么「预热」的。


专注资料整理