2. 交易系统基础
在构建多Broker适配层之前,必须先理解一个根本问题:不同市场的交易流程差异,最终都会映射到订单状态机的差异上。适配层的本质工作,就是把各Broker异构的订单状态、回报语义和交易时段规则,归一化成一套统一的内部模型。因此本节不从API出发,而是从市场机制出发,建立后续所有适配工作的概念底座。
一、核心原理:三类市场的交易流程差异
证券、期货、外汇虽然都表现为"下单—成交—回报",但其底层撮合与清算逻辑截然不同,这直接决定了订单生命周期中会出现哪些状态。
- 证券(股票):交易所集中撮合,T+1交收(A股),存在涨跌停限制。订单进入交易所后可能经历"已报—部成—全成—已撤"的完整链路,且当日买入不可卖出。
- 期货:同样集中撮合,但为T+0、保证金交易,存在开平仓方向(开仓/平仓/平今)。订单必须携带
OffsetFlag,否则会被柜台拒绝。当日可反复交易,且有强平、交割等特殊状态。 - 外汇:多为OTC做市商模式,无统一交易所。报价驱动成交,存在点差、滑点,订单类型更丰富(市价、限价、止损、止盈、OCO)。成交回报可能来自做市商而非中央撮合,状态语义更依赖Broker定义。
关键结论:适配层不能假设"所有Broker的订单状态含义一致"。证券的"部成"与外汇的"部分成交"在撤单规则、剩余数量处理上可能完全不同,必须通过统一状态映射表来隔离。
二、订单生命周期:从提交到终态的完整状态迁移
一个订单从策略发出到最终终结,通常经历以下阶段。理解这条链路,是设计统一订单对象和状态回调的基础。
- 待提交(PendingSubmit):策略生成订单,尚未发送至Broker。
- 已提交(Submitted):已发送至Broker,等待Broker确认。
- 已接受(Accepted):Broker确认收到,进入其内部或交易所队列。
- 部分成交(PartiallyFilled):部分数量成交,剩余仍在挂单。
- 全部成交(Filled):终态,订单完全执行。
- 已撤销(Cancelled):终态,未成交部分被撤回。
- 已拒绝(Rejected):终态,因资金、风控、参数错误等被拒。
需要特别注意的是,部分成交后撤单会产生"部成部撤"的复合终态,适配层必须能表达"已成交X手、已撤Y手"的最终结果,而不能简单归为Cancelled。
三、常见交易术语与业务场景对照
不同市场对同一概念使用不同术语,适配层需要建立术语映射。下表列出高频术语及其跨市场差异。
| 术语 | 证券 | 期货 | 外汇 |
|---|---|---|---|
| 订单方向 | 买/卖 | 买开/卖开/买平/卖平 | Buy/Sell(含止损止盈) |
| 成交模式 | 集中撮合 | 集中撮合 | 做市商报价 |
| 交收周期 | T+1 | T+0 | T+0(即时) |
| 撤单规则 | 当日有效,可撤 | 当日有效,平今受限 | 依Broker,可能不支持 |
| 价格限制 | 涨跌停 | 涨跌停+保证金 | 无涨跌停,有点差 |
在适配层设计中,建议将"方向"抽象为
Side枚举,将"开平"抽象为Offset枚举,二者组合后再映射到具体Broker字段。这样证券只需忽略Offset,期货则完整使用。
四、误区与复盘:订单状态映射的典型陷阱
在实际对接中,最常见的错误是用单一状态字段表达复合语义。例如某期货Broker的回报中,"部成部撤"只返回一个Status=Cancelled,若不解析FilledVolume,策略会误以为完全未成交,导致仓位计算错误。
// 错误做法:仅看状态字段
if (order.Status == "Cancelled") {
position -= order.Quantity; // 假设全部未成交
}
// 正确做法:结合已成交数量判断
if (order.Status == "Cancelled") {
position -= order.FilledVolume; // 只回滚未成交部分
// 已成交部分已在成交回报中处理
}
另一个高频误区是忽略交易时段。证券有集合竞价与连续竞价之分,期货有夜盘,外汇几乎24小时但存在结算窗口。适配层若不做时段校验,可能在非交易时段发出订单,被Broker拒绝后产生大量无效重试。
复盘建议:在适配层入口处增加"交易时段校验器",按市场类型加载对应日历,拒绝非交易时段的订单,并记录明确日志。这比依赖Broker拒绝后再处理更高效,也更容易排查问题。
理解上述市场流程、订单生命周期与术语差异后,我们才能设计出既统一又保留市场特性的内部订单模型。下一阶段将进入具体Broker接口的抽象与适配实现。