3、第一个回测程序:从零到一跑通你的策略
说实话,很多新手学量化回测,最容易卡在第一步——「我该先写什么?」。我个人习惯是,先让程序跑起来,哪怕它只是个最简单的移动平均线策略。今天我们就来写第一个回测程序,把整个流程走通。
你想想看,一个回测程序无非就这几步:导入工具、创建引擎、加载数据、写个策略、跑一下、看结果。嗯,就这么简单。我们一步步来。
3.1 导入库:先把工具箱备好
Backtrader 是个第三方库,你得先把它请进来。我一般会同时导入几个常用的辅助库,比如 datetime 处理时间,pandas 处理数据。
import backtrader as bt
import datetime
import pandas as pd
这里有个小细节:backtrader 官方缩写就是 bt,业内都这么写。你以后看别人的代码,看到 bt.Cerebro、bt.Strategy 就知道是 Backtrader 的东西。
backtrader 的版本也打印出来,方便排查问题。用 print(bt.__version__) 就行。
3.2 创建 Cerebro 引擎:回测的大脑
Cerebro 是 Backtrader 的核心引擎。你可以把它想象成一个「调度中心」——它负责加载数据、运行策略、计算收益、输出结果。创建它只需要一行代码:
cerebro = bt.Cerebro()
就这么简单?对,就这么简单。但别小看它,背后做了很多事。比如它默认会帮你处理订单、滑点、佣金等等。不过初期我们先用默认设置,后面再慢慢调。
3.3 加载数据:给引擎喂「原料」
回测需要历史数据。Backtrader 支持多种数据源:CSV 文件、Pandas DataFrame、甚至直接从 Yahoo Finance 拉取。我们先用最简单的——从本地 CSV 文件加载。
假设你有一个 000300.SH.csv 文件,里面是沪深300的日线数据,格式如下:
| 日期 | 开盘 | 最高 | 最低 | 收盘 | 成交量 |
|---|---|---|---|---|---|
| 2023-01-03 | 3880.12 | 3921.45 | 3875.33 | 3910.78 | 125678900 |
| 2023-01-04 | 3915.20 | 3940.11 | 3902.56 | 3925.44 | 132456700 |
加载代码也很直接:
data = bt.feeds.GenericCSVData(
dataname='000300.SH.csv',
dtformat='%Y-%m-%d',
datetime=0,
open=1,
high=2,
low=3,
close=4,
volume=5,
fromdate=datetime.datetime(2023, 1, 1),
todate=datetime.datetime(2023, 12, 31)
)
cerebro.adddata(data)
这里要注意:datetime=0 表示日期在第0列,open=1 表示开盘价在第1列,以此类推。列索引从0开始。我曾经因为列号写错,跑出来的结果完全不对,查了半天才发现是数据对不上。嗯,这个坑我踩过。
datetime=0, close=1。别想当然地认为「日期肯定在第0列」——先看一眼文件再说。
3.4 定义简单策略:写你的第一个交易逻辑
策略是回测的灵魂。Backtrader 里,策略是一个继承 bt.Strategy 的类。你需要重写两个方法:
__init__:初始化指标和变量next:每个交易日都会调用一次,在这里写买卖逻辑
我们写一个最简单的策略:当收盘价高于5日均线时买入,低于时卖出。
class MyFirstStrategy(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(
self.data.close, period=5
)
def next(self):
if not self.position: # 没有持仓
if self.data.close[0] > self.sma[0]:
self.buy(size=100)
else: # 有持仓
if self.data.close[0] < self.sma[0]:
self.sell(size=100)
你看,逻辑很直白:self.data.close[0] 是今天的收盘价,self.sma[0] 是今天的5日均线值。如果今天收盘价上穿均线,就买100股;如果下穿,就卖掉。
这里有个细节:self.position 是 Backtrader 自动维护的持仓对象。你不需要手动记录买了多少股,引擎会帮你算。
3.5 运行回测:让引擎转起来
数据加载好了,策略也写好了,接下来就是把策略注册到引擎,然后启动回测。
cerebro.addstrategy(MyFirstStrategy)
cerebro.broker.setcash(100000.0) # 初始资金10万
print('初始资金: %.2f' % cerebro.broker.getvalue())
results = cerebro.run()
print('最终资金: %.2f' % cerebro.broker.getvalue())
运行后你会看到类似这样的输出:
初始资金: 100000.00
最终资金: 102345.67
嗯,赚了2345.67元。当然,这只是个示例,实际收益取决于你的数据和策略参数。
为什么会这样?因为 cerebro.run() 会遍历所有交易日,在每个交易日调用策略的 next() 方法,执行买卖逻辑,并更新账户资金。整个过程是模拟的,不会真的下单。
3.6 查看结果:用图表说话
光看数字不够直观。Backtrader 内置了绘图功能,一行代码就能画出资金曲线、买卖点、指标线。
cerebro.plot()
它会弹出一个窗口,包含两个子图:上面是价格和均线,下面是资金曲线。红色箭头表示买入,绿色箭头表示卖出。一眼就能看出策略表现如何。
我个人习惯把图保存下来,方便复盘。可以这样:
import matplotlib.pyplot as plt
fig = cerebro.plot(style='candlestick')
fig[0][0].savefig('回测结果.png')
3.7 完整代码:直接复制就能跑
我把上面所有代码整合到一起,你复制到 Python 文件里,改一下 CSV 文件路径就能跑。
import backtrader as bt
import datetime
class MyFirstStrategy(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(
self.data.close, period=5
)
def next(self):
if not self.position:
if self.data.close[0] > self.sma[0]:
self.buy(size=100)
else:
if self.data.close[0] < self.sma[0]:
self.sell(size=100)
cerebro = bt.Cerebro()
cerebro.addstrategy(MyFirstStrategy)
data = bt.feeds.GenericCSVData(
dataname='000300.SH.csv',
dtformat='%Y-%m-%d',
datetime=0, open=1, high=2, low=3, close=4, volume=5,
fromdate=datetime.datetime(2023, 1, 1),
todate=datetime.datetime(2023, 12, 31)
)
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
print('初始资金: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('最终资金: %.2f' % cerebro.broker.getvalue())
cerebro.plot()
这段代码不到30行,但已经是一个完整的回测程序了。你想想看,从零到跑通第一个策略,其实就这么几步。后面我们会在这个基础上,一步步加功能:多品种、多周期、滑点、佣金、绩效分析……
嗯,今天就到这里。代码跑通了吗?如果遇到问题,先检查 CSV 文件路径和列索引对不对。这两个地方最容易出错。
上面这张图把整个流程串起来了。你可以看到,从导入库到分析优化,是一个闭环。跑完一次回测不是结束,而是开始——根据结果调整参数,再跑,再调,直到策略稳定。
公众号:蓝海资料掘金营,微信deep3321