01 引言backtrader是功能非常強(qiáng)大的量化回測(cè)框架之一,得到歐洲很多銀行、基金等金融機(jī)構(gòu)的青睞,并應(yīng)用于實(shí)盤交易中。公眾號(hào)Python金融量化針對(duì)backtrader的入門和應(yīng)用已連續(xù)發(fā)布了四篇推文:《【手把手教你】入門量化回測(cè)最強(qiáng)神器backtrader(一)》、《【手把手教你】入門量化回測(cè)最強(qiáng)神器backtrader(二)》、《【手把手教你】入門量化回測(cè)最強(qiáng)神器backtrader(三)》和《backtrader如何加載股票因子數(shù)據(jù)?以換手率、市盈率為例進(jìn)行回測(cè)【附Python代碼】》,分別介紹了backtrader整個(gè)框架的組成部分、回測(cè)系統(tǒng)的運(yùn)行,策略模塊交易日志的編寫、策略參數(shù)的尋優(yōu),Analyzers模塊的分析、策略業(yè)績(jī)?cè)u(píng)價(jià)指標(biāo)可視化分析以及擴(kuò)張數(shù)據(jù)加載模塊對(duì)市盈率因子進(jìn)行回測(cè)等。上述文章有一個(gè)共同點(diǎn)是回測(cè)實(shí)例均以個(gè)股為交易標(biāo)的,那么如何對(duì)股票組合進(jìn)行回測(cè)呢?本文將重點(diǎn)介紹如何加載多只股票數(shù)據(jù),并構(gòu)建交易組合進(jìn)行量化回測(cè)。 02 股票組合回測(cè)實(shí)例數(shù)據(jù)獲取 第一步是數(shù)據(jù)獲取和加載,A股數(shù)據(jù)個(gè)人一般使用tushare來獲取,由于對(duì)多只股頻繁獲取容易出現(xiàn)接口報(bào)錯(cuò),因此個(gè)人在本地搭建了一個(gè)股票數(shù)據(jù)庫(關(guān)于數(shù)據(jù)庫搭建請(qǐng)參照:《【手把手教你】Python面向?qū)ο缶幊倘腴T及股票數(shù)據(jù)管理應(yīng)用實(shí)例》)。注意,下面導(dǎo)入的update_sql和base是自己寫的本地腳本,使用自己數(shù)據(jù)運(yùn)行時(shí)請(qǐng)將其注釋掉。 import backtrader as btimport pandas as pd#以下引入腳本是個(gè)人的數(shù)據(jù)庫文件,導(dǎo)入其他數(shù)據(jù)請(qǐng)注釋掉from update_sql import update_sql#更新數(shù)據(jù)庫update_sql(table_name='daily_data')
import tushare as ts#tushare pro需到官網(wǎng)注冊(cè)并獲取token才能用token='輸入你的token'pro=ts.pro_api(token)def get_data2(code,date='20150101'): 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.vol data['openinterest']=0 data['datetime']=pd.to_datetime(data.trade_date) data=data[['datetime','open','high','low','close','volume','openinterest']] data=data.fillna(0) return data由于A股全市場(chǎng)有三千多只股票,如果對(duì)所有股票進(jìn)行遍歷構(gòu)建交易策略,Python循環(huán)起來會(huì)非常慢。為了節(jié)省時(shí)間,下面先對(duì)市場(chǎng)個(gè)股進(jìn)行一次篩選,根據(jù)個(gè)人偏好,通過條件設(shè)置過濾掉大部分股票。
len(get_code_list())
策略編寫下面以一個(gè)簡(jiǎn)單的“動(dòng)量+趨勢(shì)跟蹤”策略作為示例。策略思路為:計(jì)算24只股票過去30日的收益率并進(jìn)行排序,選擇前10只股票加入選股池(動(dòng)量),逐日滾動(dòng)計(jì)算和判斷:如果選股池中某只個(gè)股滿足股價(jià)位于20均線以上且沒有持倉時(shí)買入(以20日均線為生命線跟蹤趨勢(shì));如果某只個(gè)股已持倉但判斷不在選股池中或股價(jià)位于20均線以下則賣出。每次交易根據(jù)十只個(gè)股平均持倉(注意:最多交易10只個(gè)股)。 class MyStrategy(bt.Strategy): # 策略參數(shù) params = dict( period=20, # 均線周期 look_back_days=30, printlog=False ) def __init__(self): self.mas = dict() #遍歷所有股票,計(jì)算20日均線 for data in self.datas: self.mas[data._name] = bt.ind.SMA(data.close, period=self.p.period) def next(self): #計(jì)算截面收益率 rate_list=[] for data in self.datas: if len(data)>self.p.look_back_days: p0=data.close[0] pn=data.close[-self.p.look_back_days] rate=(p0-pn)/pn rate_list.append([data._name,rate]) #股票池 long_list=[] sorted_rate=sorted(rate_list,key=lambda x:x[1],reverse=True) long_list=[i[0] for i in sorted_rate[:10]] # 得到當(dāng)前的賬戶價(jià)值 total_value = self.broker.getvalue() p_value = total_value*0.9/10 for data in self.datas: #獲取倉位 pos = self.getposition(data).size if not pos and data._name in long_list and \ self.mas[data._name][0]>data.close[0]: size=int(p_value/100/data.close[0])*100 self.buy(data = data, size = size) if pos!=0 and data._name not in long_list or \ self.mas[data._name][0]<data.close[0]: self.close(data = data) def log(self, txt, dt=None,doprint=False): if self.params.printlog or doprint: dt = dt or self.datas[0].datetime.date(0) print(f'{dt.isoformat()},{txt}') #記錄交易執(zhí)行情況(可省略,默認(rèn)不輸出結(jié)果) def notify_order(self, order): # 如果order為submitted/accepted,返回空 if order.status in [order.Submitted, order.Accepted]: return # 如果order為buy/sell executed,報(bào)告價(jià)格結(jié)果 if order.status in [order.Completed]: if order.isbuy(): self.log(f'買入:\n價(jià)格:{order.executed.price:.2f},\ 成本:{order.executed.value:.2f},\ 手續(xù)費(fèi):{order.executed.comm:.2f}') self.buyprice = order.executed.price self.buycomm = order.executed.comm else: self.log(f'賣出:\n價(jià)格:{order.executed.price:.2f},\ 成本: {order.executed.value:.2f},\ 手續(xù)費(fèi){order.executed.comm:.2f}') self.bar_executed = len(self) # 如果指令取消/交易失敗, 報(bào)告結(jié)果 elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('交易失敗') self.order = None #記錄交易收益情況(可省略,默認(rèn)不輸出結(jié)果) def notify_trade(self,trade): if not trade.isclosed: return self.log(f'策略收益:\n毛收益 {trade.pnl:.2f}, 凈收益 {trade.pnlcomm:.2f}') 數(shù)據(jù)加載和回測(cè)系統(tǒng)設(shè)置 寫一個(gè)循環(huán)遍歷24只個(gè)股數(shù)據(jù)并加載到回測(cè)系統(tǒng)中,將初始本金設(shè)置為10萬元,手續(xù)費(fèi)為千分之一,回測(cè)結(jié)束打印出交易日記。 #加載數(shù)據(jù) cerebro = bt.Cerebro() 輸出結(jié)果:
策略回測(cè)結(jié)果可視化 cerebro = bt.Cerebro() for s in get_code_list(): 注意,plot_result是自己寫的對(duì)回測(cè)結(jié)果進(jìn)行可視化的腳本文件,代碼比較長(zhǎng),此處省略,完整代碼分享在“金融量化”知識(shí)星球中(文末)。 bt.plot_result(cerebro) 03 結(jié)語本文著重介紹了如何使用backtrader進(jìn)行股票組合量化回測(cè),回測(cè)實(shí)例僅供參考,不構(gòu)成任何交易建議。文中構(gòu)建的“動(dòng)量+趨勢(shì)跟蹤”策略,并沒有對(duì)相關(guān)參數(shù)進(jìn)行優(yōu)化,而且股票組合的選股范圍較?。ùx股票只有24只,而每次交易組合不超過10只),不同交易周期、不同標(biāo)的參數(shù)閾值設(shè)置可能存在較大差異。從回測(cè)結(jié)果的評(píng)價(jià)指標(biāo)來看,該策略并不是很理想,年化收益只有11%,最大回撤21%,夏普比率只有0.95。當(dāng)然,本文的目的不在于結(jié)果而是過程。總之,兄弟我先拋塊磚,有玉的盡管砸過來。 參考資料: 1. backtrader官方文檔和安裝包原生代碼 https://www./docu/ |
|