前言:

谈及BackTrader的回测速度优化,最常见的说法是从底层使用numpy等计算库来替换,但这种优化无疑非常新手不友好。因此本文着眼于如何最简单的优化多股情况下回测慢这一情况。考虑测试效率,本文使用100支股票回测。经过测试,优化后策略执行速度提升59%(93->38.4)。

策略描述:

前一天非一字涨停的股票进入候选池。

第二天10~11点若涨幅大于4%买入。

持仓股在14:30时若未涨停卖出。

V1策略及运行时间:

v1代码设计思路:

使用5分数据进行交易,而使用日线数据进行候选池判断及涨幅判断。添加定时器只在每天15:00点筛选候选池,然后在next中根据时间与涨幅判断是否需要买入或卖出。策略部分代码如下:

class MyStrategy(bt.Strategy):params = dict(when=bt.timer.SESSION_START,end=bt.timer.SESSION_END,timer=True,cheat=False,offset=timedelta(),repeat=timedelta(),weekdays=[],period=3,)def log(self, txt, dt=None):''' Logging function fot this strategy'''dt = dt or self.datas[0].datetime.datetime(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):self.order = Noneself.add_timer(when=time(15, 0),offset=self.p.offset,repeat=self.p.repeat,weekdays=self.p.weekdays,)s_m = []for i, d in enumerate(self.datas):if not d._name.endswith('_day'):s_m.append([d._name, i, None])self.st_df = pd.DataFrame(data=s_m, columns=['code', 'min', 'day'])for i, d in enumerate(self.datas):if d._name.endswith('_day'):n = d._name.split('_')[0]self.st_df.loc[self.st_df.code == n, 'day'] = i#         self.stock_names.append(d._name)# self.min_stocks = self.datas[:int(len(self.datas)/2)]# self.day_stocks = self.datas[-int(len(self.datas)/2):]self.zt_list = []self.last_hold = []self.new_hold = []self.zt_num = 0def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashidx = self.st_df.loc[self.st_df.code==order.data._name].index.values[0]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.new_hold.append(idx)self.zt_list.remove(idx)else:  # Sellself.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))self.last_hold.remove(idx)elif order.status in [order.Canceled, order.Expired, order.Margin, order.Rejected]:self.log('Order Canceled/Expired/Margin/Rejected')self.new_hold.remove(idx)# Write down: no pending orderself.order = Nonedef next(self):t = self.datetime.time(0)#1.每天早上10:05至11:05买入len_for_new = 10 - len(self.last_hold) - len(self.new_hold)if len(self.zt_list) > 0 and len_for_new > 0:if t >= time(9,40) and t <= time(14,30):for i in self.zt_list:if i in self.last_hold:continued = self.datas[self.st_df.loc[i, 'min']]if len_for_new <= 0:breaklast_close = self.datas[self.st_df.loc[i, 'day']].close[0]if 1.045 * last_close < d.close[0] < 1.09 * last_close:len_for_new -= 1targetvalue = 0.1 * self.broker.getvalue()size = targetvalue/(last_close*1.09)//100*100self.buy(data=d, size=size, price=last_close*1.09, exectype=bt.Order.Limit,valid=self.datetime.datetime(0)+timedelta(minutes=5))#2.每天14:35卖出if len(self.last_hold) > 0:if t == time(14, 35):for i in self.last_hold:m = self.datas[self.st_df.loc[i, 'min']]d = self.datas[self.st_df.loc[i, 'day']]if m.close[0] < d.high_limit[0]: #14:30时 day bar最新是昨天的print('sell 平仓', m._name, self.getposition(m).size)self.close(data=m)def notify_timer(self, timer, when, *args, **kwargs):# 2.合并买入卖出结果self.last_hold += self.new_holdself.new_hold = []# 1.根据涨停预选股票池self.zt_list = []for i, row in self.st_df.iterrows():d = self.datas[row['day']]if d.close[0] > d.low[0] and d.pctChg[0] > 9.9:self.log('zhangting ' + str(d.close[0]) + d._name)self.zt_list.append(i)# 3.删除已买入self.zt_list = list(set(self.zt_list)-set(self.last_hold))self.zt_num += len(self.zt_list)#print('平均涨停数', self.zt_num/len(self.data0))

运行时间:

总时间:72秒

读取csv cerebro.adddata 执行完成
5.8 4 62

可以看到耗时主要集中在cerebro添加数据完成到执行完成,[3]中所提及的优化数据读取的方式便不适用。而根据[2]中提出,Observers和Analyzers耗时能达到执行的一半,去掉以后重新运行得到总时间:71秒,没有明显提升,可能是本例中添加的Observers和Analyzers都比较简单。

V2策略及运行时间:

v2代码改进思路:

为了提高运行效率,考虑尽量减少next中的判断,将其放到cerebro之外,同时将信号直接附加到5min数据上,不再传入日数据。代码如下:

class PandasDataExtendInd(bt.feeds.PandasData):# 增加线lines = ('ind','high_limit','buy_ind', 'sell_ind',)params = (('ind', -1),('high_limit', -1),('buy_ind', -1),('sell_ind', -1), )  # 机构持股数量合计class MyStrategy(bt.Strategy):params = dict(when=bt.timer.SESSION_START,end=bt.timer.SESSION_END,timer=True,cheat=False,offset=timedelta(),repeat=timedelta(),weekdays=[],period=3,)def log(self, txt, dt=None):''' Logging function fot this strategy'''dt = dt or self.datas[0].datetime.datetime(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):self.order = Noneself.add_timer(when=time(15, 0),offset=self.p.offset,repeat=self.p.repeat,weekdays=self.p.weekdays,)self.zt_list = []self.last_hold = []self.new_hold = []self.zt_num = 0def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cash#idx = self.st_df.loc[self.st_df.code==order.data._name].index.values[0]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.new_hold.append(order.data)self.zt_list.remove(self.datas.index(order.data))else:  # Sellself.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))self.last_hold.remove(order.data)elif order.status in [order.Canceled, order.Expired, order.Margin, order.Rejected]:self.log('Order Canceled/Expired/Margin/Rejected')self.new_hold.remove(order.data)# Write down: no pending orderself.order = Nonedef next(self):t = self.datetime.time(0)#1.每天早上10:05至11:05买入len_for_new = 10 - len(self.last_hold) - len(self.new_hold)if len(self.zt_list) > 0 and len_for_new > 0:if t >= time(9,40) and t <= time(14,30):for i in self.zt_list:if i in self.last_hold:continued = self.datas[i]if len_for_new <= 0:breakif d.buy_ind:len_for_new -= 1targetvalue = 0.1 * self.broker.getvalue()size = targetvalue/(d.high_limit*0.99)//100*100self.buy(data=d, size=size, price=d.high_limit*0.99, exectype=bt.Order.Limit,valid=self.datetime.datetime(0)+timedelta(minutes=5))#2.每天14:35卖出if len(self.last_hold) > 0:if t == time(14, 35):for m in self.last_hold:if m.sell_ind: #14:30时 day bar最新是昨天的print('sell 平仓', m._name, self.getposition(m).size)self.close(data=m)def notify_timer(self, timer, when, *args, **kwargs):# 2.合并买入卖出结果self.last_hold += self.new_holdself.new_hold = []# 1.根据涨停预选股票池self.zt_list = []for i, d in enumerate(self.datas):if d.ind[0]:self.zt_list.append(i)# 3.删除已买入self.zt_list = list(set(self.zt_list)-set(self.last_hold))self.zt_num += len(self.zt_list)#print('平均涨停数', self.zt_num/len(self.data0))

运行时间:

总时间:103秒

读取csv cerebro.adddata 执行完成
5.8 4.5 93

反向优化效果显著,也就是next中的比较操作+少传入日数据的效果远远小于传入了复杂的5分钟数据。详细打印运行时间,可以看到next第一次开始时为80秒,中间接近70秒的时间是cerebro进行各种初始化。

V3最终优化

优化思路:

详细分析代码后可以得到其中最耗时的部分为:

# cerebro.py -> runstrategies()
for data in self.datas:data.preload()# feed.py -> preload()
def preload(self):while self.load():passself._last()self.home()

preload本身不好优化,但是对于runstrategies可以采用多线程执行进行优化,采用cerebro本身使用的Multiprocessing.Pool完成。

运行时间:

总时间:49秒

读取csv cerebro.adddata 执行完成
5.9 4.3 38.4

数据读取、载入耗时不变,执行速度大幅提升。

结论

利用多线程可以大幅提升策略回测速度,同时修改难度较低。

电脑参数:

i7-10510U 2.30GHz, 4核8线程

15G内存

win10

参考:

[1] https://zhuanlan.zhihu.com/p/345815425

[2] https://www.zhihu.com/question/440467223

[3] https://community.backtrader.com/topic/2263/which-line-code-function-consume-more-time-when-doing-a-backtest/13

BackTrader:性能优化之多股策略速度优化相关推荐

  1. Hexo速度优化及遇到的问题(gulp4、hexo-neat)

    0x00 前言 这几天闲来无事,学习了下Hexo的博客速度优化,发现有好多方法.CDN加速.Coding部署.gulp压缩.hexo-neat.InstantClick 黑科技等等.这里记录下我使用g ...

  2. SEM营销推广策略和优化,做好其实并不难!

    当今社会,移动互联网和各种超级app兴起,导致营销形式一直在变化,营销手段多种多样,但是不得不承认的是SEM仍然是很多中小企业最信赖的推广方式.但是竞价推广的成本却随着其发展变得越来越高,而转化成交也 ...

  3. 【股票策略】使用backtrader测试狗股策略版本4---在版本3的基础上进行代码改进优化

    在上几次测试狗股策略的时候,忽略了我们加载的第一个数据可能有停牌的可能性,这次考虑所有股票的交易时间之后再进行测试,可以使用指数的数据,或者如果是全市场的股票数据,把所有股票的时间加载到一块,也可以充 ...

  4. 12、【股票策略】使用backtrader回测升级版的狗股策略-基于股息率和市净率两个因子

    12.使用backtrader回测升级版的狗股策略-基于股息率和市净率两个因子 更进一步的回测代码可以参考版本4:[股票策略]使用backtrader测试狗股策略版本4-在版本3的基础上进行代码改进优 ...

  5. 11、【股票策略】用backtrader回测在A股上复利年化收益率超20%的“狗股策略”?

    11.用backtrader回测在A股上复利年化收益率超20%的"狗股策略"? 更进一步的回测代码可以参考版本4:[股票策略]使用backtrader测试狗股策略版本4-在版本3的 ...

  6. WordPress速度优化和性能提升的终极指南

    点击这里阅读WordPress速度优化和性能提升的终极指南原文 您想加快WordPress网站的速度吗?快速加载页面可改善用户体验,增加页面浏览量,并为您的WordPress SEO提供帮助.在本文中 ...

  7. vue第一次加载慢怎么优化_vue如何优化首屏加载速度?面试过程遇到的性能优化问题...

    问题:vue如何优化首屏加载速度? 问题描述: 在Vue项目中,引入到工程中的所有js.css文件,编译时都会被打包进vendor.js,浏览器在加载该文件之后才能开始显示首屏.若是引入的库众多,那么 ...

  8. Android开发——H5容器加载速度优化方案

    1. 背景介绍 在偏重活动运营的电商App中,受制于App版本审核,具备开发周期短.可灵活发布等特点的H5页面受到青睐,承载了很多重要业务.但App Webview存在令人烦恼的性能问题,特别突出的是 ...

  9. MySQL02--高级(BTreeB+Tree、聚簇索引非聚簇索引、性能分析(Explain)、索引、sql优化)

    1.MySQL架构 2.sql 执行顺序: FROM <LEFT_TABLE> ON <JOIN_CONDITION> <JOIN_TYPR> JOIN <R ...

最新文章

  1. windows下编译jsoncpp 1.y.z
  2. Java设计模式-观察者模式(订阅发布模式)
  3. OAuth的MVC实现(微软)
  4. Mask R-CNN详解
  5. 潘多拉设置有线中继_避坑指南:购买无线中继器必看
  6. matlab信号分割与比对,matlab测量计算信号的相似度
  7. 显示日期的指令: date
  8. 利用预渲染解决优化性能问题IOS
  9. 关于.Net Application Server对象访问方式的设计(2.上)
  10. 学习笔记-数据结构与算法之线性表
  11. 【SQL学习】select语句使用实例
  12. 2021年6月23日,我们毕业啦!!!
  13. 理想RISO ORPHIS EX7250 打印机驱动
  14. MATLAB 中有哪些命令,让人相见恨晚?
  15. Nginx的 MIME TYPE问题导致的mjs文件加载出错的问题解决
  16. linux安装源文件出现错误,编译安装源码时出现错误的解决方法
  17. 巴西电商Olist数据分析项目:SQL+FineBI
  18. 现代交换技术中,分组交换和电路交换的区别
  19. 四川SFSCB-A15/4PY智能浪涌后备保护器 遥信功能 上传防雷系统平台
  20. 短信服务之阿里云平台

热门文章

  1. 一文极速读懂UniProt数据库
  2. mysql源码分析——VIO数据结构
  3. sleep()和wait()方法有什么区别
  4. 【Flask教程】Flask开发基础与入门
  5. 算法详解 - 神奇的兔子数列
  6. STM32H7双核培训
  7. 直线回归和相关------(一)回归和相关的概念与直线回归(含最小二乘推导)
  8. 如何给linux设置固定ip地址,设置Linux系统的固定IP地址
  9. python opencv 视频人脸识别
  10. 脱不花.怎样成为高效学习的人