Backtrader-量化教程-01
Backtrader 介绍
Backtrader 的背景
Backtrader 是 Daniel Rodriguez 在 2015 年 1 月 10 发布在其 GitHub(https://github.com/mementum/backtrader) 上的一个开源项目。目前依据 Backtrader 官网的介绍,其可以支持股票、期货、期权、基金、外汇、加密货币等资产的量化回测和实盘交易(国外为主,国内需要定制化)的 Python 框架。从 2015 年 6 月 3 日发布 Backtrader 的第一个正式版本,版本号为 1.0.0.21。截止目前已经发布 141 个版本,最新的版本是在 2020 年 7 月 3 号发布的:1.9.76.123,其中 1.9.76 代表主体功能的版本,后面的 123 表示 Backtrader 中可以构建的指标个数。从最初的 1.0.0.21 到最新的 1.9.76.123,Backtrader 已经从当初的 21 个指标更新到目前的 123 个指标,再加上 TA-Lib 等第三方库的支持,Backtrader 的指标计算能力已经十分强大。
一个开源项目的成功除了需要开源作者的项目设计能力和长期的坚持,还需要丰富的文档支持、广大社区爱好者的不断使用和反馈意见。Backtrader 在其官网:https://www.backtrader.com/ 撰写了丰富详实的文档(有部分功能文档写的不清晰),其中还提供了 https://community.backtrader.com/ 的讨论区可以让广大爱好者在上面交流 backtrader 的使用经验和量化策略交流等。
Backtrader 的优缺点
目前类似 Backtrader 的量化框架比较多,国内优秀的开源作者们也做出了很多优秀的基于 C++ 或者 Python 的支持量化回测和实盘交易的框架。对于各位小伙伴来说选择一款合适的量化框架还是比较重要的。依据目前笔者的经验,主要是看是否开源,是否有丰富的文档支持,是否有活跃的社区支持等,当然最重要的是要跟自己的需求相匹配。在阅读框架的文档或者源码的过程,也是提高自己对交易和代码理解的过程,从优秀的开发者身上学习策略编写的经验和框架搭建的经验也是广大量化爱好者在赚钱之外能获得提高的方面。
工欲善其事,必先利其器。对量化投资感兴趣的小伙伴,可以先从开源的量化交易框架入手,从获取数据、清洗数据、存储数据到编写量化策略进行回测及策略评价,最后进行模拟与实盘交易过程体验下量化投资的全流程。本次介绍的 Backtrader 具有强大的量化回测功能,结合 AKShare 等开源财经数据接口库,可以比较轻松的实现量化策略的数据获取、策略回测。而后期的实盘交易,在股票市场可以选择手动交易,在期货市场可以选择合适的期货接口进行自动化交易。目前随着机器学习和深度学习的发展,目前很多大型的私募发型了很多基于机器学习的量化产品,因为 Backtrader 是基于 Python 语言开发的,所以对 Python 机器学习及深度学习算法有很好的兼容性,小伙伴在做策略的时候可以将相关算法融入到交易策略中。
上述部分主要是对 Backtrader 的优点简单介绍,虽然 Backtrader 的优点很多,但是其强大的功能,也伴随着其较复杂的源码编写过程。各位阅读过 Backtrader 源码的小伙伴肯定发现其中编写了很多元类,元类是 Python 面向对象编程中比较底层的功能,简而言之就是创造类的类,除了元类之外还编写了很多的抽象基类。再加上 Backtrader 需要考虑对 Python 2 和 Python 3 的兼容性,导致其代码编写有一定的复杂性。很多已经在 Python 3 中可以通过调用内置模块解决的问题在 Backtrader 中却需要编写较复杂的实现过程。以上都是在代码实现层面的问题,而在代码风格层面,由于 Backtrader 诞生较早,其编写的代码风格未完全安装 PEP 8 的风格,导致在读其源码或者使用框架的过程中会出现一定的不适。在功能层面,由于 Backtrader 的主要开发者都在国外,比如默认绘图中的绿色 K 线表示上涨,红色 K 线表示下跌,刚好与国内 K 线逻辑相反,而在实盘方面,还没有合适的官方或者第三方模型支持国内的实盘交易。再比如在 Tick 级别回测上也会遇到速度较慢等问题。
如何入门 Backtrader
虽然 Backtrader 的优点很多,因为其文档和社区内容都是英文撰写,虽然也有很多中文的文章讲述如何使用 Backtrader 来进行量化回测,但是大部分缺乏完整性(代码或者数据无法获取、没有更新等),讲述的重点是在交易策略实现上,对 Backtrader 的功能解读讲解的太少等。对于刚入门或者想进阶的小伙伴会遇到各种各样的问题。笔者在 AKShare 的文档-策略示例(https://www.akshare.xyz/demo.html)中用 Backtrader 框架编写了部分策略(主要是为了演示如何使用 AKShare 的数据接口),依然收到小伙伴的各种提问,比如环境配置不对、无法绘制图形、没有相关统计指标、无法进行多股票回测等问题。在解答各位小伙伴问题的过程中,笔者对 Backtrader 的源码、文档和相关文章进行了一定的研究和阅读,后续会在【数据科学实战】公众号撰写 Backtrader 结合 AKShare 的使用教程,将数据接口和回测框架紧密结合起来,来实现多种量化策略,敬请关注!同时也会在知识星球【数据科学家】中进行视频直播,有兴趣的小伙伴可以联系小助手加入星球。后续更多Backtrader系列文章和非公开代码会放在星球内,会员专属福利!
Backtrader 策略演示
下面代码是一个通过 AKShare 来获取数据,利用 Backtrader 来进行量化回测并进行策略参数优化的案例代码,可以在文档 https://www.akshare.xyz/demo.html 中查看。在后续的文章中会介绍如何使用 Backtrader 来一步一步实现该策略代码,并且在后期会增加 Python 面向对象编程、多股票投资组合回测、多时间粒度回测等内容,通过讲解其中的指标专题、订单专题、分析者专题等来详细了解如何编写自定义的指标和各种订单的编写、最后输出图形化的评价指标并且利用交易接口进行实盘交易。
from datetime import datetime
import akshare as ak
import backtrader as bt
import matplotlib.pyplot as plt # 由于 Backtrader 的问题,此处要求 pip install matplotlib==3.2.2
import pandas as pd
plt.rcParams["font.sans-serif"] = ["SimHei"] # 设置画图时的中文显示
plt.rcParams["axes.unicode_minus"] = False # 设置画图时的负号显示
class MyStrategy(bt.Strategy):
"""
主策略程序
"""
params = (("maperiod", 20),
('printlog', False),) # 全局设定交易策略的参数, maperiod 是 MA 均值的长度
def __init__(self):
"""
初始化函数
"""
self.data_close = self.datas[0].close # 指定价格序列
# 初始化交易指令、买卖价格和手续费
self.order = None
self.buy_price = None
self.buy_comm = None
# 添加移动均线指标
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod
)
def next(self):
"""
主逻辑
"""
# self.log(f'收盘价, {data_close[0]}') # 记录收盘价
if self.order: # 检查是否有指令等待执行
return
# 检查是否持仓
if not self.position: # 没有持仓
# 执行买入条件判断:收盘价格上涨突破15日均线
if self.data_close[0] > self.sma[0]:
self.log("BUY CREATE, %.2f" % self.data_close[0])
# 执行买入
self.order = self.buy()
else:
# 执行卖出条件判断:收盘价格跌破15日均线
if self.data_close[0] < self.sma[0]:
self.log("SELL CREATE, %.2f" % self.data_close[0])
# 执行卖出
self.order = self.sell()
def log(self, txt, dt=None, do_print=False):
"""
Logging function fot this strategy
"""
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
# 如果order为buy/sell executed,报告价格结果
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("(MA均线: %2d日) 期末总资金 %.2f" % (self.params.maperiod, self.broker.getvalue()), do_print=True)
def main(code="600070", start_cash=1000000, stake=100, commission_fee=0.001):
cerebro = bt.Cerebro() # 创建主控制器
cerebro.optstrategy(MyStrategy, maperiod=range(3, 31)) # 导入策略参数寻优
# 利用 AKShare 获取股票的后复权数据,这里只获取前 6 列
stock_hfq_df = ak.stock_zh_a_hist(symbol=code, adjust="hfq", start_date='20000101', end_date='20210617').iloc[:, :6]
# 处理字段命名,以符合 Backtrader 的要求
stock_hfq_df.columns = [
'date',
'open',
'close',
'high',
'low',
'volume',
]
# 把 date 作为日期索引,以符合 Backtrader 的要求
stock_hfq_df.index = pd.to_datetime(stock_hfq_df['date'])
start_date = datetime(1991, 4, 3) # 回测开始时间
end_date = datetime(2021, 6, 16) # 回测结束时间
data = bt.feeds.PandasData(dataname=stock_hfq_df, fromdate=start_date, todate=end_date) # 规范化数据格式
cerebro.adddata(data) # 将数据加载至回测系统
cerebro.broker.setcash(start_cash) # broker设置资金
cerebro.broker.setcommission(commission=commission_fee) # broker手续费
cerebro.addsizer(bt.sizers.FixedSize, stake=stake) # 设置买入数量
print("期初总资金: %.2f" % cerebro.broker.getvalue())
cerebro.run(maxcpus=1) # 用单核 CPU 做优化
print("期末总资金: %.2f" % cerebro.broker.getvalue())
if __name__ == '__main__':
main(code="600070", start_cash=1000000, stake=100, commission_fee=0.001)