對策略進行優(yōu)化
多數(shù)策略實際上依賴于指標(biāo),指標(biāo)又依賴一下預(yù)設(shè)的數(shù)值。那么預(yù)設(shè)的數(shù)值是否合理? 光憑腦袋想肯定是不行的,既然我們用了量化的方法。可以教給計算機來計算,找到最優(yōu)值。
經(jīng)典的28輪動策略
300、500、國債指數(shù)輪動,300和500的20天漲幅哪個大持有哪個,兩個都為負(fù)數(shù)持有國債.
28輪動的收益到底怎么樣?我們通過回測程序看下。
我們還是回測2012-1-1到2019-12-31的歷史數(shù)據(jù)。(主要時baostock的國債指數(shù)在2011年有部分缺失)
對策略進行優(yōu)化
策略里的20天,究竟為什么?有沒有更好的選擇?
可以通過optstrategy設(shè)置策略參數(shù)的測試范圍,backtrader會對這些參數(shù)進行測試驗證。
這塊驗證了1~60天的收益率情況。
periods = range(1, 60)
...
cerebro.optstrategy(TestStrategy, period=periods)
我們可以通過優(yōu)化,進行下測試,測試結(jié)果如下(最高的5個):
21211.89%12.05%
20176.95%10.72%
8174.41%10.62%
19163.45%10.17%
22163.34%10.17%
怎么設(shè)置交易費率(不可忽視的交易成本)
短周期的輪動策略,通常會導(dǎo)致大量換手。不同交易費率,究竟在回測結(jié)果中又多大影響?
我們可以通過setcommission設(shè)置交易費率進行下測試。
可以看出來不同費率下總的收益和每年平均收益,差距還是挺大的。
cerebro.broker.setcommission(0.0005)
手續(xù)費為0.0005時的收益率情況:
21211.89%12.05%
20176.95%10.72%
8174.41%10.62%
19163.45%10.17%
22163.34%10.17%
手續(xù)費為0.0001時的收益率情況:
periodTotal ROIAnnual ROI
21275.69%14.15%
8269.06%13.95%
20233.37%12.8%
19219.36%12.31%
12219.02%12.3%
總結(jié)
28輪動的策略,總體來說回報還是不錯的,遠(yuǎn)遠(yuǎn)戰(zhàn)勝了通脹。 在28輪動策略里,我們需要重視交易費率的問題,盡量采用費率更低的輪動方式。(比如 從券商處獲得更大的優(yōu)惠,或者通過同一家基金公司的基金的方式轉(zhuǎn)換節(jié)省交易費用。)
完整回測代碼
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
"""計算28輪動某個標(biāo)的池的盈利情況"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
# Import the backtrader platform
import backtrader as bt
import backtrader.feeds as btfeed
# Create a Stratey
class TestStrategy(bt.Strategy):
params = (
('period', 20),
)
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders
self.order = None
self.month = -1
self.mom = [bt.indicators.MomentumOscillator(i, period=self.params.period) for i in self.datas]
def next(self):
# Simply log the closing price of the series from the reference
buy_id = 0
c = [i.momosc[0] for i in self.mom]
c[0] = 0
index, value = c.index(max(c)), max(c)
if value > 100:
buy_id = index
# print("buy_id ", buy_id)
for i in range(0, len(c)):
if i != buy_id:
position_size = self.broker.getposition(data=self.datas[i]).size
if position_size != 0:
self.order_target_percent(data=self.datas[i], target=0)
position_size = self.broker.getposition(data=self.datas[buy_id]).size
if position_size == 0:
self.order_target_percent(data=self.datas[buy_id], target=0.98)
def stop(self):
# print(self.broker.getvalue())
return_all = self.broker.getvalue() / 1200000.0
print('{0},{1}%,{2}%'.format(self.params.period,
round((return_all - 1.0) * 100, 2),
round((pow(return_all, 1.0 / 8) - 1.0) * 100, 2)
))
class TSCSVData(btfeed.GenericCSVData):
params = (
("fromdate", datetime.datetime(2012, 1, 1)),
("todate", datetime.datetime(2019, 12, 31)),
('nullvalue', 0.0),
('dtformat', ('%Y-%m-%d')),
('openinterest', -1)
)
def backtest(cash, files, periods):
files = files
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.optstrategy(TestStrategy, period=periods)
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
for i in files:
datapath = os.path.join(modpath, 'datas/{0}'.format(i))
# Create a Data Feed
data = TSCSVData(dataname=datapath)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Set our desired cash start
cerebro.broker.setcash(cash)
cerebro.broker.setcommission(0.0005)
# Run over everything
print("period,Total ROI,Annual ROI")
cerebro.run()
if __name__ == '__main__':
"""第0個標(biāo)的是默認(rèn)標(biāo)的"""
files = ['bs_sh.000012.csv', 'bs_sh.000300.csv', 'bs_sh.000905.csv']
cash = 1200000.0
periods = range(0, 60)
backtest(cash, files, periods)
|