第三章 策略引擎核心设计

策略引擎,说白了就是量化系统的「大脑」。

我做了这么多年量化架构,见过太多团队在策略引擎上栽跟头。有的回测跑得飞起,一上实盘就崩;有的策略热加载搞成了「热崩溃」。今天咱们就把这块掰开揉碎了讲清楚。

3.1 策略生命周期管理

一个策略从诞生到退役,经历哪些阶段?我习惯把它分成六个状态:

状态 说明 典型操作
INIT 策略初始化 加载参数、建立连接
READY 就绪待命 等待启动信号
RUNNING 运行中 接收行情、生成信号
PAUSED 暂停 停止交易,保留状态
STOPPED 停止 清理资源
ERROR 异常 触发告警、自动恢复

每个状态之间怎么流转?我画了张图,你一看就明白:

INIT READY RUNNING PAUSED STOPPED ERROR 初始化成功 启动 暂停 恢复 停止 异常 手动恢复

核心要点:状态机设计要保证「幂等性」。同一个状态转换操作执行多次,结果必须一致。我见过有人把PAUSED状态搞成「半死不活」——暂停后恢复不了,只能重启。

3.2 信号生成机制

信号生成,就是策略引擎的核心产出。我个人习惯把它拆成三层:

  1. 数据层:接收行情,做预处理(去噪、对齐、填充)
  2. 计算层:跑指标、算因子、生成原始信号
  3. 决策层:信号过滤、组合、生成最终交易指令

举个例子,一个简单的双均线策略:

class MovingAverageStrategy:
    def __init__(self, fast=5, slow=20):
        self.fast = fast
        self.slow = slow
        self.position = 0  # 0空仓, 1持仓
    
    def on_tick(self, price):
        # 计算层
        fast_ma = self.calc_ma(price, self.fast)
        slow_ma = self.calc_ma(price, self.slow)
        
        # 决策层
        if fast_ma > slow_ma and self.position == 0:
            self.position = 1
            return Signal.BUY
        elif fast_ma < slow_ma and self.position == 1:
            self.position = 0
            return Signal.SELL
        return Signal.HOLD

我的经验:信号生成一定要做「防抖」处理。行情剧烈波动时,信号可能在几毫秒内反复翻转。我一般加一个「确认窗口」——连续N个tick都指向同一方向,才真正发出信号。

3.3 回测与实盘引擎的差异

这个问题,我每次面试都会问。很多人答不上来,或者只答「回测用历史数据,实盘用实时数据」。太表面了。

真正的差异在哪?我列个表:

维度 回测引擎 实盘引擎
时间模型 可加速、可暂停 实时推进,不可逆
成交模拟 理想化(滑点、手续费估算) 真实撮合,有拒单风险
数据源 本地文件/数据库 行情推送(WebSocket/FIX)
容错机制 出错可重跑 必须实时处理异常
性能要求 尽量快,但允许慢 微秒级响应

避坑指南:我曾经把一个回测引擎直接改改就上实盘,结果吃了大亏。回测里「成交即确认」的逻辑,在实盘里根本行不通——你下了单,可能被拒、可能部分成交、可能延迟成交。后来我学乖了,回测和实盘共用「策略逻辑层」,但「执行层」完全分开。

3.4 策略热加载

热加载,就是不停机更新策略。听起来很酷,做起来很坑。

为什么需要热加载?你想想看,一个策略跑着跑着,发现参数需要微调。如果每次都要重启整个系统,那其他策略也跟着遭殃。尤其是做高频的,重启一次可能错过几十个交易机会。

热加载的核心难点有三个:

  • 状态迁移:旧策略的持仓、挂单怎么交接给新策略?
  • 版本管理:加载失败了怎么回滚?
  • 资源隔离:新策略不能影响其他策略的运行

我常用的方案是「双缓冲」模式:

class HotReloadManager:
    def __init__(self):
        self.active_strategy = None
        self.pending_strategy = None
    
    def load_new_strategy(self, strategy_code):
        # 先编译新策略,不切换
        new_strategy = compile_and_check(strategy_code)
        self.pending_strategy = new_strategy
        
    def switch(self):
        # 原子切换
        old = self.active_strategy
        self.active_strategy = self.pending_strategy
        self.pending_strategy = None
        # 旧策略优雅退出
        old.shutdown()

关键设计:切换时要做「状态快照」。把当前持仓、未成交订单、累计盈亏都记录下来。新策略启动时,先恢复这些状态,再开始交易。我见过有人忘了做这个,结果新策略一上来就开仓,跟旧策略的持仓叠加,仓位直接翻倍。

嗯,这里还要注意一点——热加载的「原子性」。切换操作必须在一瞬间完成,不能出现「新旧策略同时运行」的状态。我一般用读写锁或者CAS操作来保证。

最后说一句,热加载不是银弹。如果你的策略更新频率很低(比如一周一次),那重启系统也没啥问题。但如果你在做高频策略、或者需要频繁调参,那热加载就是必备技能了。

我的习惯:每次热加载前,先在小资金账户上跑一段时间验证。没问题了再切到主账户。这叫「灰度发布」,做量化的都懂。


公众号:蓝海资料掘金营,微信deep3321