2. 量化策略Serving架构:策略服务整体架构、策略执行引擎、信号分发组件、回测与实盘隔离
量化策略从研究到实盘交易,需要一个健壮、低延迟且可扩展的 Serving 架构。本章将拆解策略服务的核心组件,包括整体架构分层、策略执行引擎、信号分发机制以及回测与实盘的隔离设计,帮助你在灰度发布模型中建立可靠的策略运行环境。
2.1 策略服务整体架构
一个生产级的量化策略 Serving 系统通常采用分层架构,将数据接入、策略计算、信号输出与风控解耦。下图展示了核心分层与数据流:
各层职责清晰,数据流单向向下,便于监控与故障隔离。在灰度发布场景中,策略计算层可同时运行多个版本,通过路由规则将流量导向不同策略实例。
2.2 策略执行引擎
策略执行引擎是 Serving 架构的核心,负责接收市场数据、运行策略逻辑并输出交易信号。一个高性能引擎需要具备以下能力:
| 能力维度 | 说明 | 灰度发布关键点 |
|---|---|---|
| 事件驱动 | 基于 Tick / K线 / 订单事件触发计算 | 支持事件回放,便于回测与实盘共用逻辑 |
| 多策略隔离 | 每个策略运行在独立上下文(进程/容器/线程池) | 灰度版本与基线版本互不影响 |
| 参数热加载 | 运行时更新策略参数,无需重启 | 灰度参数动态调整,快速验证 |
| 状态管理 | 持仓、订单、指标等状态持久化与恢复 | 灰度切换时状态平滑迁移 |
| 延迟监控 | 每个环节耗时打点,支持性能分析 | 对比灰度与基线延迟差异 |
实践建议: 策略执行引擎应设计为无状态计算 + 外部状态存储(如 Redis / 数据库),方便灰度版本快速扩缩容,同时避免单点故障。
2.3 信号分发组件
信号分发组件负责将策略引擎生成的交易信号可靠地传递给交易执行系统。在灰度发布模型中,信号分发需要支持多路分发、版本标记与流量控制。
典型信号分发流程如下:
- 信号封装:将策略输出(买卖方向、价格、数量、策略ID、版本号)序列化为标准消息格式。
- 消息路由:根据策略版本号或灰度标签,将信号发送到对应的消息队列 Topic 或分区。
- 信号转换:将内部信号格式转换为交易所或券商 API 所需的指令格式。
- 幂等发送:确保同一信号在重试时不会重复下单,通常通过唯一信号 ID 实现。
- 回执处理:接收交易系统的执行回执,更新信号状态(已成交/部分成交/拒绝)。
灰度信号分发要点:
- 信号消息体中必须包含
strategy_version和gray_tag字段,便于下游过滤与统计。 - 消息队列建议使用 Topic 隔离(如
signal.v1、signal.v2),或使用单一 Topic + 消费者端过滤。 - 信号转换组件应支持多版本适配,灰度版本可能使用不同的交易接口或参数。
2.4 回测与实盘隔离
回测与实盘共用同一套策略逻辑是量化工程的最佳实践,但必须通过架构手段实现严格隔离,防止回测操作影响实盘账户。隔离设计包含以下层面:
| 隔离层面 | 回测环境 | 实盘环境 | 隔离方式 |
|---|---|---|---|
| 数据源 | 历史数据回放 | 实时行情流 | 数据源配置分离,回测使用本地文件或数据库 |
| 交易接口 | 模拟撮合引擎 | 真实券商 API | 依赖注入,策略代码不直接引用具体接口 |
| 资金账户 | 虚拟资金 | 真实账户 | 账户 ID 硬隔离,回测使用独立资金管理模块 |
| 状态存储 | 本地内存 / 测试数据库 | 生产数据库 / Redis | 数据库连接配置分离,禁止回测写入生产库 |
| 风控规则 | 宽松或关闭 | 严格风控 | 风控模块通过配置开关切换模式 |
常见陷阱: 回测与实盘共用同一套数据库连接配置,导致回测时误写入实盘订单表。务必在启动脚本或容器编排中通过环境变量强制区分环境,并在代码层增加环境断言检查。
在灰度发布模型中,回测环境还可用于验证灰度策略的历史表现,而实盘环境则运行灰度版本与基线版本的对比交易。通过统一的策略执行引擎接口,回测与实盘可以共享同一份策略代码,仅通过配置切换环境上下文:
# 策略引擎初始化示例
class StrategyEngine:
def __init__(self, config: dict):
self.env = config.get("env", "backtest") # backtest | paper | live
self.data_source = self._init_data_source(config)
self.broker = self._init_broker(config)
self.risk_manager = self._init_risk_manager(config)
def _init_broker(self, config):
if self.env == "backtest":
return SimulatedBroker(config)
elif self.env == "paper":
return PaperBroker(config)
else:
return LiveBroker(config)
def run(self):
# 策略主循环,不关心具体 broker 实现
while self.data_source.has_next():
data = self.data_source.next()
signal = self.strategy.on_data(data)
if signal:
self.broker.place_order(signal)
通过依赖注入和环境配置,策略代码无需修改即可在回测、模拟盘、实盘之间切换,极大降低了灰度发布时的回归测试成本。