4、策略回测框架:Backtrader入门、回测引擎核心概念、编写第一个回测脚本
好,咱们进入正题。这一章聊的是回测框架,具体说就是 Backtrader。我个人觉得,这是目前 Python 量化圈里最顺手的工具之一。你想想看,一个框架能让你把策略逻辑、数据加载、交易执行、绩效分析全串起来,而且代码写起来还不费劲——这就是 Backtrader 的本事。
4.1 为什么选 Backtrader?
市面上回测框架不少,比如 Zipline、PyAlgoTrade、vn.py 等等。但我为什么推荐 Backtrader?说白了,三个原因:
- 轻量级:不需要装数据库,不需要配 Redis,pip install 就完事。
- 设计优雅:它的「策略-数据-交易」三层架构非常清晰,新手也能快速上手。
- 实盘可复用:回测代码稍微改改就能对接实盘接口,这一点我在项目中深有体会。
我记得有一次帮朋友调试一个多因子策略,用 Zipline 折腾了半天数据格式,换成 Backtrader 半小时就跑通了。嗯,工具选对了,效率翻倍。
4.2 回测引擎的核心概念
在写代码之前,咱们得先搞懂 Backtrader 的几个核心概念。别急,我画了一张图,你看完就明白了。
这张图把 Backtrader 的架构讲得很清楚了。核心就是 Cerebro 这个大脑,它负责把数据喂给策略,策略产生信号后交给 Broker 执行交易。分析器和观察者则是用来做绩效统计和日志记录的。
4.3 核心组件详解
4.3.1 Cerebro 引擎
Cerebro 是 Backtrader 的调度中心。你所有的操作——加数据、加策略、设手续费、跑回测——都得通过它。说白了,它就是那个「总指挥」。
核心方法一览:
adddata(data):添加行情数据addstrategy(strategy_class):添加策略类addsizer(sizer_class):设置仓位管理addanalyzer(analyzer_class):添加分析器run():启动回测plot():绘制结果图表
4.3.2 Data Feed 数据馈送
数据是回测的根基。Backtrader 支持多种数据源:CSV 文件、Pandas DataFrame、在线 API 等。我个人习惯用 CSV,因为稳定、可控。
数据格式要求很简单:时间、开盘价、最高价、最低价、收盘价、成交量,按这个顺序排列就行。列名可以自定义,但顺序别搞错。
我曾经踩过的坑:数据里的时间戳一定要是递增的,不能有跳跃或重复。有一次我用的数据里混了几条乱序数据,回测结果直接偏了 20%。从那以后,我每次加载数据都会先检查时间序列的完整性。
4.3.3 Strategy 策略
策略是回测的灵魂。你需要继承 bt.Strategy 类,然后实现两个核心方法:
__init__():初始化指标和变量next():每根 K 线都会调用一次,在这里写买卖逻辑
嗯,这里要注意:next() 里不要做耗时操作,因为回测时每根 K 线都会跑一遍。如果你在里面写了个循环几万次的复杂计算,回测速度会慢得让你怀疑人生。
4.3.4 Broker 交易执行
Broker 模拟了真实的交易环境。你可以设置:
| 参数 | 说明 | 默认值 |
|---|---|---|
| commission | 手续费率(如 0.001 表示千分之一) | 0.001 |
| cash | 初始资金 | 10000 |
| slippage | 滑点设置 | 0 |
| mult | 合约乘数(期货用) | 1 |
小技巧:刚开始回测时,先把手续费设为 0,滑点设为 0。等策略逻辑跑通了,再逐步加上真实成本。这样能快速定位问题是出在策略本身还是交易成本上。
4.4 编写第一个回测脚本
光说不练假把式。咱们直接写一个最简单的双均线策略回测脚本。这个策略的逻辑很简单:
- 当 5 日均线上穿 20 日均线时,买入
- 当 5 日均线下穿 20 日均线时,卖出
代码如下:
import backtrader as bt
# 1. 定义策略
class SmaCross(bt.Strategy):
params = dict(
pfast=5, # 短期均线周期
pslow=20 # 长期均线周期
)
def __init__(self):
# 计算均线指标
sma_fast = bt.ind.SMA(period=self.params.pfast)
sma_slow = bt.ind.SMA(period=self.params.pslow)
# 生成交叉信号
self.crossover = bt.ind.CrossOver(sma_fast, sma_slow)
def next(self):
# 没有持仓时,买入信号
if not self.position:
if self.crossover > 0:
self.buy()
# 有持仓时,卖出信号
elif self.crossover < 0:
self.sell()
# 2. 加载数据
data = bt.feeds.YahooFinanceCSVData(dataname='AAPL.csv')
# 3. 初始化 Cerebro 引擎
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(SmaCross)
# 4. 设置初始资金和手续费
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001)
# 5. 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
# 6. 运行回测
print('初始资金: %.2f' % cerebro.broker.getvalue())
results = cerebro.run()
print('最终资金: %.2f' % cerebro.broker.getvalue())
# 7. 输出分析结果
strat = results[0]
print('夏普比率:', strat.analyzers.sharpe.get_analysis())
print('年化收益率:', strat.analyzers.returns.get_analysis())
# 8. 绘制图表
cerebro.plot()
这段代码虽然短,但五脏俱全。你仔细看:
- 第 1-18 行:定义策略类,用
bt.ind.SMA计算均线,用bt.ind.CrossOver检测金叉死叉 - 第 21 行:加载数据,这里用的是 Yahoo Finance 的 CSV 格式
- 第 24-26 行:创建 Cerebro 引擎,添加数据和策略
- 第 29-30 行:设置初始资金 10 万,手续费千分之一
- 第 33-34 行:添加夏普比率和收益率分析器
- 第 37-38 行:跑回测,打印资金变化
- 第 41-42 行:输出分析结果
- 第 45 行:画图,直观看策略表现
运行结果示例:
初始资金: 100000.00
最终资金: 123456.78
夏普比率: 1.25
年化收益率: 12.3%
4.5 回测中容易踩的坑
写回测脚本不难,但写出靠谱的回测脚本,需要避开几个常见的坑。我一个个说:
4.5.1 未来函数
这是回测里最致命的错误。什么叫未来函数?就是你在回测时用到了「未来」的数据。比如在 __init__ 里计算指标时,Backtrader 默认会用全部数据来计算,这就引入了未来信息。
解决办法:在 __init__ 里只定义指标,不要在 next() 之前使用指标的值。Backtrader 的指标在 next() 中会自动按时间顺序更新,不会泄露未来数据。
4.5.2 滑点与手续费
我见过太多人回测时收益漂亮,实盘一跑就亏。原因很简单——没考虑滑点和手续费。尤其是高频策略,手续费能把利润吃光。
我曾经犯过的错:有个日内策略回测年化 80%,我兴冲冲地实盘了。结果一个月下来倒亏 5%。一查原因,滑点加上手续费,每笔交易成本比回测时高了 3 倍。从那以后,我回测时至少按实际成本的 1.5 倍来设置手续费。
4.5.3 过拟合
参数调得太完美,往往意味着过拟合。你想想看,一个策略在历史数据上表现完美,一到未来就失效,这有什么意义?
我的建议是:参数不要超过 3 个,而且一定要做样本外测试。把数据分成两段,前 70% 用来调参,后 30% 用来验证。如果样本外表现也不错,那这个策略才值得进一步研究。
4.6 本章小结
这一章咱们把 Backtrader 的核心概念和第一个回测脚本都过了一遍。你掌握了:
- Backtrader 的五大核心组件:Cerebro、Data Feed、Strategy、Broker、Analyzers
- 如何编写一个完整的双均线回测脚本
- 回测中常见的三个坑:未来函数、滑点手续费、过拟合
说实话,Backtrader 入门不难,但想用好它,需要你在实践中不断积累经验。下一章咱们会深入讲策略的编写技巧和高级用法。嗯,今天就先到这里,你可以动手跑跑上面的代码,感受一下回测的完整流程。
公众号:蓝海数据掘金营,微信deep3321