一、订单簿系统概述

什么是订单簿

订单簿,说白了就是一张「买卖挂单表」。

我刚开始接触金融系统时,觉得这名字挺唬人。后来发现,它本质上就是一个实时更新的价格-数量映射表。买方报出想买的价格和数量,卖方报出想卖的价格和数量,系统把这些订单按价格排序,就形成了订单簿。

举个例子。假设现在有几个人想买苹果:

  • A 说:我出 5 元,买 10 个
  • B 说:我出 4.9 元,买 20 个
  • C 说:我出 4.8 元,买 15 个

另外几个人想卖:

  • X 说:我 5.1 元卖 10 个
  • Y 说:我 5.2 元卖 25 个
  • Z 说:我 5.3 元卖 30 个

把这些信息按价格排好,就是订单簿。买方这边叫「买盘」(Bid Side),卖方那边叫「卖盘」(Ask Side)。中间那个价格差,叫「价差」(Spread)。

核心要点:订单簿是市场微观结构的核心载体,它记录了所有未成交的限价订单。

订单簿在金融交易中的核心作用

订单簿到底有多重要?我这么跟你说吧——没有订单簿,交易所就没办法运转。

它的核心作用有三个:

  1. 价格发现——买卖双方通过订单簿博弈,最终形成市场公认的价格。我在做期货系统时,经常盯着订单簿看,能明显感觉到大资金在某个价位「筑墙」。
  2. 流动性展示——订单簿告诉你,在什么价位能买到多少货。深度好的市场,大单砸进去价格波动很小;深度差的市场,几万块就能把价格打飞。
  3. 交易撮合——系统根据订单簿的排队规则,把买单和卖单配对。谁先来、谁价优,谁就先成交。

嗯,这里要注意一点:订单簿展示的是「限价单」,不是所有订单。市价单不会出现在订单簿里,它直接跟订单簿上的对手单成交。

常见订单类型

做交易系统这么多年,我接触过的订单类型少说也有几十种。但最核心的,其实就两种。

限价单(Limit Order)

限价单就是「指定价格下单」。你说 5 元买,那就只能 5 元或更低的价格成交。卖单同理。

限价单会进入订单簿排队。价格优先,时间其次。什么意思?

  • 买单:出价高的排前面
  • 卖单:出价低的排前面
  • 价格相同:先来的排前面

我曾经遇到过一个坑:某个客户下了大量限价单,价格设得跟市场价差 1 分钱。结果行情剧烈波动,这些单子全部成交,客户亏损严重。所以限价单不是「安全单」,它只是「指定价格单」。

市价单(Market Order)

市价单不指定价格,只指定数量。系统会以当前最优价格帮你成交。

比如你想买 100 股,当前卖一价是 5.1 元,卖二价是 5.2 元。市价买单会先吃掉卖一的 50 股,再吃掉卖二的 50 股。成交均价是 (5.1×50 + 5.2×50) / 100 = 5.15 元。

避坑提醒:市价单在流动性不足时,可能会以很差的价格成交。我见过有人下市价大单,直接把价格打穿了好几个档位,多花了几十万。这就是所谓的「滑点」。

订单簿数据结构

好了,理论说完了,咱们看看实际怎么存。

订单簿的数据结构,核心就是两个队列:

  • 买盘队列(Bid Queue):按价格降序排列(出价高的在前)
  • 卖盘队列(Ask Queue):按价格升序排列(出价低的在前)

每个队列里的元素,通常包含:价格、总数量、订单列表。

我习惯用这样的结构来表示:

// 订单簿核心数据结构(伪代码)
struct OrderBook {
    PriceLevel[] bids;  // 买盘,按价格降序
    PriceLevel[] asks;  // 卖盘,按价格升序
}

struct PriceLevel {
    double price;           // 价格
    long totalQuantity;     // 该价格总数量
    Order[] orders;         // 该价格下的订单列表(按时间排序)
}

struct Order {
    string orderId;         // 订单ID
    string side;            // 方向:buy/sell
    double price;           // 价格(市价单为0)
    long quantity;          // 数量
    long filledQuantity;    // 已成交数量
    long timestamp;         // 下单时间戳
}

你想想看,为什么买盘要降序、卖盘要升序?

因为撮合时,系统要快速找到「最优价格」。买单的最高价、卖单的最低价,就是最优价。所以买盘队列头是最高价,卖盘队列头是最低价。这样取第一个元素就能拿到最优价,时间复杂度 O(1)。

个人经验:实际生产环境中,我通常用跳表(Skip List)或红黑树来实现价格队列。因为除了取最优价,还需要支持:插入新订单、撤销订单、修改订单、查询某个价格档位。这些操作如果用数组,性能会很难看。

下面这张图,是我画的一个简化版订单簿结构:

订单簿数据结构示意图 买盘队列(Bid) 价格降序排列 价格: 5.00 数量: 150 价格: 4.99 数量: 230 价格: 4.98 数量: 85 价格: 4.97 数量: 310 ↓ 价格递减 价差 0.01 卖盘队列(Ask) 价格升序排列 价格: 5.01 数量: 120 价格: 5.02 数量: 200 价格: 5.03 数量: 175 价格: 5.04 数量: 90 ↑ 价格递增 最优买价: 5.00 最优卖价: 5.01

这张图里,买盘最高价是 5.00,卖盘最低价是 5.01。中间差了 0.01 元,这就是价差。任何一笔新的买单,如果价格 ≥ 5.01,就会立即跟卖盘成交;反之,如果卖单价格 ≤ 5.00,就会跟买盘成交。

我记得有一次做性能优化,发现订单簿的插入操作成了瓶颈。后来把价格队列从数组换成了跳表,插入性能提升了 3 倍。所以数据结构选型,真的会影响整个系统的吞吐量。

总结一下:订单簿就是买卖双方的「价格-数量」映射表。买盘降序、卖盘升序,中间是价差。限价单进队列排队,市价单直接吃对手单。数据结构上,价格队列用跳表或红黑树,订单列表用链表或数组,具体看业务场景。

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