3、第一个回测程序:从零到一跑通你的策略

说实话,很多新手学量化回测,最容易卡在第一步——「我该先写什么?」。我个人习惯是,先让程序跑起来,哪怕它只是个最简单的移动平均线策略。今天我们就来写第一个回测程序,把整个流程走通。

你想想看,一个回测程序无非就这几步:导入工具、创建引擎、加载数据、写个策略、跑一下、看结果。嗯,就这么简单。我们一步步来。

3.1 导入库:先把工具箱备好

Backtrader 是个第三方库,你得先把它请进来。我一般会同时导入几个常用的辅助库,比如 datetime 处理时间,pandas 处理数据。

import backtrader as bt
import datetime
import pandas as pd

这里有个小细节:backtrader 官方缩写就是 bt,业内都这么写。你以后看别人的代码,看到 bt.Cerebrobt.Strategy 就知道是 Backtrader 的东西。

我的习惯: 我会把 backtrader 的版本也打印出来,方便排查问题。用 print(bt.__version__) 就行。

3.2 创建 Cerebro 引擎:回测的大脑

Cerebro 是 Backtrader 的核心引擎。你可以把它想象成一个「调度中心」——它负责加载数据、运行策略、计算收益、输出结果。创建它只需要一行代码:

cerebro = bt.Cerebro()

就这么简单?对,就这么简单。但别小看它,背后做了很多事。比如它默认会帮你处理订单、滑点、佣金等等。不过初期我们先用默认设置,后面再慢慢调。

重要概念: 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开始。我曾经因为列号写错,跑出来的结果完全不对,查了半天才发现是数据对不上。嗯,这个坑我踩过。

避坑指南: CSV 文件的列顺序必须和参数一一对应。如果文件里第一列是日期,第二列是收盘价,那你的参数就得写成 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 文件路径和列索引对不对。这两个地方最容易出错。

小技巧: 如果不想用 CSV 文件,可以用 Backtrader 自带的 Yahoo Finance 数据源。不过需要联网,而且 Yahoo 的接口有时不稳定。我个人还是推荐用本地 CSV,可控性更强。
第一个回测程序核心流程 导入库 import backtrader 创建Cerebro引擎 cerebro = bt.Cerebro() 加载数据 cerebro.adddata() 定义策略 class Strategy 运行回测 cerebro.run() 查看结果 cerebro.plot() 分析优化 调整参数 迭代优化循环 每个步骤都有对应的Backtrader API,初学者按此顺序编写即可 核心步骤 执行步骤 输出步骤 优化步骤

上面这张图把整个流程串起来了。你可以看到,从导入库到分析优化,是一个闭环。跑完一次回测不是结束,而是开始——根据结果调整参数,再跑,再调,直到策略稳定。

公众号:蓝海资料掘金营,微信deep3321