回测框架搭建:从零开始构建你的量化引擎

做量化投资这些年,我搭建过不下五个回测框架。说实话,每次重构都有新感悟。回测框架就像房子的地基——地基没打好,后面装修得再漂亮也白搭。今天咱们就聊聊,怎么用Python搭一个靠谱的回测框架。

回测框架的核心组件

一个完整的回测框架,说白了就四个核心模块:

  • 数据模块:负责获取、清洗、对齐行情数据
  • 策略模块:实现你的交易逻辑,生成买卖信号
  • 执行模块:模拟下单、成交、滑点等真实交易细节
  • 分析模块:计算收益、回撤、夏普比率等绩效指标

我刚开始做回测时,总觉得数据模块最不重要。结果呢?有一次数据没对齐,策略跑出来年化收益50%,实际一测亏成狗。嗯,从那以后我再也不敢轻视数据清洗了。

核心要点:这四个模块必须解耦。数据模块只负责数据,策略模块只负责信号。耦合在一起,后期改一个地方就得改全部,你会疯掉的。

使用Python搭建简易回测框架

咱们直接上代码。一个最简的回测框架,其实几十行就能搞定:

import pandas as pd
import numpy as np

class SimpleBacktest:
    def __init__(self, data, strategy, initial_capital=100000):
        self.data = data
        self.strategy = strategy
        self.capital = initial_capital
        self.positions = 0
        self.trades = []
    
    def run(self):
        for i in range(len(self.data)):
            signal = self.strategy.generate_signal(self.data.iloc[:i+1])
            if signal == 1 and self.positions == 0:
                self.positions = self.capital / self.data.iloc[i]['close']
                self.capital = 0
                self.trades.append(('buy', self.data.index[i], self.data.iloc[i]['close']))
            elif signal == -1 and self.positions > 0:
                self.capital = self.positions * self.data.iloc[i]['close']
                self.positions = 0
                self.trades.append(('sell', self.data.index[i], self.data.iloc[i]['close']))
        return self.calculate_performance()
    
    def calculate_performance(self):
        final_value = self.capital + self.positions * self.data.iloc[-1]['close']
        return final_value - 100000  # 简单返回收益

你看,核心逻辑就一个循环:遍历每一天,检查信号,执行交易。但这里有个坑——我当年就是这么写的,结果发现回测结果和实盘差得离谱。为什么?因为没考虑滑点和手续费。

避坑指南:我曾经在回测中忽略滑点,结果策略在实盘时每笔交易都多亏0.5%。别小看这0.5%,高频交易一个月下来,收益能差20%以上。

事件驱动 vs 向量化回测

这两个概念,我当年也迷糊了很久。简单说:

对比维度 事件驱动 向量化
处理方式 逐条处理每个事件 一次性计算整个序列
性能 较慢,但更真实 极快,适合大规模回测
适用场景 高频、复杂策略 中低频、简单策略
代码复杂度 较高 较低

我个人习惯:做轮动模型回测时,先用向量化快速验证想法。等策略逻辑稳定了,再换成事件驱动做精细回测。你想想看,如果每次改参数都要等半小时,那还怎么优化?

向量化回测的代码长这样:

# 向量化回测示例
def vectorized_backtest(data, signals):
    # 一次性计算持仓
    positions = signals.shift(1)  # 第二天开盘执行
    returns = data['close'].pct_change()
    strategy_returns = positions * returns
    cumulative = (1 + strategy_returns).cumprod()
    return cumulative

而事件驱动呢,就像我们前面写的那个类,一行一行地处理。说白了,向量化是用空间换时间,事件驱动是用时间换真实度。

回测框架的性能考量

做回测最怕什么?跑一次要等半天。我优化过的最夸张的一个框架,从45分钟优化到了3分钟。怎么做到的?

  • 用numpy替代pandas:pandas虽然方便,但循环起来慢。核心计算用numpy,能快10倍以上
  • 避免重复计算:比如移动平均线,算一次缓存起来,别每次循环都重新算
  • 多进程并行:参数优化时,把不同参数组合分到不同进程跑
  • 数据预加载:别在回测循环里读数据库,一次性加载到内存

我的经验:性能优化有个二八法则——80%的瓶颈在20%的代码上。先用profiler找出最慢的部分,再针对性优化。别一上来就搞什么多线程、C扩展,很多时候只是多了一个不必要的循环。

嗯,说到性能,还有一个容易被忽略的点:内存管理。如果你回测十年数据,每天几千只股票,DataFrame可能会吃掉几十G内存。我建议用float32代替float64,内存直接减半,精度损失几乎可以忽略。

回测框架核心知识体系 回测框架 数据模块 策略模块 执行模块 分析模块 获取行情 清洗对齐 缺失处理 信号生成 参数管理 规则引擎 模拟下单 滑点模型 手续费 收益计算 风险指标 归因分析 事件驱动回测 向量化回测 性能优化:numpy · 缓存 · 并行 · 内存 四个核心模块 + 两种回测方式 + 性能优化 = 完整的回测框架

最后说一句:回测框架没有银弹。你追求速度,就得牺牲一些真实度;你追求真实,就得忍受慢一点。关键是根据你的策略类型做取舍。做轮动模型嘛,我个人建议先用向量化快速迭代,等策略成熟了再上事件驱动做精细验证。