2. 回测框架搭建:使用Python进行因子回测的基本流程与工具选择

做因子投资,回测框架就是你的实验台。没有它,再好的策略也只是纸上谈兵。

我刚开始做量化那会儿,也踩过不少坑。最惨的一次,回测年化收益40%,实盘直接亏了15%。后来才发现,是框架里一个不起眼的未来函数在作祟。嗯,从那以后,我对回测框架的搭建就格外较真了。

今天咱们就聊聊,怎么用Python搭一个靠谱的回测框架。说白了,就是让你在回测阶段就能发现那些实盘会暴雷的问题。

2.1 回测的核心流程

一个标准的因子回测流程,其实就五步:

  1. 数据准备 — 拿到干净、对齐的行情与财务数据
  2. 因子计算 — 按你的逻辑算出每个股票每天的因子值
  3. 信号生成 — 根据因子值决定买卖什么、买卖多少
  4. 组合构建 — 考虑交易成本、持仓限制,生成实际持仓
  5. 绩效评估 — 算收益、回撤、夏普比率,看策略行不行

你想想看,这五步里哪一步出了问题,结果都会失真。我见过有人把数据对齐搞错了,回测结果漂亮得不行,实盘却一塌糊涂。

核心原则:回测框架必须模拟真实交易环境。包括交易成本、滑点、涨跌停限制、停牌处理。少一个,你的回测就是自欺欺人。

2.2 工具选择:从零开始 vs 现成框架

Python里做回测,工具选择其实挺多的。我个人习惯分两种情况:

2.2.1 现成框架

框架 特点 适合场景
Backtrader 功能全面,社区活跃,支持多数据源 中低频策略、初学者
Zipline Quantopian出品,事件驱动,文档好 美股、学术研究
PyAlgoTrade 轻量级,易上手,支持回测与实盘 快速验证想法
vnpy 国内团队开发,支持期货、股票 CTA、高频交易

说实话,现成框架最大的好处是省事。但问题也明显:你很难完全控制底层逻辑。尤其是做因子研究时,很多细节(比如停牌处理、复权方式)框架默认的处理方式可能跟你的想法不一样。

2.2.2 自建框架

我在项目中遇到过好几次,用现成框架跑出来的结果,跟手动算的对不上。后来干脆自己写了一个轻量级的回测引擎。核心代码其实不长:

class FactorBacktest:
    def __init__(self, data, factor, rebalance_freq='monthly'):
        self.data = data          # 多级索引 DataFrame
        self.factor = factor      # 因子值 Series
        self.rebalance_freq = rebalance_freq
        
    def run(self):
        # 1. 对齐数据
        aligned = self._align_data()
        # 2. 生成调仓信号
        signals = self._generate_signals(aligned)
        # 3. 模拟交易(含成本)
        portfolio = self._simulate_trading(signals)
        # 4. 计算绩效
        return self._evaluate(portfolio)
    
    def _align_data(self):
        # 处理停牌、缺失值、复权
        pass
    
    def _generate_signals(self, data):
        # 分组排序,做多空组合
        pass
    
    def _simulate_trading(self, signals):
        # 考虑滑点、手续费、涨跌停
        pass
    
    def _evaluate(self, portfolio):
        # 计算收益、回撤、夏普
        pass
我的建议:刚开始做因子研究,先用现成框架快速验证想法。等你要深入分析某个因子时,再自己写一个精简版。这样效率最高。

2.3 数据处理的坑

回测框架里,数据处理是最容易出问题的地方。我总结了几条血泪教训:

  • 前复权 vs 后复权 — 回测必须用后复权,否则分红送股会扭曲收益率计算
  • 停牌处理 — 停牌期间不能交易,要用前一天的收盘价继续计算
  • 涨跌停 — 涨停买不进,跌停卖不出,这个必须模拟
  • 幸存者偏差 — 要用历史快照数据,不能用当前成分股回测过去

我曾经因为用了前复权数据,回测结果比实际高了3个点。查了三天才发现问题。嗯,从那以后我就在框架里加了个数据校验模块。

2.4 核心逻辑:因子回测的骨架

下面这张图,是我自己画的一个因子回测框架的核心流程。你看一眼就能明白整体结构:

因子回测框架核心流程 数据层 行情数据 · 财务数据 · 复权处理 · 停牌标记 · 涨跌停状态 因子计算层 因子值计算 · 标准化 · 中性化 · 行业市值调整 信号生成层 分组排序 · 多空组合 · 权重分配 · 调仓频率 交易模拟层 滑点模型 · 手续费 · 冲击成本 · 持仓限制 绩效评估层 年化收益 · 最大回撤 · 夏普比率 · 换手率 · IC分析 输入 输出
注意:很多人在搭建框架时,只关注因子计算和绩效评估,忽略了交易模拟层。但实盘和回测最大的差异,往往就出在交易成本上。我曾经回测时没考虑冲击成本,结果实盘收益直接腰斩。

2.5 实战:一个简单的因子回测示例

说了这么多理论,咱们来点实际的。下面是一个用Python实现的简单因子回测:

import pandas as pd
import numpy as np
from scipy import stats

def factor_backtest(price_df, factor_series, rebalance_dates):
    """
    最简单的因子回测:每月调仓,等权做多top10%
    """
    results = []
    
    for i in range(len(rebalance_dates)-1):
        date = rebalance_dates[i]
        next_date = rebalance_dates[i+1]
        
        # 获取当期因子值
        factor_t = factor_series.loc[date]
        
        # 选股:取因子值最高的10%
        threshold = factor_t.quantile(0.9)
        selected = factor_t[factor_t >= threshold].index
        
        # 计算下期收益
        ret = price_df.loc[next_date] / price_df.loc[date] - 1
        portfolio_ret = ret[selected].mean()
        
        results.append({
            'date': date,
            'return': portfolio_ret,
            'n_stocks': len(selected)
        })
    
    result_df = pd.DataFrame(results)
    return result_df

# 使用示例
# result = factor_backtest(price_data, momentum_factor, monthly_dates)
# print(f"年化收益: {result['return'].mean() * 12:.2%}")
小技巧:写回测框架时,先把最核心的逻辑跑通,再慢慢加细节。我一般先做「不考虑成本、等权、月度调仓」的版本,验证因子有效后,再逐步加入交易成本、行业中性化等复杂处理。

2.6 避坑指南

最后,分享几个我踩过的坑,希望能帮你少走弯路:

  • 未来函数 — 回测时用了未来才知道的数据。比如用当月的财务数据去预测当月的收益。我曾经犯过这个错,回测结果好得离谱,后来发现是数据对齐出了问题。
  • 前视偏差 — 用到了未来的信息来选股。比如用今天的收盘价去计算昨天的因子值。这种错误很隐蔽,但后果很严重。
  • 过度优化 — 为了追求回测收益,不断调整参数。结果就是过拟合,实盘一塌糊涂。我的原则是:参数不超过3个,且必须有经济含义。
  • 忽略交易成本 — 高频调仓的策略,交易成本可能吃掉所有收益。我见过一个策略,回测年化20%,加上千分之二的交易成本后,直接变成8%。

搭建回测框架,说白了就是尽可能模拟真实交易环境。你模拟得越真实,回测结果就越可信。别嫌麻烦,这些细节决定了你的策略到底是「纸上富贵」还是「真金白银」。

专注资料整理