1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > Backtrader 策略回测初探

Backtrader 策略回测初探

时间:2020-09-14 02:17:47

相关推荐

Backtrader 策略回测初探

Backtrader 策略回测初探

这篇介绍简单的回测流程,主要的内容如下:

回测函数介绍

单股回测

多股回测

回测函数

回测策略类很简洁,直接继承 bt.Strategy ,复写父类的方法,最后把回测策略类添加到大脑即可。

回测参数回测类参数添加通过属性变量 params 记录,可以是元组形式,也可以是字典形式。

定义参数

#元组形式,注意最后一个,逗号别删除

params=(

('maperiod',20),

)

#字典形式

params={'maperiod':20}

使用

通过 self.p.maperiod 访问提取。

bt.ind.SMA(self.data,period=self.p.maperiod)

传参

将策略类传入大脑时,传入参数

cerebro.addstrategy(TestStrategy,maperiod=5)

函数

因为继承了策略类 bt.Strategy ,运行策略时会回调这些函数,需要搞的事情是在相应的函数调用买卖逻辑即可。

先说下 lines 线对象概念,每个指标都是一条 line 线对象,贯穿所有的回测日期。bar 概念是每个日期对应所有的指标。简单理解就是 excel 表的中的列和行,line 线对象相当于 excel 的列,bar 概念相当于 excel 表的行。

还有其他一些函数,这里不一一介绍了,不是很重要的。

指标简介

指标在代码层面上的表示形式就是以 line 线对象的方式存在,贯穿整个回测周期,一般在测类类的__init__函数里面构建好

在数据篇,有展示过在 feeds.Data 上直接扩展指标,下面介绍的是通过 backtrader 的指标对象来构建新的指标,新的指标也是 line 对象,贯穿整个回测周期。

下面例子构建 20 日移动平均线 SMA:

def__init__(self):

self.sma=bt.ind.SMA(self.data,period=20)

注意:这种形式构建的指标是基于数据篇里面所说的第一个表数据的指标,并不是针对所有表的。而且会影响到 next 回调时机。20 日移动平均线的指标,前 20 日个回测 bar 是无效的,不会在 next() 函数回调,但回调到 prenext() 函数。

下图所示:

关于指标在这里不细说,后面会写一篇详细点的介绍。

回测简易配置

只是做一个简单回测测试,所以简易配置下经纪商,setcash() 配置了一小目标资产 1亿 ,设置佣金 setcommission() 千分之一,addstrategy() 添加策略并设置自定义的参数。

#设置资产

cerebro.broker.setcash(100000000.0)

#设置佣金

cerebro.broker.setcommission(commission=0.001)

#添加回测策略,设置自定义参数数值

cerebro.addstrategy(SingleTestStrategy,maperiod=20)

#执行策略

cerebro.run()

#画图

cerebro.plot()

这里不细说这配置了,后面再详细说一篇。

单股回测

下面进入主题,搞一个策略,回测下能不能赚钱,这里只是介绍使用方法,实际应用肯定不能只靠一个指标。

策略这策略很简单,当当日收盘价高于 20 日平均线时买买买!当当日收盘价低于 20 日移动平均线时卖卖卖。

注意:

该策略是以收盘价下单,以下单后的下一日开盘价作为交割价。

最后一日不做任何的买卖。所以在回测的最后一日的前一天必须下单卖出股票,以便在最后一根 bar 的开盘价做为交割价卖出,不然出现未来函数。

买卖常用函数:

self.order_target_percent(secu_data, target_pct, name=secu)

self.order_target_value(secu_data, target_val, name=secu)

self.buy(secu_data, order_amount, name=secu)

self.sell(secu_data, order_amount, name=secu)

代码胜过千言万语,直接上完整代码:

importdatetime

importbacktraderasbt

importpandasaspd

importstock_dbassdb

classSingleTestStrategy(bt.Strategy):

params=(

('maperiod',20),

)

def__init__(self):

self.order=None

self.sma=bt.ind.SMA(self.data,period=self.p.maperiod)

pass

defdowncast(self,amount,lot):

returnabs(amount//lot*lot)

#可以不要,但如果你数据未对齐,需要在这里检验

defprenext(self):

print('prenext执行',self.datetime.date(),self.getdatabyname('300015')._name

,self.getdatabyname('300015').close[0])

pass

defnext(self):

#检查是否有指令执行,如果有则不执行这bar

ifself.order:

return

#回测如果是最后一天,则不进行买卖

ifpd.Timestamp(self.data.datetime.date(0))==end_date:

return

ifnotself.position:#没有持仓

#执行买入条件判断:收盘价格上涨突破20日均线;

#不要在股票剔除日前一天进行买入

ifself.datas[0].close>self.smaandpd.Timestamp(self.data.datetime.date(1))<end_date:

#永远不要满仓买入某只股票

order_value=self.broker.getvalue()*0.98

order_amount=self.downcast(order_value/self.datas[0].close[0],100)

self.order=self.buy(self.datas[0],order_amount,name=self.datas[0]._name)

else:

#执行卖出条件判断:收盘价格跌破20日均线,或者股票剔除

ifself.datas[0].close<self.smaorpd.Timestamp(self.data.datetime.date(1))>=end_date:

#执行卖出

self.order=self.order_target_percent(self.datas[0],0,name=self.datas[0]._name)

self.log(f'卖{self.datas[0]._name},price:{self.datas[0].close[0]:.2f},pct:0')

pass

defnotify_order(self,order):

iforder.statusin[order.Submitted,order.Accepted]:

#Buy/Sellordersubmitted/acceptedto/bybroker-Nothingtodo

return

#Checkifanorderhasbeencompleted

#Attention:brokercouldrejectorderifnotenoughcash

iforder.statusin[pleted,order.Canceled,order.Margin]:

iforder.isbuy():

self.log(

f"买入{order.info['name']},成交量{order.executed.size},成交价{order.executed.price:.2f}订单状态:{order.status}")

self.log('买入后当前资产:%.2f元'%self.broker.getvalue())

eliforder.issell():

self.log(

f"卖出{order.info['name']},成交量{order.executed.size},成交价{order.executed.price:.2f}订单状态:{order.status}")

self.log('卖出后当前资产:%.2f元'%self.broker.getvalue())

self.bar_executed=len(self)

#Writedown:nopendingorder

self.order=None

deflog(self,txt,dt=None):

"""

输出日期

:paramtxt:

:paramdt:

:return:

"""

dt=dtorself.datetime.date(0)#现在的日期

print('%s,%s'%(dt.isoformat(),txt))

pass

defnotify_trade(self,trade):

'''可选,打印交易信息'''

pass

#开始查询时间

start_query='-01-01'

end_query='-09-01'

#开始回测时间

from_date=datetime.datetime(,1,1)

to_date=datetime.datetime(,10,10)

cerebro=bt.Cerebro()

#添加几个股票数据

codes=[

'300015',

#'300347',

#'300760',

#'603127',

#'600438'

]

#添加多个股票回测数据

end_date=0

forcodeincodes:

data=sdb.stock_daily(code,start_query,end_query)

data.index.names=['datetime']

data_feed=bt.feeds.PandasData(dataname=data,

fromdate=from_date,

todate=to_date)

cerebro.adddata(data_feed,name=code)

end_date=data.index[-1]#股票剔除日

print('添加股票数据:code:%s'%code)

cerebro.broker.setcash(100000000.0)

cerebro.broker.setcommission(commission=0.001)

cerebro.addstrategy(SingleTestStrategy,maperiod=20)

cerebro.run()

cerebro.plot()

if__name__=='__main__':

pass

结果:

em em ... 一个亿的资产,亏了接近 1000w, 策略失败!!!!

来看看回测图,backtrader 的回测图的确有点丑哈,后面会有重构可视化篇的。

最上面是资产分析图,行情数据区域的绿色三角形是买入,红色三角形是卖出。

多股回测

单股回测,上面已经介绍了,那如何多个股同时回测呢?在这个问题上,我们首先要解决的是多个股的指标计算并存储起来。

策略逻辑和上面的相同。 计算均线的时候用了dict循环计算每只股票的指标。

self.getdatanames()按顺序返回所有股票的名称list

self.getdatabyname(secu_name):返回该股票的data

所以,在给大脑塞数据时,需要指定 feedData 的 name ,统一用股票代码赋值,这样方便后面的索引。

直接上图说下整个流程逻辑:

代码只是再单股回测的基础下添加多股指标和多股持仓买卖判断,策略和单股相同。

代码如下:

importdatetime

importbacktraderasbt

importpandasaspd

importstock_dbassdb

classMultiTestStrategy(bt.Strategy):

params=(

('maperiod',20),

)

defprenext(self):

pass

defdowncast(self,amount,lot):

returnabs(amount//lot*lot)

def__init__(self):

#初始化交易指令

self.order=None

self.buy_list=[]

#添加移动平均线指标,循环计算每个股票的指标

self.sma={x:bt.ind.SMA(self.getdatabyname(x),period=self.p.maperiod)forxinself.getdatanames()}

defnext(self):

ifself.order:#检查是否有指令等待执行

return

#如果是最后一天,不进行买卖

ifpd.Timestamp(self.datas[0].datetime.date(0))==end_dates[self.datas[0]._name]:

return

#是否持仓

iflen(self.buy_list)<2:#没有持仓

#没有购买的票

forsecuinset(self.getdatanames())-set(self.buy_list):

data=self.getdatabyname(secu)

#如果突破20日均线买买买,不要在最后一根bar的前一天买

ifdata.close>self.sma[secu]andpd.Timestamp(data.datetime.date(1))<end_dates[secu]:

#买买买

order_value=self.broker.getvalue()*0.48

order_amount=self.downcast(order_value/data.close[0],100)

self.order=self.buy(data,size=order_amount,name=secu)

self.log(f"买{secu},price:{data.close[0]:.2f},amout:{order_amount}")

self.buy_list.append(secu)

elifself.position:

now_lst=[]

forsecuinself.buy_list:

data=self.getdatabyname(secu)

#执行卖出条件判断:收盘价格跌破20日均线,或者股票最后一根bar的前一天之剔除日

ifdata.close[0]<self.sma[secu]orpd.Timestamp(data.datetime.date(1))>=end_dates[secu]:

#卖卖卖

self.order=self.order_target_percent(data,0,name=secu)

self.log(f"卖{secu},price:{data.close[0]:.2f},pct:0")

continue

now_lst.append(secu)

self.buy_list=now_lst

defnotify_order(self,order):

iforder.statusin[order.Submitted,order.Accepted]:

#Buy/Sellordersubmitted/acceptedto/bybroker-Nothingtodo

return

#Checkifanorderhasbeencompleted

#Attention:brokercouldrejectorderifnotenoughcash

iforder.statusin[pleted,order.Canceled,order.Margin]:

iforder.isbuy():

self.log(f"""买入{order.info['name']},成交量{order.executed.size},成交价{order.executed.price:.2f}""")

self.log(

f'资产:{self.broker.getvalue():.2f}持仓:{[(x,self.getpositionbyname(x).size)forxinself.buy_list]}')

eliforder.issell():

self.log(f"""卖出{order.info['name']},成交量{order.executed.size},成交价{order.executed.price:.2f}""")

self.log(

f'资产:{self.broker.getvalue():.2f}持仓:{[(x,self.getpositionbyname(x).size)forxinself.buy_list]}')

self.bar_executed=len(self)

#Writedown:nopendingorder

self.order=None

deflog(self,txt,dt=None):

"""

输出日期

:paramtxt:

:paramdt:

:return:

"""

dt=dtorself.datetime.date(0)#现在的日期

print('%s,%s'%(dt.isoformat(),txt))

#开始查询时间

start_query='-01-01'

end_query='-09-01'

#开始回测时间

from_date=datetime.datetime(,1,1)

to_date=datetime.datetime(,10,10)

cerebro=bt.Cerebro()

#添加几个股票数据

codes=[

'300015',

'300347',

#'300760',

#'603127',

#'600438'

]

#添加多个股票回测数据

end_dates={}

end_date=0

forcodeincodes:

data=sdb.stock_daily(code,start_query,end_query)

data.index.names=['datetime']

data_feed=bt.feeds.PandasData(dataname=data,

fromdate=from_date,

todate=to_date)

cerebro.adddata(data_feed,name=code)

end_dates[code]=data.index[-1]#股票剔除日

end_date=data.index[-1]#股票剔除日

print('添加股票数据:code:%s'%code)

cerebro.broker.setcash(100000000.0)

cerebro.broker.setcommission(commission=0.001)

cerebro.addstrategy(MultiTestStrategy,maperiod=20)

cerebro.run()

#获取回测结束后的总资金

portvalue=cerebro.broker.getvalue()

#打印结果

print(f'结束资金:{round(portvalue,2)}')

cerebro.plot()

if__name__=='__main__':

pass

看下日志:

enen ,居然赚钱了,小赚接近 1000w.

看下 backtracder 的买卖点:

后面日期好像没触发买卖逻辑,这后面再看看是咋回事。

写于 年 10 月 23 日 10:27:29

本文由 mdnice 多平台发布

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。