3. 核心数据结构设计:价格队列模型、价格-订单映射、订单生命周期管理

做量化交易系统,订单簿是绕不开的核心。说白了,它就是交易所用来记录所有挂单的账本。你想想看,每秒成千上万的订单涌进来,怎么存、怎么查、怎么改,直接决定了你的系统能不能跑得快。

我个人习惯把订单簿拆成三个核心模块:价格队列模型价格-订单映射订单生命周期管理。这三个东西搞明白了,订单簿的骨架就搭好了。

3.1 价格队列模型:按价格分桶

先问个问题:为什么不用一个巨大的数组存所有订单?

因为价格是离散的。每个价格点上的订单,其实是一堆人在排队。比如买一价 100.50 元,可能有 10 个人挂单,每人 100 股。这些订单之间没有优先级差异(按时间优先),所以用队列最合适。

我见过不少新手直接拿 std::vector 往里塞,结果撤单时遍历一遍,性能直接崩了。正确的做法是:每个价格对应一个队列

核心思路: 价格作为 key,队列作为 value。买盘和卖盘各维护一个价格队列映射表。

代码示例(C++ 风格):

// 价格队列:每个价格点一个队列
struct PriceLevel {
    double price;
    std::deque<Order> orders;  // 先进先出
};

// 买盘:价格从高到低排序
std::map<double, PriceLevel, std::greater<double>> bidLevels;
// 卖盘:价格从低到高排序
std::map<double, PriceLevel, std::less<double>> askLevels;

这里用 std::map 而不是 unordered_map,是因为我们需要按价格顺序遍历。买盘从高到低,卖盘从低到高,这样才能快速找到最优价格。

小技巧: 如果你用 Java,可以用 TreeMap;Python 的话,sortedcontainers 库的 SortedDict 是个不错的选择。

3.2 价格-订单映射:快速定位

光有价格队列还不够。你想想,用户要撤单怎么办?他只知道订单 ID,不知道价格。如果遍历所有价格队列去找,那复杂度就是 O(N),N 是价格层数乘以每层订单数。这在高频场景下根本扛不住。

所以我们需要一个反向索引:订单 ID → 价格。

我在项目中遇到过一个问题:撤单请求来了,我得先找到这个订单在哪个价格队列里,然后从队列里删掉它。如果队列是 std::deque,删除中间元素是 O(n) 的。后来我换成了 std::list,并且把迭代器存下来,删除就变成 O(1) 了。

代码示例:

// 订单结构体
struct Order {
    int64_t id;
    double price;
    int64_t quantity;
    int64_t timestamp;
    // 指向队列中位置的迭代器
    std::list<Order>::iterator it;
};

// 价格-订单映射
std::unordered_map<int64_t, Order> orderMap;

这样设计的好处是:

  • 新增订单:插入到价格队列尾部,同时记录迭代器到 orderMap
  • 撤单:从 orderMap 拿到迭代器,直接 O(1) 删除
  • 查单:通过 orderMap 直接定位,不用遍历
注意: 迭代器在插入后可能会失效,取决于容器类型。用 std::list 是安全的,但 std::deque 在中间插入会导致迭代器失效。我建议用 std::list 或自己实现一个 intrusive list。

3.3 订单生命周期管理:从生到死

一个订单从创建到成交或撤单,会经历几个状态。我习惯用状态机来管理:

状态 说明 触发动作
NEW 新订单,刚进入系统 收到下单请求
PARTIAL 部分成交 与对手单匹配,未完全成交
FILLED 完全成交 剩余数量为 0
CANCELLED 已撤单 用户主动撤单
REJECTED 被拒绝 风控检查不通过

状态流转图:

NEW PARTIAL FILLED CANCELLED REJECTED

我曾经踩过一个坑:订单部分成交后,剩余数量更新了,但忘记更新价格队列里的数量统计。结果盘口显示的量是错的,被交易所警告了。所以每次状态变化,都要同步更新三个地方:

  1. 订单本身:剩余数量、状态
  2. 价格队列:该价格的总量、订单数
  3. 映射表:如果价格变了(比如改单),要重新插入队列
实战建议: 我习惯把订单生命周期做成一个回调链。每个状态变化触发一个事件,比如 onOrderNewonOrderFill。这样风控、统计、日志都可以挂上去,代码解耦得很干净。

3.4 三者如何协作

这三个模块不是孤立的。我画个图帮你理解:

价格队列模型 按价格分桶存储 价格-订单映射 订单ID → 价格/迭代器 订单生命周期 状态机 + 事件回调

举个例子你就明白了:

  • 一个新订单进来,先通过生命周期管理创建状态为 NEW
  • 然后根据价格,插入到价格队列模型中对应的队列尾部
  • 同时把订单 ID 和迭代器存入价格-订单映射
  • 如果后续有撤单请求,通过映射找到迭代器,直接从队列删除,状态改为 CANCELLED

嗯,这套设计我用了好几年,在多个项目里验证过。吞吐量做到每秒 10 万笔订单没问题,前提是内存要够,别频繁 GC。

避坑指南: 我曾经在并发场景下忘了加锁,结果两个线程同时修改同一个价格队列,数据全乱了。后来改用无锁队列 + CAS 操作才搞定。如果你用 C++,可以考虑 boost::lockfree;Java 的话 ConcurrentLinkedDeque 是个选择。

好了,核心数据结构就这些。说白了就是三个东西:按价格分桶存订单、用映射表快速定位、用状态机管理生命周期。搞懂了它们,订单簿的骨架就有了。

专注资料整理