量化交易學習筆記(十七)——多隻股票同時策略回測


量化交易學習筆記(十七)——多隻股票同時策略回測

  • 假設我們現在有策略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>


分享到:


相關文章: