一、系统架构设计:核心组件与通信机制

做量化回测系统,说白了就是在模拟真实交易环境。我刚开始做的时候,也踩过不少坑——最典型的就是把所有逻辑揉在一个大脚本里,改个参数要翻半天代码。后来才明白,好的架构设计,才是回测系统能跑得稳、改得动的关键。

今天咱们就聊聊事件驱动回测系统的核心架构。我会把每个模块拆开来讲,再告诉你它们之间怎么配合。

1.1 整体架构概览

事件驱动回测系统,核心思想就一句话:一切皆事件。行情来了是事件,订单成交了是事件,风控触发了也是事件。系统里各个模块不直接调用对方,而是通过事件引擎来沟通。

我画了一张架构图,你先感受下整体脉络:

事件驱动回测系统架构图 事件引擎 Event Engine 策略模块 Strategy 数据模块 Data Feed 风控模块 Risk Manager 执行模块 Execution 发送信号事件 推送行情事件 订单事件 执行指令 成交回报 箭头方向 = 事件流动方向 | 虚线 = 异步回调

你看,事件引擎在正中间,其他四个模块围着它转。这种设计的好处很明显——模块之间解耦了,你想换策略或者换数据源,都不影响其他部分。

1.2 事件引擎——系统的"心脏"

事件引擎是整个系统的调度中心。它维护一个事件队列,按照时间顺序依次处理每个事件。我个人习惯把事件引擎设计成单例模式,整个系统只有一个实例在跑。

核心职责:

  • 接收各模块发来的事件
  • 按时间戳排序放入队列
  • 依次派发给对应的处理器

这里有个关键点:事件必须按时间顺序处理。我在项目中遇到过因为事件乱序导致回测结果完全不对的情况,排查了两天才发现是队列排序出了问题。

# 事件引擎的核心骨架
class EventEngine:
    def __init__(self):
        self.queue = []          # 事件队列
        self.handlers = {}       # 事件类型 -> 处理器列表

    def register_handler(self, event_type, handler):
        """注册事件处理器"""
        if event_type not in self.handlers:
            self.handlers[event_type] = []
        self.handlers[event_type].append(handler)

    def put(self, event):
        """放入事件"""
        self.queue.append(event)
        self.queue.sort(key=lambda e: e.timestamp)  # 按时间排序

    def run(self):
        """启动事件循环"""
        while self.queue:
            event = self.queue.pop(0)
            self._dispatch(event)

    def _dispatch(self, event):
        """派发事件到对应处理器"""
        handlers = self.handlers.get(event.type, [])
        for handler in handlers:
            handler(event)

小技巧:事件队列用优先队列(heapq)实现,比每次排序效率高得多。我后来重构时改成了heapq,回测速度提升了30%。

1.3 策略模块——你的交易大脑

策略模块是用户写策略的地方。它接收行情事件,计算信号,然后生成交易指令。说白了,这就是你交易思路的代码化表达。

我建议把策略基类设计成接口,只暴露两个方法:on_bar()on_tick()。用户只需要实现这两个方法就行,其他事情不用管。

class BaseStrategy:
    def __init__(self, name):
        self.name = name
        self.event_engine = None

    def on_bar(self, bar):
        """处理K线数据"""
        raise NotImplementedError

    def on_tick(self, tick):
        """处理Tick数据"""
        raise NotImplementedError

    def send_signal(self, signal):
        """发送交易信号"""
        self.event_engine.put(signal)

嗯,这里要注意:策略里不要做任何耗时操作。我曾经见过有人在策略里写数据库查询,结果回测速度慢得像蜗牛。策略就该只做计算,其他事情交给别的模块。

1.4 数据模块——喂给策略的"粮食"

数据模块负责把原始数据转换成策略能理解的事件。它从CSV、数据库或者API读取数据,然后按时间顺序推送给事件引擎。

数据源类型 适用场景 注意事项
CSV文件 小规模回测、研究阶段 注意日期格式统一
数据库 中大规模回测 做好索引优化查询
实时API 模拟盘/实盘 处理断线重连

数据模块还有一个重要职责——数据清洗。比如处理停牌、复权、缺失值等。我习惯在数据模块里做一次性的清洗,而不是让策略去处理这些脏活。

1.5 风控模块——系统的"刹车"

风控模块很多人会忽略,但我觉得它恰恰是最重要的。它负责检查每一笔交易是否合规,比如仓位是否超限、是否在禁止交易时段等。

我曾经踩过的坑:一开始没做风控,回测结果看起来很美。结果实盘第一天就触发了交易所的风控限制,直接爆仓。从那以后,我回测系统里必加风控模块。

风控模块的检查逻辑通常包括:

  • 单笔交易金额上限
  • 总仓位比例限制
  • 交易品种黑名单
  • 交易时间窗口
  • 最大回撤阈值

1.6 执行模块——把想法变成现实

执行模块负责模拟订单的成交过程。它接收风控通过后的订单,然后根据市场流动性、滑点等因素模拟成交。

这里有个容易忽略的点:成交模拟的精度。简单的做法是直接按当前价成交,但这样会高估收益。我建议至少考虑以下因素:

  • 滑点:按固定比例或随机比例
  • 成交量限制:大单可能无法完全成交
  • 延迟:从下单到成交的时间差
class ExecutionModule:
    def execute_order(self, order):
        """模拟订单执行"""
        # 计算滑点
        slippage = self.calc_slippage(order.volume)
        # 模拟成交价
        fill_price = order.price * (1 + slippage)
        # 模拟成交量(考虑流动性)
        fill_volume = min(order.volume, self.available_liquidity)
        # 生成成交回报
        return Fill(order.id, fill_price, fill_volume)

1.7 模块间通信机制

模块之间怎么通信?说白了就是通过事件。每个模块只跟事件引擎打交道,不直接调用其他模块。

举个例子,策略模块想下单:

  1. 策略生成信号事件,发给事件引擎
  2. 事件引擎把信号事件派发给风控模块
  3. 风控检查通过后,生成订单事件
  4. 事件引擎把订单事件派发给执行模块
  5. 执行模块模拟成交,生成成交回报事件
  6. 成交回报再通过事件引擎通知策略

你想想看,这种设计的好处是什么?每个模块只需要关心自己收到的和发出的事件,不需要知道其他模块的存在。想加一个新模块?注册几个事件处理器就行。

事件类型定义示例:

  • MARKET_DATA - 行情数据事件
  • SIGNAL - 交易信号事件
  • ORDER - 订单事件
  • FILL - 成交回报事件
  • RISK_ALERT - 风控警告事件

好了,这就是事件驱动回测系统的核心架构。每个模块各司其职,通过事件引擎串联起来。这种设计我用了好几年,改起来方便,扩展性也好。你动手搭建的时候,可以先从事件引擎开始,其他模块慢慢加进去。


专注资料整理