回测框架搭建:从零开始构建你的量化引擎
做量化投资这些年,我搭建过不下五个回测框架。说实话,每次重构都有新感悟。回测框架就像房子的地基——地基没打好,后面装修得再漂亮也白搭。今天咱们就聊聊,怎么用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,内存直接减半,精度损失几乎可以忽略。
最后说一句:回测框架没有银弹。你追求速度,就得牺牲一些真实度;你追求真实,就得忍受慢一点。关键是根据你的策略类型做取舍。做轮动模型嘛,我个人建议先用向量化快速迭代,等策略成熟了再上事件驱动做精细验证。