核心架构设计:回测引擎的模块划分、事件驱动架构 vs 向量化架构、数据流与事件流设计

做回测引擎,说白了就是搭一套能模拟真实交易的系统。我见过太多人一上来就写代码,结果写到一半发现架构撑不住,推倒重来。今天咱们聊聊核心架构,把地基打牢。

模块划分:各司其职

一个回测引擎,我习惯拆成这几个核心模块。每个模块只干一件事,这样后期维护和扩展都方便。

模块名称 核心职责 我踩过的坑
数据模块 加载、清洗、对齐行情数据 时间戳不对齐,回测结果全错
策略模块 接收行情,产生交易信号 策略里用了未来数据,自己还不知道
执行模块 模拟下单、成交、滑点 滑点模型太理想,实盘直接亏
风控模块 检查仓位、资金、限价 忘记检查资金,爆仓了还在下单
记录模块 记录每笔交易、持仓、净值 日志不全,复盘时找不到原因
分析模块 计算夏普、最大回撤等指标 收益率计算方式搞错,白高兴一场

每个模块之间通过接口通信。数据模块只管给数据,策略模块只管算信号。这样你换数据源,或者换策略,都不影响其他部分。

核心原则:模块之间不要互相调用内部方法。只通过定义好的接口传数据。我见过有人直接在策略里调用数据库查询,结果换数据库时改得想哭。

事件驱动架构 vs 向量化架构

这是回测引擎设计里最核心的抉择。两种思路,各有千秋。

事件驱动架构

模拟真实交易流程。每个时间点,系统处理一个事件——可能是新的K线来了,可能是订单成交了。策略收到事件后决定下一步动作。

class EventEngine:
    def __init__(self):
        self.events = []
        self.strategies = []
    
    def run(self):
        while self.events:
            event = self.events.pop(0)
            for strategy in self.strategies:
                strategy.on_event(event)
                # 策略可能产生新事件
                self.events.extend(strategy.new_events)

优点很明显:贴近实盘,能处理限价单、撤单、部分成交这些复杂逻辑。缺点呢?慢。真的慢。我做过一个高频策略的回测,跑一天的数据要等半小时。

我的建议:如果你的策略涉及复杂的订单类型,或者需要模拟盘口深度,用事件驱动。否则,先看看向量化。

向量化架构

把整个行情数据当成一个数组。策略一次性算完所有信号,然后批量模拟交易。说白了,就是拿空间换时间。

import numpy as np

def vectorized_backtest(prices, signals):
    # 一次性计算所有信号
    positions = signals.shift(1)  # 避免未来数据
    returns = prices.pct_change()
    strategy_returns = positions * returns
    return strategy_returns.cumsum()

快,非常快。几行代码就能跑完十年数据。但缺点也明显:没法处理限价单,没法模拟滑点,更别提部分成交了。

注意:向量化回测容易产生未来函数。我曾经用shift(1)以为避开了,结果在计算信号时还是用了当天的数据。嗯,这里要特别小心。

数据流与事件流设计

数据怎么在模块之间流动?事件怎么触发?这是架构设计的灵魂。

数据流:从原始数据到策略信号

我习惯把数据流分成三层:

  1. 原始数据层:从数据库或API拿到的原始行情,可能有缺失、有异常
  2. 清洗对齐层:处理缺失值、对齐时间戳、计算技术指标
  3. 策略输入层:策略真正能用的数据,格式统一、时间对齐

为什么要分三层?因为我在项目中遇到过,数据源换了之后,清洗逻辑全要重写。分层之后,只需要改清洗对齐层,其他不动。

事件流:谁触发谁

事件驱动架构里,事件流的设计决定了系统的复杂度。我推荐用「发布-订阅」模式。

class EventBus:
    def __init__(self):
        self.subscribers = {}
    
    def subscribe(self, event_type, handler):
        if event_type not in self.subscribers:
            self.subscribers[event_type] = []
        self.subscribers[event_type].append(handler)
    
    def publish(self, event):
        for handler in self.subscribers.get(event.type, []):
            handler(event)

这样设计的好处是:策略、风控、记录模块都订阅自己关心的事件。数据模块发布新K线事件,策略模块收到后计算信号,再发布交易信号事件。执行模块收到后模拟下单,再发布成交事件。环环相扣,但每个模块只关心自己订阅的那部分。

避坑指南:我曾经把所有事件都放在一个队列里处理,结果风控模块处理太慢,导致策略模块等不及,直接用了旧数据。后来改成每个事件类型独立队列,问题解决。

架构对比:一张图看懂

下面这张图,是我自己总结的两种架构的核心区别。你一看就明白。

事件驱动架构 行情事件 → 事件队列 策略模块 → 处理事件 执行模块 → 模拟下单 风控模块 → 检查合规 特点:真实模拟,但慢 向量化架构 全部行情数据 → NumPy数组 一次性计算所有信号 批量模拟交易 一次性计算所有指标 特点:极快,但简化了细节 选择

你想想看,如果你的策略只是简单的均线交叉,用向量化架构,几秒钟跑完十年数据。但如果你要做高频做市,需要模拟盘口深度和限价单排队,那必须上事件驱动。

我的经验:先做向量化原型,快速验证策略思路。等思路成熟了,再迁移到事件驱动做精细回测。这样既快又稳。

嗯,架构设计这块,核心就是这些。模块划分要清晰,架构选择要匹配策略类型,数据流和事件流要设计好。把这些想明白了,写代码就是水到渠成的事。

专注资料整理