1 策略原理
Dual Thrust是一个趋势跟踪系统,由Michael Chalek在20世纪80年代开发,曾被Future Thruth杂志评为最赚钱的策略之一。Dual Thrust系统具有简单易用、适用度广的特点,其思路简单、参数很少,配合不同的参数、止盈止损和仓位管理,可以为投资者带来长期稳定的收益,被投资者广泛应用于股票、货币、贵金属、债券、能源及股指期货市场等。 在Dual Thrust交易系统中,对于震荡区间的定义非常关键,这也是该交易系统的核心和精髓。Dual Thrust系统使用Range = Max(HH-LC,HC-LL)来描述震荡区间的大小。其中HH是N日High的最高价,LC是N日Close的最低价,HC是N日Close的最高价,LL是N日Low的最低价。
具体说:
1、首先计算: (1)N日High的最高价HH, N日Close的最低价LC; (2)N日Close的最高价HC,N日Low的最低价LL; (3)Range = Max(HH-LC,HC-LL) (4)BuyLine = Open + K1*Range (5)SellLine = Open + K2*Range
2.构造系统 (1)当价格向上突破上轨时,如果当时持有空仓,则先平仓,再开多仓;如果没有仓位,则直接开多仓; (2)当价格向下突破下轨时,如果当时持有多仓,泽县平川,再开空仓;如果没有仓位,则直接开空仓;
Dual Thrust对于多头和空头的触发条件,考虑了非对称的幅度,做多和做空参考的Range可以选择不同的周期数,也可以通过参数K1和K2来确定。
当K1<K2时,多头相对容易被触发
当K1>K2时,空头相对容易被触发
因此,在使用该策略时,一方面可以参考历史数据测试的最优参数,另一方面,则可以根据自己对后势的判断,或从其他大周期的技术指标入手,阶段性地动态调整K1和K2的值。
Dual Thrust策略三大要素
信号:当价格突破上下轨道的瞬间发出开仓信号
过滤:除了原始的通道过滤外,还加了另外2个额外的过滤来对付震荡行情
设置上下轨,若价格在轨道内波动不产生交易信号
每天规定多开交易只做一次,避免在震荡行情中,日内不断开平仓操作从而损失手续费
只当Tick行情推送合成的分钟K线高于于日开盘价时判断交易方向为多头,设置突破的停止买入单,同理,分钟K线低于日开盘价时判断交易方向为空头,设置突破的停止卖出单。(停止单的意思是在分钟K线内,条件触发时立刻进行开平仓操作)
止损:固定点位止损策略,如多头仓位在价格下跌到下轨时自动平仓,空头仓位在价格突破到上轨时自动平仓
2 策略代码解析
1) 设置策略参数
国内市场做多的远远多于做空,因此会表现出股价上升的时候慢吞吞,股价下降时会有较大的阻力,但是若是突破阻力,其下跌的速度会相对迅猛。同时在股价下跌过程中,做多的玩家为了止损会强行平仓,因此市场上多了很多空单,进一步加速下降的速度。据观察,设置k1<k2。
fixedSize=1,表示产生交易信号后,只交易1手。initDays=10,意味着数据初始化的天数为10天。
# 策略参数
fixedSize = 100
k1 = 0.4
k2 = 0.6
initDays = 10
2) 设置策略变量
50ETF股指期货的在时间上相关性较差,市场参与者一般只关于前一天的数据而比较不关心过去几天的历史数据,所以这里对传统的Dual Thrust策略中N日取值为1,即认为只有昨天股价的变化会对当天股价有影响。
Dual Thrust策略需要用昨天日K线数据,所以创建列表barList,用于缓存每日K线的开盘价,最高价和最低价,还有K线波动(range),上下轨道和每天的收盘时间(股指期货默认是下午14:55)。
longEntered和shortEntered起着过滤器的作用,在onBar回调函数里面会用到。
# 策略变量
barList = [] # K线对象的列表
dayOpen = 0
dayHigh = 0
dayLow = 0
range = 0
longEntry = 0
shortEntry = 0
exitTime = time(hour=14, minute=55)
longEntered = False
shortEntered = False
3)onBar函数
因为Daul Thrust策略是基于在通道突破的瞬间买入卖出,该策略采取的是在1分钟K线内价格突破通道时触发条件单来交易。为了在追涨杀跌过程中获得一个好的价格,所以在该1分钟内不保证成交。为了防止之前下的单子在上1分钟没有成交,但是下一分钟可能已经调整了价格,就用用cancelAll( )的方法立刻撤销之前未成交的所有委托,保证策略在当前这1分钟开始的时候整个状态是清晰和唯一的。
由于策略由昨天的和当日这2天K线数据来产生交易信号,barList的长度设置为2。用append(bar)来插入每一天K线数据到barList列表中,若该列表的长度小于等于2时,会判断到数据缓存数量不足,则直接返回,不执行下面的任何逻辑了。当第3天到来时,barList列表长度为3, if判断为False,则会用pop(0)的方法来删除最老那天的数据,把最新的数据缓存下来。
def onBar(self, bar):
"""收到Bar推送(必须由用户继承实现)"""
# 撤销之前发出的尚未成交的委托(包括限价单和停止单)
self.cancelAll()
# 计算指标数值
self.barList.append(bar)
if len(self.barList) <= 2:
return
else:
self.barList.pop(0)
lastBar = self.barList[-2]
当新的一天来临时,先对dayHigh做一个过滤,一般情况下第1天dayHigh为0,从第2天起dayHigh不等于0,有了昨天和今天数据后,策略才可以真正运行。然后分别计算:
前1天的波动range
通道上轨(longEntry)
通道下轨(shortEntry)
缓存当天的开盘价,最高价和最低价。并且清空当天是否做出多空交易的状态(重新初始化)
若判断到当天还没有结束时,必须用max()函数来更新dayHigh的数据从而得到当日的最高价,同理dayLow数据用min()函数求得获取到当天最低价
# 新的一天
if lastBar.datetime.date() != bar.datetime.date():
# 如果已经初始化
if self.dayHigh:
self.range = self.dayHigh - self.dayLow
self.longEntry = bar.open + self.k1 * self.range
self.shortEntry = bar.open - self.k2 * self.range
self.dayOpen = bar.open
self.dayHigh = bar.high
self.dayLow = bar.low
self.longEntered = False
self.shortEntered = False
else:
self.dayHigh = max(self.dayHigh, bar.high)
self.dayLow = min(self.dayLow, bar.low)
对昨日波动range做一个过滤,range=0的情况可能是策略刚刚跑到第一天数据,可以昨天行情特殊日K线最高价和最低价相等,也可能计算出了问题。当这些情况发生后直接返回,不进行下面的操作。
过滤完一些特殊情况后,若判断到还没有到收盘时间,即14:55,可以考虑做一些开仓操作。但是注意,这里定义Daul Thrust是一个日内趋势跟踪策略,到了收盘时间就必须平仓出场,不能出现隔夜持仓的情况。
开仓操作有三种情况:
手头上无仓位,若当前这1分钟K线的收盘价高于当日开盘价,认为这是一个向上的趋势。self.longEntered表示今天是否做过多仓操作,默认为False。Dual Thrust有个问题就是当标的价格围绕在当日开盘价附近来回震荡,不断触碰到上下轨道,会造成短时间里来回交易造成的亏损。解决方法就是设置一个过滤,每日不论空头还是多头操作,都只能进行1次交易。在这里判断到当天还没有做多的交易的话,就在通道上轨处挂上一个停止买入单(stop=True)。反之,若当前的分钟K线收盘价低于当日开盘价,认为这是一个向下的趋势。同时判断到当天还没有做空的交易的话,就在通道下轨处挂上一个停止卖出单。
手头上有多仓,因为当天做了一次多头交易了,先更新多仓操作的状态为True。又由于这是日内交易策略,接下来的逻辑就是设置平仓离场了。离场的位置是通道下轨处下一个停止卖出单。同时,若判断到当天还没有做空头交易,除了平仓后还要反向做空。
手头上有空仓,因为当天做了一次空头交易了,先更新其状态为True。然后设置平仓离场位置,这次的位置处于通道上轨,在这里挂一个止损买入单。同时,若判断到当天还还有一次多头交易的机会没用完,需要反向开仓做多。
# 尚未到收盘
if not self.range:
return
if bar.datetime.time() < self.exitTime:
if self.pos == 0:
if bar.close > self.dayOpen:
if not self.longEntered:
self.buy(self.longEntry, self.fixedSize, stop=True)
else:
if not self.shortEntered:
self.short(self.shortEntry, self.fixedSize, stop=True)
# 持有多头仓位
elif self.pos > 0:
self.longEntered = True
# 多头止损单
self.sell(self.shortEntry, self.fixedSize, stop=True)
# 空头开仓单
if not self.shortEntered:
self.short(self.shortEntry, self.fixedSize, stop=True)
# 持有空头仓位
elif self.pos < 0:
self.shortEntered = True
# 空头止损单
self.cover(self.longEntry, self.fixedSize, stop=True)
# 多头开仓单
if not self.longEntered:
self.buy(self.longEntry, self.fixedSize, stop=True)
若到了收盘时间,不管持有多仓还是空仓,用最快的速度平仓。为了保证平仓离场速度和效果,这里不能用止损单,用的是限价单。当持有多仓时,限价卖单是低于市价单的1%,同理,当持有空仓时,限价买单是高于市价单的1%
# 收盘平仓
else:
if self.pos > 0:
self.sell(bar.close * 0.99, abs(self.pos))
elif self.pos < 0:
self.cover(bar.close * 1.01, abs(self.pos))
# 发出状态更新事件
self.putEvent()
3 策略回测
在Jupyter Notebook中进行策略回测,vn.py官方提供3套回测模板,分别是对沪深300股指期货回测,对螺纹钢期货回测,以及品种组合回测。在‘vnpy-1.9.0\examples\CtaBacktesting’文件夹下。在同一文件夹下也有沪深300股指期货和螺纹钢期货2010--2017年的1分钟的历史数据,需要先把历史数据导入MongoDB数据库才有数据进行回测。2017年以后的数据,需要自行找数据源导入。
1)加载策略
%matplotlib inline
from vnpy.trader.app.ctaStrategy.ctaBacktesting import BacktestingEngine, OptimizationSetting, MINUTE_DB_NAME
from vnpy.trader.app.ctaStrategy.strategy.strategyDualThrust import DualThrustStrategy
2)创建回测引擎
engine = BacktestingEngine()
3)设置回测使用的数据
engine.setBacktestingMode(engine.BAR_MODE) # 设置引擎的回测模式为K线
engine.setDatabase(MINUTE_DB_NAME, 'IF0000') # 设置沪深300股指期货 1分钟K线数据
engine.setStartDate('20100416') # 设置回测用的数据起始日期
engine.setEndDate('20180101') # 设置回测用的数据起始日期
4)配置回测引擎参数
engine.setSlippage(0.2) # 设置滑点为股指1跳
engine.setRate(0.5/10000) # 设置手续费万0.5
engine.setSize(300) # 设置股指合约大小 ,IF股指是300元/点
engine.setPriceTick(0.2) # 设置股指最小价格变动,IF股指最新价格变化单位是0.2
engine.setCapital(1000000) # 设置回测本金为100万
5)在引擎中创建策略对象
d = {} # 策略参数配置
engine.initStrategy(DualThrustStrategy, {})
6) 运行回测
engine.runBacktesting() # 运行回测
7)显示逐日回测结果
engine.showDailyResult()
回测结果显示,尽管DualThrust策略稳稳地把握住了15年末大牛熊的行情,但在其他时间都是震荡市,假突破多,造成大部分时间的亏损,其回撤的规模也是巨大的,最大回撤达-59.7%。但同样的,年化收益达15倍,因为原策略执行的手数是100手,即fixedSize = 100,在下面的优化中需要把 fixedSize 改成1手。
同时,需要对该策略的过滤器进一步改进,来更好地避免在震荡行情做单,降低整个策略的风险。止损也可以考虑用到固定百分数点位移动止损。
4 策略优化
1)修改执行手数, fixedSize=1
2)把基于1分钟K线产生交易信号换成基于5分钟判断
#----------------------------------------------------------------------
def __init__(self, ctaEngine, setting):
"""Constructor"""
super(DualThrustStrategy, self).__init__(ctaEngine, setting)
self.bg = BarGenerator(self.onBar, 5, self.onXminBar) # 创建K线合成器对象
......
#----------------------------------------------------------------------
def onBar(self, bar):
"""收到Bar推送(必须由用户继承实现)"""
self.bg.updateBar(bar)
#----------------------------------------------------------------------
def onXminBar(self, bar):
"""收到X分钟K线"""
主逻辑判断从onBar函数转到onXminBar中
......
3)设置固定点位(0.8%)移动止损(移动止损将在下一节 8.4 AtrRsi策略中详细讲解)
#策略变量
intraTradeHigh = 0 # 持仓期内的最高点
intraTradeLow = 0 # 持仓期内的最低点
longStop = 0 # 多头止损
shortStop = 0 # 空头止损
#策略参数
trailingPercent = 0.8 # 百分比移动止损
......
#----------------------------------------------------------------------
def onXminBar(self, bar):
......
# 持有多头仓位
elif self.pos > 0:
self.longEntered = True
# 计算多头持有期内的最高价,以及重置最低价
self.intraTradeHigh = max(self.intraTradeHigh, bar.high)
self.intraTradeLow = bar.low
# 计算多头移动止损
longStop = self.intraTradeHigh * (1-self.trailingPercent/100)
# 发出本地止损委托
self.sell(longStop, self.fixedSize, stop=True)
# 空头开仓单
if not self.shortEntered:
self.short(longStop, self.fixedSize, stop=True)
# 持有空头仓位
elif self.pos < 0:
self.shortEntered = True
self.intraTradeLow = min(self.intraTradeLow, bar.low)
self.intraTradeHigh = bar.high
shortStop = self.intraTradeLow * (1+self.trailingPercent/100)
self.cover(shortStop, self.fixedSize, stop=True)
# 多头开仓单
if not self.longEntered:
self.buy(shortStop, self.fixedSize, stop=True)
.....
4)通过优化参数,得到新的k1,k2数据,即k1=0.4,k2=0.7
setting = OptimizationSetting() # 新建一个优化任务设置对象
setting.setOptimizeTarget('sharpeRatio') # 设置优化排序的目标是夏普比率
setting.addParameter('k1', 0.2, 0.7, 0.1) # 增加第一个优化参数k1,起始0.2,结束0.7,步进0.1
setting.addParameter('k2', 0.2, 0.8, 0.1) # 增加第二个优化参数k2,起始0.2,结束0.8,步进0.1
# 执行多进程优化
import time
start = time.time()
resultList = engine.runParallelOptimization(DualThrustStrategy, setting)
print u'耗时:%s' %(time.time()-start)
4)优化后结果
策略改进后,最大回撤降低至-6.16%,夏普比率升至1.99。
5 滚动回测
以3年为滚动周期,步进是半年,对20100416--20180101区间进行滚动回测以及参数优化,并且收集表现最好的前3组优化参数。
通过对历史优化参数的统计分析,来预测未来的优化参数。其选择标准如下:
近年参考权重大于远年
若优化参数整体变化不大,取众数
若优化参数整体发生变化,在近年区间取众数
若前一年参数发生剧烈变化,对该区间进行更为细致的滚动回测和参数优化
上图是的历史优化参数整体变化不大,故取众数,即 k1 = 0.4,k2 = 0.7。同时,优化参数变化不大也说明策略稳健性强。
预测优化参数历史表现:
年化收益23.7%,最大回撤-6.16%,夏普比率达1.99。
对2018年以后的预测效果:
2018年春季发生较大的回撤,最大回撤达-7.15%,之后策略表现好转,年化收益13.64%,夏普比率1.02。
本文链接:https://www.kinber.cn/post/3919.html 转载需授权!
推荐本站淘宝优惠价购买喜欢的宝贝: