背景:需要扩展data feeds的场景

在backtrader中,data feeds中包含了被普遍认为是业界标准的几个字段:

  • datetime
  • open
  • high
  • low
  • close
  • volume
  • openinterest

可以使用GenericCSVData读取CSV文件,来方便地加载这些数据。但是在很多情况下,还需要在回测框架中使用其他的数据,例如:

  1. 利用分类算法,预测出股票是否已经达到买点及卖点(转化为分类问题),在backtrader中按照预测的买点及卖点进行交易,就需要将预测结果读入到框架中进行回测。
  2. 利用序列预测算法,预测出模型每日的收盘价,将预测值与其他技术指标综合分析,制定交易策略,然后进行回测,这也需要将预测得到的序列值读入到框架中。

因此,我们要想办法从CSV中读入自定义的数据,来使得这些数据可以在策略中得以应用。本文就针对上面提到的第2个场景,来扩展data feeds,实现自定义数据在backtrader回测框架中的使用。

应用场景设定

我们假定这样的应用场景:

  1. 已知某只股票的历史日线数据,包含datetime,open,high,low,close字段;
  2. 已经通过序列预测算法,利用历史数据计算出每日收盘价(或开盘价)的预测值;
  3. 利用历史日线数据及预测数据对该股票进行策略回测;
  4. 策略为:当收盘价小于前1日的预测值时,进行买入;当收盘价大于前1日的预测值时,进行卖出。

实现步骤

  1. 生成待读入CSV文件,文件中包含datetime,open,high,low,close及自定义数据字段(这里使用predict进行标识)。由于这里只是做示意,因此简单的使用predict = (high + low) / 2来计算predict的值。合并后CSV文件截图如下:
  2. 创建GenericCSVData的子类,扩展新的lines对象,添加新的参数,这样在使用这个子类时,调用者就可以通过这个参数,来指定自定义的数据在CSV文件中的哪一列,代码如下:
# 扩展DataFeed
class GenericCSVDataEx(GenericCSVData):# 添加自定义linelines = ('predict', )# openinterest在GenericCSVData中的默认索引是7,这里对自定义的line的索引加1,用户可指定params = (('predict', 8),)

这里创建了一个GenericCSVData的子类GenericCSVDataEx,定义了一个新的lines对象predict,并且添加了一个新的参数predict,默认值设置为8,在后续调用时,根据自定义数据具体在文件中的哪一列,再对这个参数进行重新赋值。

  1. 使用创建的子类导入数据,代码如下:
# 创建数据
data = GenericCSVDataEx(dataname = datapath,fromdate = datetime.datetime(2019, 1, 1),todate = datetime.datetime(2019, 12, 31),nullvalue = 0.0,dtformat = ('%Y/%m/%d'),datetime = 0,open = 1,high = 2,low = 3,close = 4,volume = -1,openinterest = -1,predict = 5 )

这里使用了新创建的类GenericCSVDataEx来导入数据。在参数中,从datetime开始,等号后面的值均表示对应字段在CSV文件中的列号,-1表示文件没有该字段数据,可以回看前面的CSV文件的截图,确认一下数据的对应关系。

  1. 在Strategy中使用自定义字段数据,主要代码如下:
class TestStrategy(bt.Strategy):params = (# 要跳过的K线根数('skip_len', 1),)def __init__(self):# 引用data[0]数据的收盘价数据self.dataclose = self.datas[0].closeself.datapredict = self.datas[0].predict# 用于绘制predict曲线btind.SMA(self.data.predict, period = 1, subplot = False)# 用于记录订单状态self.order = Noneself.buyprice = Noneself.buycomm = Nonedef next(self):# 因为需要在策略中需要和前一日的预测值比较,所以要跳过第1根K线if (len(self) <= self.p.skip_len):return# 检查是否有订单等待处理,如果是就不再进行其他下单if self.order:return# 检查是否已经进场if not self.position:# 还未进场,则只能进行买入# 当日收盘价小于前一日预测价if self.dataclose[0] < self.datapredict[-1]:# 买买买# 记录订单避免二次下单self.order = self.buy()# 如果已经在场内,则可以进行卖出操作else:# 当日收盘价大于前一日预测价if self.dataclose[0] > self.datapredict[-1]:# 卖卖卖# 记录订单避免二次下单self.order = self.sell()

在init函数中,直接使用self.datas[0].predict就可以访问到我们自定义的predict字段的值,然后将它保存在实例变量self.datapredict中,这样就可以在next函数中进行使用了。

     self.datapredict = self.datas[0].predict

在next函数中实现策略:当收盘价小于前1日的预测值时,进行买入;当收盘价大于前1日的预测值时,进行卖出。

        if not self.position:if self.dataclose[0] < self.datapredict[-1]:self.order = self.buy()else:if self.dataclose[0] > self.datapredict[-1]:self.order = self.sell()

需要说明以下几点:

  • 这里的self.datapredict(self.datas[0].predict)是lines对象,在next函数中使用时,索引[0]表示当日的数据,索引[-1]表示前1日的数据
  • 由于要和前1日predict的值做比较,而对于第1个交易日而言,是没有前1日predict值的,因此在next函数中,使用下面的代码跳过1根K线
        # 因为需要在策略中需要和前一日的预测值比较,所以要跳过第1根K线if (len(self) <= self.p.skip_len):return
  • backtrader没有提供自定义的数据绘制功能,可以在init函数中,通过借用单日的简单移动平均线来绘制自定义数据的曲线
        # 用于绘制predict曲线btind.SMA(self.data.predict, period = 1, subplot = False)

回测结果如下图所示:

Data Feeds扩展代码:

from __future__ import (absolute_import, division, print_function,unicode_literals)
import datetime  # 用于datetime对象操作
import os.path  # 用于管理路径
import sys  # 用于在argvTo[0]中找到脚本名称
import backtrader as bt # 引入backtrader框架
from backtrader.feeds import GenericCSVData # 用于扩展DataFeed
import backtrader.indicators as btind# 扩展DataFeed
class GenericCSVDataEx(GenericCSVData):# 添加自定义linelines = ('predict', )# openinterest在GenericCSVData中的默认索引是7,这里对自定义的line的索引加1,用户可指定params = (('predict', 8),)# 创建策略
class TestStrategy(bt.Strategy):params = (# 要跳过的K线根数('skip_len', 1),)def log(self, txt, dt=None):''' 策略的日志函数'''dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# 引用data[0]数据的收盘价数据self.dataclose = self.datas[0].closeself.datapredict = self.datas[0].predict# 用于绘制predict曲线btind.SMA(self.data.predict, period = 1, subplot = False)# 用于记录订单状态self.order = Noneself.buyprice = Noneself.buycomm = Nonedef notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# 提交给代理或者由代理接收的买/卖订单 - 不做操作return# 检查订单是否执行完毕# 注意:如果没有足够资金,代理会拒绝订单if order.status in [order.Completed]:if order.isbuy():self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))self.buyprice = order.executed.priceself.buycomm = order.executed.commelse: # 卖self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(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('Order Canceled/Margin/Rejected')# 无等待处理订单self.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# 因为需要在策略中需要和前一日的预测值比较,所以要跳过第1根K线if (len(self) <= self.p.skip_len):return# 日志输出收盘价数据self.log('Close, %.2f' % self.dataclose[0])# 检查是否有订单等待处理,如果是就不再进行其他下单if self.order:return# 检查是否已经进场if not self.position:# 还未进场,则只能进行买入# 当日收盘价小于前一日预测价if self.dataclose[0] < self.datapredict[-1]:# 买买买# 记录订单避免二次下单self.log('BUY CREATE, %.2f' % self.dataclose[0])self.order = self.buy()# 如果已经在场内,则可以进行卖出操作else:# 当日收盘价大于前一日预测价if self.dataclose[0] > self.datapredict[-1]:# 卖卖卖# 记录订单避免二次下单self.log('SELL CREATE, %.2f' % self.dataclose[0])self.order = self.sell()
# 创建cerebro实体
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(TestStrategy)
# 先找到脚本的位置,然后根据脚本与数据的相对路径关系找到数据位置
# 这样脚本从任意地方被调用,都可以正确地访问到数据
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, './custom.csv')
# 创建数据
data = GenericCSVDataEx(dataname = datapath,fromdate = datetime.datetime(2019, 1, 1),todate = datetime.datetime(2019, 12, 31),nullvalue = 0.0,dtformat = ('%Y/%m/%d'),datetime = 0,open = 1,high = 2,low = 3,close = 4,volume = -1,openinterest = -1,predict = 5 )
# 在Cerebro中添加价格数据
cerebro.adddata(data)
# 设置启动资金
cerebro.broker.setcash(100000.0)
# 设置交易单位大小
cerebro.addsizer(bt.sizers.FixedSize, stake = 100)
# 设置佣金为千分之一
cerebro.broker.setcommission(commission=0.001)
# 打印开始信息
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# 遍历所有数据
cerebro.run()
# 打印最后结果
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.plot()

为了便于相互交流学习,新建了微信群,感兴趣的读者请加微信。

Python量化交易学习笔记(25)——Data Feeds扩展相关推荐

  1. Python量化交易学习笔记(1)

    Python量化交易学习笔记(1) http://zwpython.com/ http://www.topquant.vip/?p=2275 [更多参见] <zwPython,目前最好的py开发 ...

  2. Python量化交易学习笔记(18)——放量突破布林线中轨买入策略

    本文将探索新的策略回测程序,主要是为了尝试不同的技术指标在backtrader平台上的应用,为后续复杂策略的实现做准备. 本文将实现的策略是,当股票放量突破布林线中轨时进行买入,当股票收盘价低于短期均 ...

  3. Python量化交易学习笔记(33)——backtrader仓位管理

    本文将对backtrader的仓位管理进行介绍,具体以同时回测交易3只股票为例,查看每日仓位情况. 策略 买入条件:5日线金叉60日线 卖出条件:5日线死叉60日线 示例 仓位信息输出的核心代码位于策 ...

  4. Python量化交易学习笔记(36)——backtrader多股回测避坑3

    本文继续记录多股回测时可能遇到的异常情况. 坑描述 多股回测时,当日期达到所有股票的技术指标都能够计算出有效值后,backtrader才开始进行回测.由于这种逻辑的存在,如果某些股票在回测周期的最后几 ...

  5. Python量化交易学习笔记(19)——连续下跌买入止盈止损卖出策略

    好友提出要验证连续下跌买入止盈止损卖出策略,本文对该策略回测和实现做分析记录. 买入条件中,连续下跌定义为收盘价连续4日低于前1日的收盘价.卖出条件中,止盈率设置为10%,止损率设置为5%.回测初始资 ...

  6. Python量化交易学习笔记(20)——保护点卖出策略

    本文主要记录保护点卖出策略,给买入的股票设立保护点,随着股票收盘价的提升,保护点不断提高,股价一旦跌破保护点,即卖出股票. 示例的买入条件为,5日线金叉60日线,且股价进行小幅回踩(较金叉日收盘价下跌 ...

  7. Python量化交易学习笔记(21)——A股股票列表更新

    在zwPython2020中,股票数据下载更新时,所读取的股票列表文件的目录位置为"zwPython\TQDat\TQDown2020v1\data\tq_wrk_code.csv" ...

  8. Python量化交易学习笔记(46)——通达信日线数据获取

    序 从2020年初开始接触量化,马上就要满一年了.在这一年里,想过去做量化,想过去做机器学习,想过去做少儿编程教育.就这样大概折腾了小半年时间,最后在CSDN上看到这样一句话:"你把时间投在 ...

  9. Python量化交易学习笔记(2)——A股数据下载

    由于笔者只接触过A股交易,因此本系列笔记仅针对A股交易展开.笔者的计算机为Windows 10 64位操作系统. TOP极宽量化总群(124134140)群文件中提供了大量的学习资料,一眼看去有点懵圈 ...

最新文章

  1. 「图解」ThreadLocal 在并发问题中的应用
  2. 2019牛客暑期多校训练营(第五场)- generator 1
  3. JS - 按钮倒计时
  4. 前端遍历列表生成表格_图书作者的演练-创建列表页和添加表单框-flask
  5. 什么是嵌入式视觉、行业应用、目前挑战
  6. php单引号和双引号的速度,在php中单引号和双引号是否有性能优势?[复制]
  7. 大数据专业认知实习作业
  8. SVN工具介绍- VisualSVN Server与TortoiseSVN
  9. 对于moxie的quot;WebWork教程quot;补充 - [文件上传]
  10. Python 实现自动刷抖音,解放双手了
  11. 2023年,我觉得拼夕夕值得去
  12. PersistenceContext.properties()
  13. JSP一个抽奖小游戏
  14. SSM+老年人社区服务平台 毕业设计-附源码211711
  15. (React-Native 学习之十 ) React-navigation 的使用:
  16. 内存不能为read或written最彻底的解决方法
  17. vue项目实战之Layout
  18. Forter宣布推出Smart Payments解决方案 帮助企业提升数字商务转化率和营收
  19. windows 使用bat脚本文件,复制文件夹到另一个盘
  20. 马云所预测的电子商务时代能否实现?

热门文章

  1. gepc 骨架图算法Graph Embedded Pose Clustering
  2. [离散数学]命题逻辑P_3:命题符号化及其应用
  3. 翻译:VESA Adaptive-Sync / AMD FreeSync / VRR 白皮书
  4. 【Python学习笔记二】函数七十二变
  5. Cisco Secure Firewall 3100 Series, Firepower Threat Defense (FTD) Software 7.2.0 ASA Software 9.18
  6. Wings-企业级单元测试自动编码引擎白皮书
  7. 图(Graph)的定义
  8. 【python3 高级编程】.一文搞懂多线程
  9. 用 Python 写了一个窃取摄像头照片的软件
  10. 欧姆龙plc多轴伺服控制程序fb 欧姆龙PLC CP1H 5轴+1转盘电机4工位程序