3、订单管理模块:订单生命周期管理、订单簿维护、订单路由、FIX协议实现

订单管理模块,说白了就是做市商系统的「心脏」。

你想想看,我们每天处理成千上万笔订单,每一笔从生到死,状态怎么变?数据怎么存?怎么跟交易所打交道?这些搞不定,系统就是一堆废铁。

我个人习惯把订单管理拆成四个核心部分来讲:生命周期、订单簿、路由、FIX协议。咱们一个一个啃。

3.1 订单生命周期管理

每一笔订单,从创建到成交或取消,都会经历一系列状态变化。我把它叫做「订单的八十一难」。

核心状态机:

  • New(新建):订单刚进来,还没处理。
  • PendingNew(待处理):系统正在往交易所发,还没确认。
  • Accepted(已接受):交易所确认收到了。
  • PartiallyFilled(部分成交):成交了一部分,剩下的还在挂。
  • Filled(全部成交):全干完了。
  • Canceled(已撤销):用户或系统主动取消。
  • Rejected(已拒绝):交易所说不行,比如价格超限。
  • Expired(已过期):比如GTC订单到了交易日结束。

我在项目中遇到过一个问题:订单状态更新不及时,导致风控系统误判。后来我们加了个「状态变更日志」,每笔状态变化都记时间戳和原因。嗯,这招很管用。

避坑指南:我曾经因为状态机设计不严谨,导致「已撤销」的订单还能被成交回报覆盖。后来我强制要求:状态变更只能单向推进,不允许回退。除非是「PendingNew」回退到「New」这种特殊情况。

代码实现上,我建议用枚举+状态模式。别用一堆if-else,后期维护会想哭。

// 订单状态枚举
public enum OrderStatus {
    NEW,
    PENDING_NEW,
    ACCEPTED,
    PARTIALLY_FILLED,
    FILLED,
    CANCELED,
    REJECTED,
    EXPIRED
}

// 状态变更接口
public interface OrderState {
    void onNew(Order order);
    void onAccept(Order order);
    void onFill(Order order, long filledQty);
    void onCancel(Order order);
    void onReject(Order order, String reason);
}

3.2 订单簿维护

订单簿,就是买卖双方的「排队簿」。做市商靠它来管理自己的挂单和市场的深度。

说白了,订单簿就是两个价格序列:买盘(Bid)和卖盘(Ask)。每个价格上挂了多少量,一目了然。

订单簿核心数据结构:

  • 价格档位(Price Level):每个价格一个档位。
  • 总量(Total Qty):该价格上所有订单的总量。
  • 订单列表(Order List):按时间排序的订单明细。
  • 最佳买卖价(Best Bid/Ask):最高买价和最低卖价。

我建议用跳表(Skip List)或红黑树来实现价格档位的排序。为什么?因为订单簿需要频繁地插入、删除、查询,这些数据结构能做到O(log n)。

注意:千万别用数组或链表硬撸。我见过一个团队用ArrayList存订单簿,每次插入都O(n),行情一快直接崩了。

订单簿维护的核心操作就四个:

  1. 新增订单:找到对应价格档位,插入订单列表。
  2. 撤销订单:从价格档位中移除订单,如果档位空了就删掉。
  3. 成交回报:减少对应价格档位的量,如果量归零就删档位。
  4. 价格变动:比如改价,本质是先撤单再重新挂单。

这里有个细节:做市商的订单簿和交易所的订单簿是两回事。我们维护的是「自己的挂单簿」,用来跟踪我们自己在各个交易所的挂单情况。别搞混了。

3.3 订单路由

订单路由,就是决定「这笔单子往哪个交易所发」。做市商通常对接多个交易所,路由策略直接决定了成交速度和成本。

我个人习惯把路由策略分为三类:

策略类型 说明 适用场景
价格优先 哪个交易所价格好,就往哪发 套利、流动性分散
时间优先 哪个交易所响应快,就往哪发 高频做市、抢单
比例分配 按固定比例分配到各交易所 风控、流动性均衡

你想想看,如果只盯着价格,忽略了延迟,可能等你发过去价格已经变了。所以实际项目中,我通常用「价格+延迟加权」的混合策略。

避坑指南:我曾经在路由模块里忘了处理「交易所故障」的情况。结果一个交易所挂了,订单全往那发,全部被拒。后来我加了健康检查:每个交易所维护一个「可用状态」,路由前先检查。

路由模块的架构,我建议做成插件化。每个交易所一个路由插件,统一接口。这样新增交易所时,只需要写一个插件,不用改核心逻辑。

// 路由插件接口
public interface RoutingPlugin {
    boolean isAvailable();
    long estimateLatency();
    double estimateCost();
    void route(Order order);
}

// 路由决策引擎
public class RoutingEngine {
    private List<RoutingPlugin> plugins;
    
    public void route(Order order) {
        // 按策略选择最优交易所
        RoutingPlugin best = selectBest(order);
        best.route(order);
    }
}

3.4 FIX协议实现

FIX协议,金融界的「通用语言」。做市商跟交易所通信,基本都靠它。

FIX协议说白了就是一堆键值对,用分隔符拼起来。比如:

8=FIX.4.2|9=78|35=D|49=CLIENT1|56=EXCHANGE|34=1|52=20240101-00:00:00|11=ORD001|55=BTC/USD|54=1|38=100|44=50000|10=123|

每个标签都有含义:35=D表示新订单,55是交易对,54=1表示买,38是数量,44是价格。

我建议不要自己手写FIX解析器。市面上有现成的库,比如QuickFIX/J(Java)、QuickFIX(C++)。直接用,稳定又省事。

FIX协议实现要点:

  • 会话管理:每个交易所一个FIX会话,维护登录、心跳、重连。
  • 消息序列号:每条消息都有序列号,用于检测丢包和重发。
  • 重连机制:网络断了自动重连,并补发丢失的消息。
  • 校验和:每条消息末尾有校验和,防止数据损坏。

我在项目中遇到过一个问题:FIX会话断了,重连后序列号对不上,导致订单状态混乱。后来我们加了「序列号持久化」,每次发送或接收消息都存到数据库。重连时从数据库恢复序列号,完美解决。

注意:FIX协议虽然强大,但不同交易所的实现有差异。比如有的交易所要求35=D的标签顺序必须固定,有的则无所谓。对接前一定要看交易所的FIX规范文档。

下面我用SVG画一张订单管理模块的整体架构图,帮你理清思路。

订单管理模块架构图 订单入口 订单生命周期管理 状态机 · 状态变更日志 · 持久化 订单簿维护 价格档位 · 跳表/红黑树 · 增删改查 订单路由 价格优先 · 时间优先 · 比例分配 · 健康检查 FIX协议实现 · 交易所通信

这张图很直观:订单从入口进来,先经过生命周期管理,状态不断变化。同时订单簿维护着所有挂单的实时数据。路由模块决定往哪个交易所发,最后通过FIX协议跟交易所通信。

嗯,订单管理模块就讲到这里。核心就是四个字:状态、数据、路由、通信。把这四个搞明白了,整个模块的骨架就有了。