backtrader是基于Python的量化回测框架,功能丰富,操作方便。其优点是运行速度快,支持pandas的矢量运算;内置多种技术指标计算,还支持股票分析技术指标库talib;支持参数自动寻优运算;支持多品种(股票、期货、期权、外汇和数字货币)、多策略、多周期(Ticks、秒、分、日、周、月和年)的回测和交易;支持PyFlio、empyrica分析模块库、alphalens多因子分析模块库等;扩展灵活,可以集成TensorFlow、PyTorch和Keras等机器学习、神经网络分析模块。
一、安装
pip install backtrader
官网:Backtrader
安装需求:
Python 2.7
Python 3.2 / 3.3/ 3.4 / 3.5
pypy/pypy3
绘图需要用到Matplotlib,最新版本建议>=1.4.1。
包引用:import backtrader as bt
二、backtrader功能
backtrader包主要包括:框架(Cerebro)、数据加载(Data Feed)、交易策略(Strategies)、技术指标(Indicators)、订单(Orders)、观察者(Observers)、测量评估(Analyzers)、订单(Orders)、经纪人(Broker)、实盘交易(Live Trading)、结果可视化(Plotting)等部分
整体结构如下图所示:
下面对其中的关键部分进行详细说明。
1.数据类型
backtrader回测的数据类型是由一系列的点组成的Lines,通常包括以下类别的数据:Open(开盘价)、High(最高价)、Low(最低价)、 Close(收盘价)、Volume(成交量)、OpenInterest(未平仓权益)。Data Feeds(数据加载)、Indicators(技术指标)和Strategies(策略)都会生成 Lines。价格数据中的所有的开盘价按时间组成一条 Line,因此一组含有以上6个类别的价格数据,共有6条 Lines。如果算上DateTime(时间,可以看作是一组数据的主键),一共有7条 Lines。
一条Line数据的下标为0表示访问当前时刻数据,-1表示访问最后一个数据。因此,在回测过程中,无需知道已经处理了多少条/分钟/天/月,”0”一直指向当前值,下标 -1 来访问最后一个值。
2.交易策略(Strategy)
该模块是回测系统最核心的部分,需要设计交易决策,得出买入/卖出信号。策略类代码包含重要的参数和用于执行策略的功能,要定义的参数或函数名如下:
(1)params:全局参数,可选,用于更改交易策略中变量/参数的值,可用于参数调优。
(2)log:日志,可选,用于记录策略的执行日志,可以打印出该函数提供的日期时间和txt变量。
(3)__init__:用于初始化交易策略,在其中声明的任何指标都会在next()方法调用之前进行计算。部分python操作符不支持,需要使用bt内置函数来处理,例如bt.And, bt.Or, bt.All, bt.Any等。
(4)notify_order,可选,用于跟踪交易订单(order)的状态。order具有提交,接受,买入/卖出执行和价格,已取消/拒绝等状态。
(5)notify_trade,可选,用于跟踪交易的状态,任何已平仓的交易都将报告毛利和净利润。
(6)next,必选,用于制定交易策略的函数,策略模块最核心的部分。
3.技术指标(Indicators)
实际上,策略的构建都需要指标来实现,该部分包括很多已经实现的指标,下面是一些比较常用的指标:
Average:指定周期的算术平均值。
SimpleMovingAverage(SMA):简单移动平均
WeightedAverage:加权移动平均
MACD:滑动平均
ExponentialMovingAverage(EMA):指数移动平均
RSI_SMA/RSI_EMA:
BollingerBands(BOLL):布林线
DoubleExponentialMovingAverage(DEMA):双指数移动平均
WilliamsR(WR):威廉指标
在指标中也集成了TA-Lib库,实际使用中发现无法正常调用,不知道是不是跟我没有安装该库有关。另外,还可以通过继承Indicator或其它已存在的指标类来实现自定义指标,方便用户加入自己开发的指标。
# 继承自bt.Indicator或其他已存在的指标类
class DummyInd(bt.Indicator):
# 定义持有的lines,至少需要1个line
lines = ('dummyline',)
# params参数可选
params = (('value', 5),)
# plotinfo可选,用来控制绘图行为
plotinfo = dict(subplot=False)
# __init__方法或next方法必选
def __init__(self):
self.lines.dummyline = bt.Max(0.0, self.params.value)
4.订单(Orders)
将策略中逻辑做出的决策转换为适合经纪人执行操作的消息,通常在交易策略中调用。
5.经纪人(Broker)
通过设置回测的初始自己、佣金费率、税收费率、滑点率等交易条件,模拟不同的订单类型,并根据现金检查订单,计算每次交易的现金和权益,保存交易数据。
6.策略评估(Analyzers)
用于分析交易策略的利润和风险,分析交易系统的绩效。
7.实盘交易(Live Trading)
从1.5.0开始支持实盘数据和实时交易。具体请参考:Live Trading。
8.观察者(Observers)
用于记录交易过程,包括现金、权益、费用以及交易动作、买卖订单等数据。
9.结果可视化(Plotting)
通过图形的方式显示交易测量回测的结果,绘图显示的结果包括三部分类型:现金及权益、交易损益、买卖动作。
绘图设置通过plotinfo来设置,其参数主要有:plot(是否绘图,默认为True),subplot(是否单独窗口绘图,默认为True,MA类指标该参数为False),plotname(指标图名,默认为指标类名),plotabove(绘图位置在数据上方,默认为False),plotlinelabels, plotymargin, plotyticks,plothlines, plotyhlines, plotforce。
注意:由于技术指标的不同,可视化结果中所包含的数据可能不同,因此需要根据技术指标来选择其所支持的参数进行设置。
三、系统流程
在backtrader内部通过对数据和指标的处理执行策略,并记录所有的交易过程和数据输出给用户。
backtrader的入口为Cerebro类,该类将所有输入(Data Feeds)、执行 (Stratgegies)、观察者(Observers)、评价(Analyzers) 和文档 (Writers)整合起来,实现回测以及交易,并返回结果和图表。
(1)创建实例
cerebro = bt.Cerebro() # 创建回测系统实例
(2)关联输入数据
cerebro.adddata(data) # 将数据加载至回测系统
(3)关联交易策略
cerebro.addstrategy(BollStrategy,nk=14,m=2,printlog=True)
(4)关联其他可选功能
addwriter():指定交易过程和数据保存的方式。
addanalyzer():增加策略评估。
addobserver():增加观察者。
addobservermulti()
(5)指定经纪人
broker = MyBroker()
cerebro.broker = broker
可以根据不同类型的交易对象设置经纪人模型。
(6)接收消息
接收回测消息有三种方式:在Cerebro类的实例中通过addnotifycallback(callback)增加回调函数、在Strategy子类中重载notify_store()方法、继承Cerebro类并重载notify_store()方法
(7)执行回测并返回结果
result=cerebro.run(maxcpus=1)
(8)记录交易信息
这个功能是通过观察者(Observers)实现,有三种类型:
经纪人观察者(Broker)保存现金和权益信息。交易观察者(Trades)记录交易的有效性。买卖观察者(Buy/Sell)记录所有交易执行的操。
(9)绘图显示
cerebro.plot()
绘图显示底层实际上是调用Matplotlib包实现的。
四、使用示例
下面以BOLL线指标为例,展示backtrader中策略的编写以及整个回测的流程。BOLL线指标的详细介绍可参考文章《BOLL指标计算》,本次回测采用的简易BOLL策略如下:
(1)当收盘价下跌跌破BOLL线通道下轨卖出;
(2)当收盘价上涨突破BOLL线通道上轨卖出。
本次将测试中采用招商银行的前复权日K线数据,将BOLL线的周期N设为13,倍数因子为默认值,并在交易回测中加入了交易过程打印功能。
示例代码如下:
from datetime import datetime
import akshare as ak
import pandas as pd
import backtrader as bt
class BollStrategy(bt.Strategy): #BOLL策略程序
params = (("nk", 13), #求均值的天数
('printlog', False),) #打印log
def __init__(self): #初始化
self.data_close = self.datas[0].close # 指定价格序列
# 初始化交易指令、买卖价格和手续费
self.order = None
self.buy_price = None
self.buy_comm = None
#Boll指标计算
self.top=bt.indicators.BollingerBands(self.datas[0],period=self.params.nk).top
self.bot=bt.indicators.BollingerBands(self.datas[0],period=self.params.nk).bot
# 添加移动均线指标
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.nk )
def next(self): #买卖策略
if self.order: # 检查是否有指令等待执行
return
# 检查是否持仓
"""
if not self.position: # 没有持仓
if self.data_close[0] > self.sma[0]: # 执行买入条件判断:收盘价格上涨突破20日均线
self.order = self.buy(size=100) # 执行买入
else:
if self.data_close[0] < self.sma[0]: # 执行卖出条件判断:收盘价格跌破20日均线
self.order = self.sell(size=100) # 执行卖出
"""
if not self.position: # 没有持仓
if self.data_close[0] < self.bot[0]: # 收盘价格跌破下轨
self.log("BUY CREATE, %.2f" % self.data_close[0])
self.order = self.buy() #执行买入
else:
if self.data_close[0] > self.top[0]: # 收盘价格上涨突破上轨
self.log("SELL CREATE, %.2f" % self.data_close[0])
self.order = self.sell() #执行卖出
def log(self, txt, dt=None, do_print=False): #日志函数
if self.params.printlog or do_print:
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def notify_order(self, order): #记录交易执行情况
# 如果order为submitted/accepted,返回空
if order.status in [order.Submitted, order.Accepted]:
return
# 指令为buy/sell,报告价格结果
if order.status in [order.Completed]:
if order.isbuy():
self.log(
f"买入:\n价格:{order.executed.price},\
成本:{order.executed.value},\
手续费:{order.executed.comm}"
)
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else:
self.log(
f"卖出:\n价格:{order.executed.price},\
成本: {order.executed.value},\
手续费{order.executed.comm}"
)
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log("交易失败") #指令取消/交易失败, 报告结果
self.order = None
def notify_trade(self, trade): #记录交易收益情况
if not trade.isclosed:
return
self.log(f"策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}")
def stop(self): #回测结束后输出结果
self.log("(BOLL线: %2d日) 期末总资金 %.2f" % (self.params.nk, self.broker.getvalue()), do_print=True)
code="600036" #股票代码
start_cash=1000000 #初始自己为1000000
stake=100 #单次交易数量为1手
commfee=0.0005 #佣金为万5
sdate='20210101' #回测时间段
edate='20220930'
cerebro = bt.Cerebro() # 创建回测系统实例
# 利用AKShare获取股票的前复权数据的前6列
df_qfq = ak.stock_zh_a_hist(symbol=code, adjust="qfq", start_date=sdate, end_date=edate).iloc[:, :6]
# 处理字段命名,以符合Backtrader的要求
df_qfq.columns = ['date','open','close','high','low','volume',]
# 把date作为日期索引,以符合Backtrader的要求
df_qfq.index = pd.to_datetime(df_qfq['date'])
start_date = datetime.strptime(sdate,"%Y%m%d") #转换日期格式
end_date = datetime.strptime(edate,"%Y%m%d")
#start_date=datetime(2022,1,4)
#end_date=datetime(2022,9,16)
data = bt.feeds.PandasData(dataname=df_qfq, fromdate=start_date, todate=end_date) #规范化数据格式
cerebro.adddata(data) #加载数据
cerebro.addstrategy(BollStrategy,nk=13,printlog=True) #加载交易策略
cerebro.broker.setcash(start_cash) # broker设置资金
cerebro.broker.setcommission(commission=commfee) # broker手续费
cerebro.addsizer(bt.sizers.FixedSize, stake=stake) # 设置买入数量
print("期初总资金: %.2f" % start_cash)
cerebro.run() #运行回测
end_value=cerebro.broker.getvalue() # 获取回测结束后的总资金
print("期末总资金: %.2f" % end_value)
cerebro.plot()
注意:
1.backtrader的输入数据需要遵循其标准。对于pandas的标准就是这些列的名字是open、close、high、low、volume。
2.backtrader要求pandas下的DataFrame的index是时间,所以设置要将date设置为索引。
3.backtrader的回测日期参数类型为datetime。
结果和交易记录如下图所示:
以上只是对的一个简单BOLL策略进行了回测演示,只是回测中万里长征的第一步。实际使用中还需要做大量的测试工作,例如不同交易费率、不同时间段、不同头寸大小等进行测试,加入止损和止盈等等,最后还要根据结果来修改交易策略。
-----------------------------------
原创不易,请多支持!