技术博客

委托生命周期状态机:从已报到已成交/已撤单

第 5 / 141 篇 · 2026-08-16 · secfin · 委托状态机交易系统幂等

委托状态机是柜台系统的心脏,几乎所有账户操作——冻结、解冻、扣款、记持仓——都挂在状态迁移上。状态机的状态不多,难的是迁移的触发源不可控:成交回报会乱序、会重复、会在你撤单的半路上和成交撞上。这篇把完整的状态集和迁移表列出来,再讲那些教科书不写的异常分支。

状态集:不止"已报/已成/已撤"

一笔委托从创建到终结,标准状态集是这些:

状态 含义 终结态
未报 柜台已受理,资金已冻结,尚未报交易所 否
已报 交易所已确认接收,进入订单簿排队 否
部成 部分成交,剩余数量仍在排队 否
已成 全部成交 是
已撤 全部撤单成功(无成交或撤掉全部剩余) 是
部撤 有成交记录后,剩余部分撤单成功 是
废单 被交易所/柜台拒绝,不进入订单簿 是
待确认 发出了请求但没收到回应,真实状态未知 否(伪状态)

部成和部撤要分开是两个状态,而不是"部成 + 已撤"的组合覆盖,因为账务处理不同:部成是活的委托(剩余还冻着资金),部撤是死的委托(要做终态结算:成交部分扣款、未成交部分解冻)。

废单必须带着废单原因落库:资金不足、价格超笼子、股东代码无效、非交易时间……原因码是后续差错排查和监管问询的依据,只记一个"失败"状态等于把信息扔了。

状态迁移表

委托状态迁移图(实线=正常路径,虚线=异常/补偿)未报已报部成已成已撤部撤废单待确认(未知)报盘确认首笔成交累计=委托量撤单成功(无成交)撤单成功(有余量)校验拒绝超时/断线后真实状态未知,须查询补偿

文字版迁移表(触发事件 → 目标状态):

  • 未报 --交易所确认--> 已报;--柜台/交易所拒绝--> 废单
  • 已报 --部分成交--> 部成;--全部成交--> 已成;--撤单确认--> 已撤
  • 部成 --继续成交至满--> 已成;--撤单确认--> 部撤
  • 任何活状态 --网关断线/确认超时--> 待确认(逻辑状态,落库保持原态 + 标志位)

部成状态下继续来部分成交,状态保持部成,只更新累计成交量——状态不变但数据变,这是状态机实现里最容易漏的一类迁移(自环)。

三个折磨人的异常分支

回报乱序。 交易所的成交回报走消息通道,理论上有序,实践中经多跳网关、断线重传之后可能乱序到达:先收到第三笔成交再收到第二笔。如果状态机用"当前状态 + 事件"粗暴迁移,乱序回报会把数量算错。防御手段是两层:一是用交易所的回报序号做顺序校验,发现空洞先缓存后补;二是数量计算用绝对值不用增量——回报里通常带"累计成交量",直接覆盖式更新 filled_qty = report.cum_qty,而不是 filled_qty += report.qty,这样重复回报和乱序回报天然幂等。成交回报篇会把幂等设计展开。

撤单与成交的赛跑。 客户点了撤单,撤单请求还在路上,订单恰好被撮合成交了。结果两种:撤单确认先到、成交回报后到(状态已撤,却又来成交);或反过来。交易所的规则是以撮合为准:撤单请求到达时订单若已成交,撤单被拒绝并回报"撤单失败:已成交"。柜台侧的正确姿势是撤单发出后把委托标记为"撤单中",收到撤单确认才迁移终态;若期间来了成交,按成交优先处理,撤单流程作废。绝不能在"撤单中"就提前解冻资金——撤单失败钱就双花了。

待确认:最危险的状态。 报盘发出后网关断了,交易所到底收没收到?不知道。此时委托在本地是活是死无法判断,这就是分布式系统的两将军问题在证券系统里的具象。处理纪律是:

  1. 本地状态保持原样,加"待确认"标志,冻结资金不动;
  2. 网关恢复后用委托查询接口逐笔核对真实状态;
  3. 查询回报回来前,禁止对该委托做任何账务操作;
  4. 收市仍未确认的,日终用交易所的成交文件兜底核对(文件是权威数据源)。

账务操作挂在迁移边上,不是状态里

状态机本身不记账,账务是迁移的副作用。每个迁移边对应一组明确的账务动作:

  • 未报 → 已报/废单:废单要全额解冻;
  • → 部成/已成:按成交金额把冻结转扣款(买入)或冻结持仓转扣减(卖出),剩余冻结保持;
  • → 已撤/部撤:未成交部分解冻,成交部分按成交结算。

这套"迁移即账务"的映射必须用事务包住:状态 UPDATE 和资金 UPDATE 同生共死。常见的脏数据形态——委托已是"已撤"但资金还冻着——九成是这两张表没在一个事务里,或者批量补偿程序只修了一张。

实现层面的两个建议

状态机显式化。 不要在代码里撒 if status == 'P' then ... 的地雷阵。把迁移表写成数据(状态 × 事件 → 目标状态 + 副作用函数),非法迁移一律拒绝并告警。非法迁移在生产上是重要信号:它意味着回报解析有 bug 或者上游状态异常,静默放过会攒成清算差错。

保留完整的状态历史。 委托主表存当前态,另起一张 order_status_log 追加式记录每次迁移(旧态、新态、触发事件、事件序号、时间戳)。监管调取委托流转记录时要的就是这张表;排查"钱为什么冻着"时,它也是唯一能还原现场的东西。审计留痕那篇会讲日志的不可抵赖设计,这里先把数据留住。

状态机设计对了,后面讲成交回报幂等、撤单成本、日终核对时你会反复受益;设计错了,它会在每个异常交易日用资损提醒你。