python版本3.8.9,工具是pycharm。
当股价突破60日均线则买入100股,当股价击穿60日均线则卖出之前买入的100股。不考虑佣金、印花税等任何其他因素。
Backtrader回测可以分成三步:第一步编写策略(Strategy);第二步加载数据(feeds);第三步回测运行(Cerebro)。接下来分段展示:
第一步编写策略(Strategy):
"""
一个最基本的backtrader单均线策略示例
只调用了backtrader和datetime
"""
from datetime import datetime
import backtrader as bt
# 第一步:编写策略(基于bt.Strategy)
class SingleSmaStrategy(bt.Strategy):
"""
一个基本的单均线策略
"""
# 构造函数,创建策略所需指标simple_ma(简单移动均线)
def __init__(self):
# bt.indicators.SMA是调用backtrader自带指标SMA
self.simple_ma = bt.indicators.SMA(
self.datas[0].close,
period=60)
# next函数,策略的主要部分,判断价格是否穿越均线在next函数中完成
def next(self):
# 判断仓位是否为0(self.position)
# 如果仓位为0且价格突破均线则买入100股
if not self.position.size:
# 价格是否穿越均线的判断,close[-1]是上一日收盘价
if self.datas[0].close[-1] < self.simple_ma[-1]
and self.datas[0].close[0] > self.simple_ma[0]:
self.buy(size=100)
# 如果仓位不为0,执行卖出条件判断
elif self.datas[0].close[-1] > self.simple_ma[-1]
and self.datas[0].close[0] < self.simple_ma[0]:
self.sell(size=100)
有几点需要注意:
1.backtrader在某些写法上非常灵活,但这种灵活有时候会给初学者造成困扰,比如bt.indicators.SMA与bt.ind.SmoothedMovingAverage是等效的。
2.backtrader在数据处理上有一个数据集的概念,比如我一共有10支股票数据需要回测,我通过接下来讲的“第二步加载数据”将10支股票数据都加载进来,那么第一支股票数据就标记为datas[0],第二支股票数据就标记为datas[1],并依此类推。
3.backtrader用open[0]表示当日开盘价,close[0]表示当日收盘价,close[-1]表示上一日收盘价,close[1]表示下一日收盘价,并依此类推。
第二步加载数据:
# 第二步:通过"feeds"加载数据
# 我电脑上数据保存位置
datapath = "G:/Stocks_date/002060.csv"
# 注意feeds表头与csv数据列内容的对应关系
data = bt.feeds.GenericCSVData(
dataname=datapath,
datetime=0,
open=1,
high=2,
low=3,
close=4,
volume=5,
openinterest=-1,
dtformat=("%Y%m%d"),
# 加载fromdate 至 todate之间的数据
fromdate=datetime(2011, 4, 26),
todate=datetime(2021, 9, 10))
有几点需要注意:
1.backtrader加载数据的方式多种多样,我采用是本地csv数据,数据类型为股票前复权日线数据,在初学阶段,本地csv数据易获取,100块可以去TB上买一大堆,且csv性能优于pandas的Dataframe。
2.我采用的csv数据格式如图:
通过excel应该可以更直观的表现出数据结构:
3. backtrader在加载数据时会自动处理数据的格式,这很便利,但如果你的数据格式不标准,就很可能引发报错,比如我最开始采用的数据,在volume列,某些数据是带有双引号的,如下图
这导致该数据变成了string型,但 backtrader需要他是float型,这会报错。我文件中日期列数据是20060810的格式,不是2006/08/10或者2006-08-10,所以在数据feeds过程中,我的dtformat是dtformat=("%Y%m%d"),另外要注意数据列的对应关系。
4.在设置数据文件路径一句,即:datapath = "G:/Stocks_date/002060.csv"。windows系统默认提供的文件路径是G:Stocks_date 02060.csv,在代码编辑器里看起来会比较明显:
因为“”在python中是转义字符,所以采用windows的默认路径会有问题,我推荐采用“”代替“/”来解决这个问题 。
以上都是一些没有什么难度的细节问题,但很有可能会导致初学者陷入错误排查的泥潭,并进一步引起暴躁,自我怀疑乃至放弃学习。
第三步回测运行(Cerebro):
# 实例化回测引擎
cerebro = bt.Cerebro()
# 添加数据
cerebro.adddata(data)
# 添加策略
cerebro.addstrategy(SingleSmaStrategy)
# 设置现金
cerebro.broker.setcash(10000.00)
# 运行回测
cerebro.run()
print(f"最终市值为:{cerebro.broker.getvalue()}")
想象一下,backtrader就像一辆汽车,第一部分编写策略就好像制定路线,第二步加载数据就好像准备汽油,而第三步的cerebro就像开车出发的一系列动作:把路线在导航里设置好,给车加好油,然后坐进车里,设置好速度,启动引擎,汽车就自动的驶向目标了。
代码的完全体:
from datetime import datetime
import backtrader as bt
# 第一步:编写策略
class SingleSmaStrategy(bt.Strategy):
"""
一个基本的单均线策略
"""
# 构造函数,创建策略所需指标simple_ma(简单移动均线)
def __init__(self):
self.simple_ma = bt.indicators.SMA(
self.datas[0].close,
period=60)
self.a = bt.ind.SmoothedMovingAverage
# next函数,策略的主要部分,判断价格是否穿越均线在next函数中完成
def next(self):
# 判断仓位是否为0(self.position)
if not self.position.size:
# 价格是否穿越均线的判断,close[-1]是上一日收盘价
if self.datas[0].close[-1] < self.simple_ma[-1]
and self.datas[0].close[0] > self.simple_ma[0]:
self.buy(size=100)
# 如果仓位不为0,执行卖出条件判断
elif self.datas[0].close[-1] > self.simple_ma[-1]
and self.datas[0].close[0] < self.simple_ma[0]:
self.sell(size=100)
# 第二步:通过"feeds"加载数据
datapath = "G:/Stocks_date/002060.csv"
# 注意feeds表头与csv数据列内容的对应关系
data = bt.feeds.GenericCSVData(
dataname=datapath,
datetime=0,
open=1,
high=2,
low=3,
close=4,
volume=5,
openinterest=-1,
dtformat=("%Y%m%d"),
# 加载fromdate 至 todate之间的数据
fromdate=datetime(2011, 4, 26),
todate=datetime(2021, 9, 10))
# 第三部 运行cerebro(大脑)程序
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(SingleSmaStrategy)
cerebro.broker.setcash(10000.00)
cerebro.run()
print(f"最终市值为:{cerebro.broker.getvalue()}")
把这份代码复制下来,按照我上文提到的格式准备好csv格式数据文件,在代码中设置好csv文件路径,那么这份代码100%可以运行。
需求是前进的起点、动力和方向,我是差等生,我能学会,你更能学会。



