委托状态机是柜台系统的心脏,几乎所有账户操作——冻结、解冻、扣款、记持仓——都挂在状态迁移上。状态机的状态不多,难的是迁移的触发源不可控:成交回报会乱序、会重复、会在你撤单的半路上和成交撞上。这篇把完整的状态集和迁移表列出来,再讲那些教科书不写的异常分支。
状态集:不止"已报/已成/已撤"
一笔委托从创建到终结,标准状态集是这些:
| 状态 | 含义 | 终结态 |
|---|---|---|
| 未报 | 柜台已受理,资金已冻结,尚未报交易所 | 否 |
| 已报 | 交易所已确认接收,进入订单簿排队 | 否 |
| 部成 | 部分成交,剩余数量仍在排队 | 否 |
| 已成 | 全部成交 | 是 |
| 已撤 | 全部撤单成功(无成交或撤掉全部剩余) | 是 |
| 部撤 | 有成交记录后,剩余部分撤单成功 | 是 |
| 废单 | 被交易所/柜台拒绝,不进入订单簿 | 是 |
| 待确认 | 发出了请求但没收到回应,真实状态未知 | 否(伪状态) |
部成和部撤要分开是两个状态,而不是"部成 + 已撤"的组合覆盖,因为账务处理不同:部成是活的委托(剩余还冻着资金),部撤是死的委托(要做终态结算:成交部分扣款、未成交部分解冻)。
废单必须带着废单原因落库:资金不足、价格超笼子、股东代码无效、非交易时间……原因码是后续差错排查和监管问询的依据,只记一个"失败"状态等于把信息扔了。
状态迁移表
文字版迁移表(触发事件 → 目标状态):
- 未报 --交易所确认--> 已报;--柜台/交易所拒绝--> 废单
- 已报 --部分成交--> 部成;--全部成交--> 已成;--撤单确认--> 已撤
- 部成 --继续成交至满--> 已成;--撤单确认--> 部撤
- 任何活状态 --网关断线/确认超时--> 待确认(逻辑状态,落库保持原态 + 标志位)
部成状态下继续来部分成交,状态保持部成,只更新累计成交量——状态不变但数据变,这是状态机实现里最容易漏的一类迁移(自环)。
三个折磨人的异常分支
回报乱序。 交易所的成交回报走消息通道,理论上有序,实践中经多跳网关、断线重传之后可能乱序到达:先收到第三笔成交再收到第二笔。如果状态机用"当前状态 + 事件"粗暴迁移,乱序回报会把数量算错。防御手段是两层:一是用交易所的回报序号做顺序校验,发现空洞先缓存后补;二是数量计算用绝对值不用增量——回报里通常带"累计成交量",直接覆盖式更新 filled_qty = report.cum_qty,而不是 filled_qty += report.qty,这样重复回报和乱序回报天然幂等。成交回报篇会把幂等设计展开。
撤单与成交的赛跑。 客户点了撤单,撤单请求还在路上,订单恰好被撮合成交了。结果两种:撤单确认先到、成交回报后到(状态已撤,却又来成交);或反过来。交易所的规则是以撮合为准:撤单请求到达时订单若已成交,撤单被拒绝并回报"撤单失败:已成交"。柜台侧的正确姿势是撤单发出后把委托标记为"撤单中",收到撤单确认才迁移终态;若期间来了成交,按成交优先处理,撤单流程作废。绝不能在"撤单中"就提前解冻资金——撤单失败钱就双花了。
待确认:最危险的状态。 报盘发出后网关断了,交易所到底收没收到?不知道。此时委托在本地是活是死无法判断,这就是分布式系统的两将军问题在证券系统里的具象。处理纪律是:
- 本地状态保持原样,加"待确认"标志,冻结资金不动;
- 网关恢复后用委托查询接口逐笔核对真实状态;
- 查询回报回来前,禁止对该委托做任何账务操作;
- 收市仍未确认的,日终用交易所的成交文件兜底核对(文件是权威数据源)。
账务操作挂在迁移边上,不是状态里
状态机本身不记账,账务是迁移的副作用。每个迁移边对应一组明确的账务动作:
- 未报 → 已报/废单:废单要全额解冻;
- → 部成/已成:按成交金额把冻结转扣款(买入)或冻结持仓转扣减(卖出),剩余冻结保持;
- → 已撤/部撤:未成交部分解冻,成交部分按成交结算。
这套"迁移即账务"的映射必须用事务包住:状态 UPDATE 和资金 UPDATE 同生共死。常见的脏数据形态——委托已是"已撤"但资金还冻着——九成是这两张表没在一个事务里,或者批量补偿程序只修了一张。
实现层面的两个建议
状态机显式化。 不要在代码里撒 if status == 'P' then ... 的地雷阵。把迁移表写成数据(状态 × 事件 → 目标状态 + 副作用函数),非法迁移一律拒绝并告警。非法迁移在生产上是重要信号:它意味着回报解析有 bug 或者上游状态异常,静默放过会攒成清算差错。
保留完整的状态历史。 委托主表存当前态,另起一张 order_status_log 追加式记录每次迁移(旧态、新态、触发事件、事件序号、时间戳)。监管调取委托流转记录时要的就是这张表;排查"钱为什么冻着"时,它也是唯一能还原现场的东西。审计留痕那篇会讲日志的不可抵赖设计,这里先把数据留住。
状态机设计对了,后面讲成交回报幂等、撤单成本、日终核对时你会反复受益;设计错了,它会在每个异常交易日用资损提醒你。