第二章:回测框架设计原则

做高频交易回测,框架设计是地基。地基没打好,后面全是坑。我这些年踩过的坑,十个有八个跟框架设计有关。今天咱们聊聊三个核心原则:模块化、事件驱动、性能优化。

一、模块化设计:别把所有鸡蛋放一个篮子里

模块化,说白了就是「各司其职」。你的回测框架里,数据加载、策略逻辑、订单管理、风控检查……这些功能应该各自独立。为什么?

我在项目中遇到过一件事:有个同事把数据清洗和策略逻辑写在一个函数里。结果换了个数据源,整个策略逻辑都得重写。嗯,那场面,挺尴尬的。

核心原则:每个模块只做一件事,并且做好一件事。

我建议这样拆分:

  • 数据模块:负责行情数据的加载、清洗、对齐。输出标准化的 OHLCV 格式。
  • 策略模块:接收行情数据,输出交易信号。不关心订单怎么执行。
  • 风控模块:检查信号是否合规。比如仓位限制、最大回撤。
  • 执行模块:把信号变成订单,模拟撮合。
  • 记录模块:记录每一笔交易、每个持仓变化。

你想想看,如果哪天你要换数据源,只需要改数据模块。策略逻辑完全不用动。这就是模块化的好处。

小技巧:用接口(Interface)来定义模块之间的通信协议。这样每个模块可以独立开发、独立测试。

二、事件驱动架构:让系统自己动起来

高频交易里,行情是源源不断的。你不能写个死循环去轮询。那样 CPU 会炸,延迟也会高。

事件驱动架构,就是让系统「被动响应」。行情来了,触发一个事件;订单成交了,触发另一个事件。每个事件都有对应的处理器。

我习惯这样设计:

class Event:
    def __init__(self, event_type, data):
        self.type = event_type
        self.data = data
        self.timestamp = time.time()

class EventEngine:
    def __init__(self):
        self.handlers = {}
    
    def register(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):
        handlers = self.handlers.get(event.type, [])
        for handler in handlers:
            handler(event)

你看,代码很简单。但核心思想很重要:事件是中心,模块是订阅者

举个例子:

  • 行情模块收到 tick 数据 → 发布 TICK_EVENT
  • 策略模块订阅 TICK_EVENT → 计算信号 → 发布 SIGNAL_EVENT
  • 风控模块订阅 SIGNAL_EVENT → 检查 → 发布 ORDER_EVENT
  • 执行模块订阅 ORDER_EVENT → 模拟撮合 → 发布 FILL_EVENT

每个模块只关心自己订阅的事件。模块之间完全解耦。我曾经把一个策略从「均线策略」换成「机器学习策略」,只改了策略模块。其他模块一行代码没动。

注意:事件队列要小心处理。如果事件处理太慢,队列会积压。高频场景下,建议用无锁队列或者环形缓冲区。

三、性能优化要点:每一微秒都很重要

高频回测,数据量巨大。一天的数据可能几百万条 tick。如果代码写得糙,回测跑一天都出不来结果。那还怎么调参数?

我总结几个关键点:

1. 数据结构选型

别用 Python 的 list 存 tick 数据。用 numpy array 或者 pandas DataFrame。为什么?因为底层是 C 实现的,循环快得多。

# 慢
prices = []
for tick in ticks:
    prices.append(tick.price)

# 快
import numpy as np
prices = np.array([tick.price for tick in ticks])

2. 避免对象创建

高频场景下,每毫秒都在创建对象。GC(垃圾回收)会频繁触发,导致卡顿。我建议:

  • 用对象池复用对象
  • __slots__ 减少内存开销
  • 避免在热路径上创建临时变量

3. 向量化计算

能用矩阵运算,就别用 for 循环。比如计算移动平均线:

# 慢
ma = []
for i in range(20, len(prices)):
    ma.append(np.mean(prices[i-20:i]))

# 快
import pandas as pd
ma = pd.Series(prices).rolling(20).mean().values

4. 预分配内存

如果你知道数据量,提前分配好内存。别让 Python 动态扩容。

性能对比(实测数据):

优化项 优化前 优化后 提升倍数
数据结构 Python list numpy array 10x
循环方式 for 循环 向量化 50x
对象创建 每次 new 对象池 5x

四、框架整体架构图

下面这张图,是我个人习惯的框架结构。你可以参考,但别照搬。每个团队、每个策略都有自己的特点。

高频回测框架架构图 数据模块 行情加载 · 清洗 · 对齐 事件引擎 事件队列 · 分发 · 调度 策略模块 信号生成 · 参数管理 风控模块 仓位检查 · 止损 · 限仓 执行模块 订单管理 · 撮合模拟 记录模块 交易日志 · 绩效统计 行情事件 分发 信号事件 分发 订单事件 分发 成交事件 记录 模块之间通过事件引擎解耦,每个模块只处理自己订阅的事件

五、避坑指南

最后,分享几个我踩过的坑:

  • 我曾经把日志打印放在热路径上。结果回测速度慢了 30 倍。后来改成异步日志,问题解决。
  • 我曾经用 Python 的 datetime 做时间戳比较。高频场景下,这个操作很慢。换成 int64 纳秒时间戳,速度快了一个数量级。
  • 我曾经在事件处理里做数据库写入。结果事件队列越积越多。后来改成批量写入,每 1000 个事件写一次。

我的建议:先写出能跑的版本,再优化。别一开始就追求极致性能。80% 的性能提升来自 20% 的代码优化。

好了,模块化、事件驱动、性能优化,这三个原则你记住了吗?下次写回测框架时,试着用这些思路去设计。你会发现,代码好维护了,跑得也快了。


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