第二章 订单生命周期:从下单到成交的完整流程
好,咱们直接进入正题。这一章我带你走一遍订单的完整旅程——从用户点击「买入」按钮,到最终成交记录落库。说白了,就是一笔订单在撮合引擎里到底经历了什么。
我个人习惯把订单生命周期分成四个阶段:接收→校验→撮合→结算。每个阶段都有坑,我一个个讲。
2.1 订单的诞生:接收与校验
用户下单,请求先到网关层。网关做两件事:格式校验和风控检查。
格式校验很简单——价格是不是正数?数量是不是大于0?交易对存不存在?这些要是不过,直接打回。
风控检查就有点意思了。我记得有一次,一个用户用API连续下了几百笔小单,每笔0.0001个BTC。单看每笔都没问题,但加起来明显是刷单行为。我们后来加了个「单位时间内下单频率」的检查,才把这问题按住。
2.2 订单状态机:一张图说清楚
订单状态机,说白了就是订单的「人生轨迹」。我直接给你看核心状态:
| 状态 | 含义 | 触发条件 |
|---|---|---|
| NEW | 新订单,等待撮合 | 校验通过后 |
| PARTIALLY_FILLED | 部分成交 | 撮合时只成交了一部分 |
| FILLED | 完全成交 | 订单全部成交 |
| CANCELED | 已撤销 | 用户主动撤单 |
| REJECTED | 被拒绝 | 校验失败 |
这里有个细节:PARTIALLY_FILLED 状态其实可以反复出现。比如你挂了一笔100个ETH的买单,市场先给你成交了30个,状态变成PARTIALLY_FILLED;过一会儿又成交了50个,状态还是PARTIALLY_FILLED;直到最后20个也成交了,才变成FILLED。
2.3 订单簿:撮合的核心战场
订单簿是什么?说白了就是一个「买家和卖家的报价清单」。买方的报价叫买单队列,卖方的叫卖单队列。
我习惯用两个数据结构来表示:
// 买单队列:价格从高到低排序
buy_orders = [
{price: 100.5, quantity: 10, order_id: "A001"},
{price: 100.3, quantity: 5, order_id: "A002"},
{price: 100.1, quantity: 8, order_id: "A003"}
]
// 卖单队列:价格从低到高排序
sell_orders = [
{price: 100.6, quantity: 7, order_id: "B001"},
{price: 100.8, quantity: 12, order_id: "B002"},
{price: 101.0, quantity: 3, order_id: "B003"}
]
为什么买单要降序、卖单要升序?你想想看——买方当然想用最低价买,但能成交的永远是出价最高的买单;卖方想卖最高价,但能成交的永远是出价最低的卖单。这就是价格优先原则。
2.4 撮合流程:一步步拆解
假设现在来了一个买单:价格100.7,数量5个ETH。
第一步:检查卖单队列的最低价。当前最低卖价是100.6,低于100.7,可以成交。
第二步:成交。卖单B001有7个ETH,买单只要5个。所以成交5个,价格取100.6(卖方价格)。
第三步:更新状态。买单完全成交,变成FILLED。卖单B001还剩2个,状态变成PARTIALLY_FILLED。
嗯,这里要注意:成交价格到底取买方价还是卖方价?不同交易所规则不同。有的取卖方价,有的取买方价,还有的取两者中间价。我个人建议取先进入订单簿的那一方价格,这样逻辑最清晰。
2.5 结算:最后的收尾工作
成交之后,还有一堆事要做:
- 更新账户余额:买方扣钱,卖方扣币
- 生成成交记录:时间、价格、数量、买卖双方ID
- 推送行情数据:最新价、成交量、深度变化
- 通知用户:WebSocket推送成交回报
这里我踩过一个坑:结算顺序。是先更新余额,还是先生成成交记录?如果先更新余额,但生成记录时系统挂了,就会出现「钱扣了但没记录」的情况。正确的做法是:先持久化成交记录,再更新余额。这样即使后续步骤失败,也能通过记录做回滚。
2.6 完整流程的时间线
最后,我给你画个时间线,把整个流程串起来:
- T+0ms:用户下单,网关接收
- T+1ms:格式校验通过
- T+2ms:风控检查通过
- T+3ms:订单进入撮合引擎,状态设为NEW
- T+4ms:撮合引擎找到匹配的卖单,成交
- T+5ms:更新订单状态为FILLED/PARTIALLY_FILLED
- T+6ms:更新账户余额
- T+7ms:生成成交记录,写入数据库
- T+8ms:推送行情和成交回报
整个流程,从下单到用户收到成交通知,理想情况下应该在10毫秒以内完成。如果超过50毫秒,用户就能感觉到卡顿。
好了,订单生命周期就讲到这里。下一章我会深入订单簿的数据结构,讲讲怎么用跳表实现O(log n)级别的价格查询。到时候见。