失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 如何用backtrader对股票组合进行量化回测?

如何用backtrader对股票组合进行量化回测?

时间:2022-08-10 02:22:27

相关推荐

如何用backtrader对股票组合进行量化回测?

01 引言

backtrader是功能非常强大的量化回测框架之一,得到欧洲很多银行、基金等金融机构的青睐,并应用于实盘交易中。公众号Python金融量化针对backtrader的入门和应用已连续发布了四篇推文:《【手把手教你】入门量化回测最强神器backtrader(一)》、《【手把手教你】入门量化回测最强神器backtrader(二)》、《【手把手教你】入门量化回测最强神器backtrader(三)》和《backtrader如何加载股票因子数据?以换手率、市盈率为例进行回测【附Python代码】》,分别介绍了backtrader整个框架的组成部分、回测系统的运行,策略模块交易日志的编写、策略参数的寻优,Analyzers模块的分析、策略业绩评价指标可视化分析以及扩张数据加载模块对市盈率因子进行回测等。上述文章有一个共同点是回测实例均以个股为交易标的,那么如何对股票组合进行回测呢?本文将重点介绍如何加载多只股票数据,并构建交易组合进行量化回测。

02 股票组合回测实例

数据获取

第一步是数据获取和加载,A股数据个人一般使用tushare来获取,由于对多只股频繁获取容易出现接口报错,因此个人在本地搭建了一个股票数据库(关于数据库搭建请参照:《【手把手教你】Python面向对象编程入门及股票数据管理应用实例》)。注意,下面导入的update_sql和base是自己写的本地脚本,使用自己数据运行时请将其注释掉。

importbacktraderasbtimportpandasaspd#以下引入脚本是个人的数据库文件,导入其他数据请注释掉fromupdate_sqlimportupdate_sql#更新数据库update_sql(table_name="daily_data")

frombaseimportsql_engine,ts_propro=ts_pro()engine=sql_engine()defget_data(code):sql=f"select*fromdaily_datawheretrade_date>"0201"andts_code="{code}""data=pd.read_sql(sql,engine)data=data.sort_values("trade_date")#前复权data["adjclose"]=(data.close*data.adj_factor/data.adj_factor.iloc[-1]).valuesdata["adjvol"]=(data.vol*data.adj_factor/data.adj_factor.iloc[-1]).valuesdata["adjopen"]=(data.open*data.adj_factor/data.adj_factor.iloc[-1]).valuesdata["adjhigh"]=(data.high*data.adj_factor/data.adj_factor.iloc[-1]).valuesdata["adjlow"]=(data.low*data.adj_factor/data.adj_factor.iloc[-1]).valuesdata=data[["trade_date","adjopen","adjhigh","adjlow","adjclose","adjvol"]]n1=["open","high","low","close","volume"]n2=["adjopen","adjhigh","adjlow","adjclose","adjvol"]data.rename(columns=dict(zip(n2,n1)),inplace=True)data.index=pd.to_datetime(data.trade_date)data=data.sort_index()data["openinterest"]=0data["datetime"]=data.indexdata=data[["datetime","open","high","low","close","volume"]]returndata如果不会搭建数据库,也可以使用tushare pro直接在线获取数据,并转化为backtrader能接受的数据格式。importtushareasts#tusharepro需到官网注册并获取token才能用token="输入你的token"pro=ts.pro_api(token)defget_data2(code,date="0101"):data=ts.pro_bar(ts_code=code,adj="qfq",start_date=date)data.index=pd.to_datetime(data.trade_date)data=data.sort_index()data["volume"]=data.voldata["openinterest"]=0data["datetime"]=pd.to_datetime(data.trade_date)data=data[["datetime","open","high","low","close","volume","openinterest"]]data=data.fillna(0)returndata由于A股全市场有三千多只股票,如果对所有股票进行遍历构建交易策略,Python循环起来会非常慢。为了节省时间,下面先对市场个股进行一次筛选,根据个人偏好,通过条件设置过滤掉大部分股票。

defget_code_list(date="0202"):#默认开始回测dd=pro.daily_basic(trade_date=date)x1=dd.close<100#流通市值低于300亿大于50亿x2=dd.circ_mv>500000x3=dd.circ_mv<3000000#市盈率低于80x4=dd.pe_ttm<80#股息率大于2%x5=dd.dv_ttm>3x=x1&x2&x3&x4&x5stock_list=dd[x].ts_code.valuesreturnstock_list通过价格、市值、市盈率和股息率指标的设置,选择了24只个股进行量化回测。len(get_code_list())

24

策略编写

下面以一个简单的“动量+趋势跟踪”策略作为示例。策略思路为:计算24只股票过去30日的收益率并进行排序,选择前10只股票加入选股池(动量),逐日滚动计算和判断:如果选股池中某只个股满足股价位于20均线以上且没有持仓时买入(以20日均线为生命线跟踪趋势);如果某只个股已持仓但判断不在选股池中或股价位于20均线以下则卖出。每次交易根据十只个股平均持仓(注意:最多交易10只个股)。

classMyStrategy(bt.Strategy):#策略参数params=dict(period=20,#均线周期look_back_days=30,printlog=False)def__init__(self):self.mas=dict()#遍历所有股票,计算20日均线fordatainself.datas:self.mas[data._name]=bt.ind.SMA(data.close,period=self.p.period)defnext(self):#计算截面收益率rate_list=[]fordatainself.datas:iflen(data)>self.p.look_back_days:p0=data.close[0]pn=data.close[-self.p.look_back_days]rate=(p0-pn)/pnrate_list.append([data._name,rate])#股票池long_list=[]sorted_rate=sorted(rate_list,key=lambdax:x[1],reverse=True)long_list=[i[0]foriinsorted_rate[:10]]#得到当前的账户价值total_value=self.broker.getvalue()p_value=total_value*0.9/10fordatainself.datas:#获取仓位pos=self.getposition(data).sizeifnotposanddata._nameinlong_listand\self.mas[data._name][0]>data.close[0]:size=int(p_value/100/data.close[0])*100self.buy(data=data,size=size)ifpos!=0anddata._namenotinlong_listor\self.mas[data._name][0]<data.close[0]:self.close(data=data)deflog(self,txt,dt=None,doprint=False):ifself.params.printlogordoprint:dt=dtorself.datas[0].datetime.date(0)print(f"{dt.isoformat()},{txt}")#记录交易执行情况(可省略,默认不输出结果)defnotify_order(self,order):#如果order为submitted/accepted,返回空iforder.statusin[order.Submitted,order.Accepted]:return#如果order为buy/sellexecuted,报告价格结果iforder.statusin[pleted]:iforder.isbuy():self.log(f"买入:\n价格:{order.executed.price:.2f},\成本:{order.executed.value:.2f},\手续费:{m:.2f}")self.buyprice=order.executed.priceself.buycomm=melse:self.log(f"卖出:\n价格:{order.executed.price:.2f},\成本:{order.executed.value:.2f},\手续费{m:.2f}")self.bar_executed=len(self)#如果指令取消/交易失败,报告结果eliforder.statusin[order.Canceled,order.Margin,order.Rejected]:self.log("交易失败")self.order=None#记录交易收益情况(可省略,默认不输出结果)defnotify_trade(self,trade):ifnottrade.isclosed:returnself.log(f"策略收益:\n毛收益{trade.pnl:.2f},净收益{trade.pnlcomm:.2f}")

数据加载和回测系统设置

写一个循环遍历24只个股数据并加载到回测系统中,将初始本金设置为10万元,手续费为千分之一,回测结束打印出交易日记。

#加载数据

cerebro = bt.Cerebro()

for s in get_code_list():

feed = bt.feeds.PandasData(dataname = get_data(s))

cerebro.adddata(feed, name = s)

#回测设置

startcash=100000.0

cerebro.broker.setcash(startcash)

# 设置佣金为千分之一

cerebro.broker.setcommission(commission=0.001)

# 添加策略

cerebro.addstrategy(MyStrategy,printlog=True)

cerebro.run()

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

portvalue = cerebro.broker.getvalue()

pnl = portvalue - startcash

#打印结果

print(f"总资金: {round(portvalue,2)}")

print(f"净收益: {round(pnl,2)}")

输出结果:

-04-27,买入:价格:14.54,成本:8724.53,手续费:8.72-04-27,买入:价格:22.34,成本:8934.14,手续费:8.93-04-28,卖出:价格:23.06,成本:8934.14,手续费9.22-04-28,策略收益:毛收益287.97,净收益269.82......-05-26,策略收益:毛收益624.00,净收益591.71-05-26,策略收益:毛收益570.00,净收益537.05-05-26,策略收益:毛收益40.00,净收益7.37-05-26,策略收益:毛收益561.00,净收益527.87-05-27,买入:价格:20.00,成本:16000.00,手续费:16.00总资金:182914.68净收益:82914.68

策略回测结果可视化

cerebro = bt.Cerebro()

for s in get_code_list():

feed = bt.feeds.PandasData(dataname = get_data(s))

cerebro.adddata(feed, name = s)

#回测设置

startcash=100000.0

cerebro.broker.setcash(startcash)

# 设置佣金为千分之一

cerebro.broker.setcommission(commission=0.001)

# 添加策略

cerebro.addstrategy(MyStrategy)

注意,plot_result是自己写的对回测结果进行可视化的脚本文件,代码比较长,此处省略,完整代码分享在“金融量化”知识星球中(文末)。

bt.plot_result(cerebro)

03 结语

本文着重介绍了如何使用backtrader进行股票组合量化回测,回测实例仅供参考,不构成任何交易建议。文中构建的“动量+趋势跟踪”策略,并没有对相关参数进行优化,而且股票组合的选股范围较小(待选股票只有24只,而每次交易组合不超过10只),不同交易周期、不同标的参数阈值设置可能存在较大差异。从回测结果的评价指标来看,该策略并不是很理想,年化收益只有11%,最大回撤21%,夏普比率只有0.95。当然,本文的目的不在于结果而是过程。总之,兄弟我先抛块砖,有玉的尽管砸过来。

参考资料:

1. backtrader官方文档和安装包原生代码

/docu/

如果觉得《如何用backtrader对股票组合进行量化回测?》对你有帮助,请点赞、收藏,并留下你的观点哦!

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