上次了解了backtrader的基本内容和使用方法。下面就用backtrader框架来解决一个问题:回测一下我的实盘数据。
我从2018年开始股票定投,就两个:300etf和纳指etf。开始每个月一次,后来每隔十天一次。由于时间并不绝对固定,没法直接用算法来描述。我从券商APP里把交易记录人肉输入到一个csv文件里,像这样。
下面就用backtrader来回测一下。先用tushare下载历史数据并保存到文件里。并转换成backtrader可以接受的形式。
1 | # 获取数据 |
下面获取股价数据,并建立数据源。
1 | if __name__ == "__main__": |
下面建立策略类,先写最简单的,直接显示股价。
1 | # 交易策略 |
然后建立cerebro实例,加载数据和策略。
1 | # 建立回测实例,加载数据,策略。 |
最后运行回测。
1 | # 运行回测 |
搞定!
接下来,把我的交易记录读入,根据交易记录进行交易。
折腾了好久,主要是datetime作为DataFrame的索引,如何找出某个日期的数据呢?用datetime的strftime函数输出字符串,再用loc选取数据。具体看下面。
1 | # 交易策略 |
下面就要用这些实盘的数据进行回测交易。
1 | def next(self): |
跑一下看看。
有两个问题:貌似一个bar里执行了两次,另外买入日期是第二天,因此有交易失败的情况。
看了半天,原来是因为我没有判断实盘证券名称与bar数据中的证券名称是否一致的原因。另外查了一下交易的exectype类型:
Order.Market或者None:Market订单将以下一个可行的价格进行交易,在回测中,就将以下一根K线的开盘价进行交易。
Order.Limit:在给定的价位price或者更好的价位执行的订单。
Order.Stop:当价格突破price时,触发订单成交。
Order.StopLimit:当价格突破price时触发订单(类似于Order.Stop订单),之后以给定的价位plimit或者更好的价位执行订单(相当于以参数plimit为price的Order.Limit订单)。
Order.StopTrailLimit:Order.StopTrail和Order.Limit的组合,按照Order.StopTrail条件触发,按照Order.Limit条件成交。
Order.Historical:尚未发现相关说明及应用。
以上参考:https://blog.csdn.net/m0_46603114/article/details/106031259
看不太明白,我挨个试了一下。最后结果还是第一个,即以第二天开盘价成交的回测结果与实际结果最接近,但还是差了几千块。就先用这个结果吧。
1 | def next(self): |
绿色标志是买入点,红色是卖出点,接下来看看画图能不能改进。
默认的cerebro.plot()调用有CashValue,Trade,BuySell三个观察器,分别监控总市值,交易盈亏和买卖点。用stdstats参数来控制,默认为True。
有三种方法可以改变绘图数据:
通过adddata, replaydata和resampledata往cerebro里添加数据。
通过addindicator往strategy里添加指标。
通过addobserver往cerebro里添加观察器。
实操一下,先通过添加观察器增加回撤观察器:
1 | # 添加回撤观察器 |
再到策略类里实现stop函数。输出策略的最大回撤值:
1 | def stop(self): |
输出结果:
画的图里也有回撤值的图像。
下面试试在strategy里添加indicator,要注意的是任何在__init__()里声明的indicator都会在next()被调用之前被计算。
关于绘图,声明Indicator会自动绘图,但通过操作符得到的lines对象不会自动绘图,要绘图可以通过LinePlotterIndicator进行。
直接在策略类的__init__()里声明indicators就行啦。
1 | bt.indicators.AroonDown() |
下面来看看怎么修改图片显示参数。indicators和observers有很多选项可以调整图片显示。有三大类,分别是可以控制整个对象,单独某个lines,和整个系统范围内的图像输出。对于indicators和observers可以设置plotinfo参数,可以在定义是直接指定参数及值,也可以在定义了对象之后,设置对象名.plotinfo.参数名。
两个方法都试一下:
1 | ad = bt.indicators.AroonDown(plotname = "AD") |
当subplot参数为False时,图与前面一个图形画在一起,如画移动均线时跟股价画到一起。
indicators和observers类中也定义了很多以_开头的函数用来控制绘图。
还有控制整个系统绘图的函数,cerebro.plot(),其中numfigs指定分成几张图,默认为1,我改一下看看。
1 | cerebro.plot(numfigs = 2) |
貌似没啥用。
先这样吧。下面看看怎么加入分析器。
用cerebro.addanalyzer函数加入,在run结束后才计算结果,尽管其在内部也是lines。
1 | import backtrader.analyzers as btay |
先算个夏普比例
1 | # 添加分析对象 |
才0.707,嘿嘿。
再加几个试试。
1 | # 添加分析对象 |
这种方式输出很乱,有两种改进方法,一个是直接输出结果字典的某个键值,还有一个方法是调用分析对象的print()成员函数。两种方法都试试:
1 | print("夏普比例:", results[0].analyzers.sharpe.get_analysis()["sharperatio"]) |
结果
1 | 夏普比例: 0.7071076051413587 |
这就清楚多了。
现在的问题是计算α,β值的方法,貌似框架里没有直接的方法。下次弄吧,另外想根据《重构》这本书对代码进行一下重构。
本文代码: https://github.com/zwdnet/MyQuant/tree/master/46
我发文章的三个地方,欢迎大家在朋友圈等地方分享,欢迎点“在看”。
我的个人博客地址:https://zwdnet.github.io
我的知乎文章地址: https://www.zhihu.com/people/zhao-you-min/posts
我的微信个人订阅号:赵瑜敏的口腔医学学习园地