- 假設我們現在有策略A,在股票a的歷史數據上進行回測後,發現能夠取得穩定收益。但是我們有很長時間要等待股票a達到買入條件後,才能進行買入。這是對時間成本的嚴重浪費。
- 我們可以嘗試做這樣的改進:在股票a,b,c……的歷史數據上分別進行策略回測,找到一個能夠穩定收益策略B,來避免時間成本浪費的問題。但是這樣仍然存在問題,在等待股票a出現買點的時候,股票b,c……的買點可能也沒有出現。因此對所有股票依次做單獨的策略回測,不足以驗證策略的優劣。
鑑於以上兩點,我們在驗證策略時,需要對多隻甚至全部的股票同時進行回測。本文基於backtrader,編寫了多股票同時回測程序。
同樣,本文旨在驗證回測功能,策略依然選擇簡單的長短期均線金叉買入死叉賣出策略。核心代碼位於策略類的init及next方法,先來看init方法:
<code>def __init__(self): self.inds = dict() for i, d in enumerate(self.datas): self.inds[d] = dict() self.inds[d]['sma1'] = bt.ind.SMA(d.close, period=self.p.pfast) # 短期均線 self.inds[d]['sma2'] = bt.ind.SMA(d.close, period=self.p.pslow) # 長期均線 self.inds[d]['cross'] = bt.ind.CrossOver(self.inds[d]['sma1'], self.inds[d]['sma2'], plot = False) # 交叉信號/<code>
這裡定義了一個python字典類型變量self.inds,用於存儲不同股票數據的技術指標,該字典的key為單支股票的數據,即代碼中的d,value對應的該股票對應的技術指標,這些技術指標也存在一個字典內,字典內包含短期均線、長期均線、交叉信號3個指標。
再來看next方法:
<code> def __init__(self): self.inds = dict() for i, d in enumerate(self.datas): self.inds[d] = dict() self.inds[d]['sma1'] = bt.ind.SMA(d.close, period=self.p.pfast) # 短期均線 self.inds[d]['sma2'] = bt.ind.SMA(d.close, period=self.p.pslow) # 長期均線 self.inds[d]['cross'] = bt.ind.CrossOver(self.inds[d]['sma1'], self.inds[d]['sma2'], plot = False) # 交叉信號/<code>
next方法中,循環遍歷所有待測的股票,對每隻股票,獲取時間及股票名稱,這樣便於後續打印輸出、日誌留存或者調試。然後通過判斷當前股票position的size,判斷是否已經買入該股票,如果沒有買入,判斷短期均線金叉長期均線後,即可買入。如果已經持有了該股票,那麼判斷長期均線死叉短期均線後即可賣出。
最後要注意的是,向cerebro添加不同股票數據時,補充添加股票名稱,以便後續調試及分析使用:
<code>cerebro.adddata(data, name = stk_code) # 在Cerebro中添加股票數據/<code>
我們依然選擇5日線作為短期均線,60日線作為長期均線,回測初始資金100000,單筆操作單位1000股,佣金千分之一,回測時間自2018年1月1日至2020年3月3日,按股票代碼的升序排列依次添加回測股票,即先回測000001, 再加入000002,再加入000004……(000003停牌還是退市了。。。)
當1只股票進行回測時,回測最終資產103355.34:
當2只股票進行回測時,回測最終資產97427.99:
當3只股票進行回測時,回測最終資產120535.95:
此外,還測試了5只股票回測最終資產為118823.75,10只股票最終資產為116871.02,隨著回測股票數目的增加,程序運行的時間也越長。
友情提示:本系列學習筆記只做數據分析,記錄個人學習過程,不作為交易依據,盈虧自負。
多隻股票同時策略回測程序:
<code>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框架 import pandas as pd stk_num = 3 # 回測股票數目 # 創建策略 class SmaCross(bt.Strategy): # 可配置策略參數 params = dict( pfast=5, # 短期均線週期 pslow=60, # 長期均線週期 poneplot = False, # 是否打印到同一張圖 pstake = 1000 # 單筆交易股票數目 ) def __init__(self): self.inds = dict() for i, d in enumerate(self.datas): self.inds[d] = dict() self.inds[d]['sma1'] = bt.ind.SMA(d.close, period=self.p.pfast) # 短期均線 self.inds[d]['sma2'] = bt.ind.SMA(d.close, period=self.p.pslow) # 長期均線 self.inds[d]['cross'] = bt.ind.CrossOver(self.inds[d]['sma1'], self.inds[d]['sma2'], plot = False) # 交叉信號 # 跳過第一隻股票data,第一隻股票data作為主圖數據 if i > 0: if self.p.poneplot: d.plotinfo.plotmaster = self.datas[0] def next(self): for i, d in enumerate(self.datas): dt, dn = self.datetime.date(), d._name pos = self.getposition(d).size if not pos: if self.inds[d]['cross'] > 0: self.buy(data = d, size = self.p.pstake) elif self.inds[d]['cross'] < 0: self.close(data = d) cerebro = bt.Cerebro() # 創建cerebro # 讀入股票代碼 stk_code_file = '../TQDat/data/tq_stock_code.csv' stk_pools = pd.read_csv(stk_code_file, encoding = 'gbk') if stk_num > stk_pools.shape[0]: print('股票數目不能大於%d' % stk_pools.shape[0]) exit() for i in range(stk_num): stk_code = stk_pools['code'][stk_pools.index[i]] stk_code = '%06d' % stk_code # 讀入數據 datapath = '../TQDat/day/stk/' + stk_code + '.csv' # 創建數據 data = bt.feeds.GenericCSVData( dataname = datapath, fromdate = datetime.datetime(2018, 1, 1), todate = datetime.datetime(2020, 3, 31), nullvalue = 0.0, dtformat = ('%Y-%m-%d'), datetime = 0, open = 1, high = 2, low = 3, close = 4, volume = 5, openinterest = -1 ) # 在Cerebro中添加股票數據 cerebro.adddata(data, name = stk_code) # 設置啟動資金 cerebro.broker.setcash(100000.0) # 設置交易單位大小 #cerebro.addsizer(bt.sizers.FixedSize, stake = 5000) # 設置佣金為千分之一 cerebro.broker.setcommission(commission=0.001) cerebro.addstrategy(SmaCross, poneplot = False) # 添加策略 cerebro.run() # 遍歷所有數據 # 打印最後結果 print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) cerebro.plot(style = "candlestick") # 繪圖/<code>