Dual Thrust交易算法介绍
Dual Thrust交易算法是由Michael Chalek开发的著名量化交易策略。它通常用于期货,外汇和股票市场。Dual Thrust的概念属于典型的突破交易系统,其运用“双推力”系统根据历史价格构建更新的回溯期,这在理论上使其在任何给定时期内更加稳定。
在这篇文章中,我们给出了此策略的详细逻辑细节,并展示了如何在发明者量化平台上实现此算法。首先,我们要选择所交易标的的历史价格,该范围基于最近N天的收盘价,最高价和最低价计算。当市场从开盘价移动一定范围时,执行开仓。
我们在常见的两个市场状态下用单个交易对测试了此策略,即趋势市场和震荡市场。结果表明,这种动量交易系统在趋势市场中运行得更好,在波动较大的市场中会触发一些无效买卖信号。在区间市场下,我们可以调整参数以获得更好的回报。作为个别参照交易标的的比较,我们还测试了国内商品期货市商。结果表明该策略好于平均表现。
DT策略原理
它的逻辑原型是常见的日内交易策略。开盘区间突破策略基于今天的开盘价加上或减去昨天幅度的一定百分比来确定上下轨。当价格突破上方轨道时,它会开仓买入,当它突破下方轨道时,它会开仓做空。
策略原理
- 在收盘后,计算两个价值:最高价 – 收盘价,收盘价 – 最低价。然后取这两个值中较大的值,将该值乘以0.7。让我们称之为值K,K值我们称为触发值。
- 第二日开市后,记录开盘价,然后在价格超过(开盘价+触发价值)时立即买入,或在价格低于(开盘价 – 触发价值)时卖空。
- 此策略没有明显的止损。这个系统是一个反向系统,也就是说,如果在价格超过(开盘价+触发值)时有一个空头仓位订单,那么它将发送两个买单(一个关闭错误的仓位,另一个打开正确方向的仓位)。出于同样的原因,如果有一个多头仓位价格低于(开盘价-触发价值),那么它将发送两个卖单。
DT策略的数学表达式
范围=最大值(HH-LC,HC-LL)
多头信号的计算方法是
cap = open + K1 × Rangecap = open + K1 × Range
空头短信号的计算方法是
floor = open – K2 × Rangefloor = open – K2 × Range
其中K1和K2是参数。当K1大于K2时,触发多头信号,反之亦然。为了演示,我们选择K1 = K2 = 0.5。在实际交易中,我们仍然可以使用历史数据来优化这些参数或根据市场趋势调整参数。如果您看涨市场,K1应小于K2,如果您看跌市场,则K1应大于K2。
该系统是一个反转系统,因此如果投资者在价格突破上轨时持有空头仓位,则在开多仓前要先平掉空头仓位。如果投资者在价格突破下轨时持有多头仓位,则在开新的空头仓位之前应先平掉多头仓位。
DT策略的改进:
在范围设置中,引入前N天的四个价格点(高,开,低,收),使得一定时期内的范围相对稳定,可应用于日线趋势跟踪。
此策略的开多和空的触发条件,考虑不对称幅度,多空交易可参考范围应该选择不同数量的周期,也可以通过参数K1和K2来确定。当K1K2时,空头信号相对容易被触发。
因此,在使用此策略时,一方面可以参考历史数据回测的最佳参数。另一方面,您可以根据自己对后势的判断或其他主要周期技术指标分阶段调整K1和K2。
这是一种等待信号,进入市场,套利,然后离开市场的典型交易方式,但效果非常出色。
在发明者量化平台部署DT策略
我们打开,FMZ.COM, 登陆账户,点击控制中心,部署托管者和机器人。
关于如何部署托管者和机器人,请参考我之前的文章:https://www.fmz.com/bbs-topic/4140
想购买自己云计算服务器部署托管者的读者,可以参考这篇文章:https://www.fmz.com/bbs-topic/2848
接下来,我们点击左侧栏目当中的策略库,点击新建策略
在编写策略页面右上角记得选择编程语言为Python,如图:
接下来我们把Python代码写入代码编辑页面中,下面的代码,有着非常详细的逐行注释,各位读者可以慢慢理解和体会。
我们就用OKCoin期货来测试这个策略:
import time # 这里需要引入python自带的时间库,后边的程序会用到class Error_noSupport(BaseException): # 我们定义一个名为ChartCfg的全局class,用来初始化策略图表设置。对象有很多关于图表功能的属性。图表库为:HighCharts def __init__(self): # log出提示信息 Log("只支持OKCoin期货!#FF0000")class Error_AtBeginHasPosition(BaseException): def __init__(self): Log("启动时有期货持仓! #FF0000")ChartCfg = { '__isStock': True, # 该属性用于控制是否显示为单独控制数据序列(可以在图表上取消单独一个数据序列的显示),如果指定__isStock: false, 则显示为普通图表 'title': { # title为图表的主要标题 'text': 'Dual Thrust 上下轨图' # title的一个属性text为标题的文本,这里设置为'Dual Thrust 上下轨图'该文本就会显示在标题位置 }, 'yAxis': { # 图表坐标Y轴的相关设置 'plotLines': [{ # Y轴上的水平线(和Y轴垂直),该属性的值是一个数组,即多条水平线的设置 'value': 0, # 水平线在Y轴上的坐标值 'color': 'red', # 水平线的颜色 'width': 2, # 水平线的线宽 'label': { # 水平线上的标签 'text': '上轨', # 标签的文本 'align': 'center' # 标签的显示位置,这里设置为居中(即 :'center') }, }, { # 第二条水平线([{...},{...}]数组中的第二个元素) 'value': 0, # 水平线在Y轴上的坐标值 'color': 'green', # 水平线的颜色 'width': 2, # 水平线的线宽 'label': { # 标签 'text': '下轨', 'align': 'center' }, }] }, 'series': [{ # 数据序列,即用来在图表上显示数据线、K线、标记等等内容的数据。也是一个数组第一个索引为0。 'type': 'candlestick', # 索引为0数据序列的类型:'candlestick' 表示为K线图 'name': '当前周期', # 数据序列的名称 'id': 'primary', # 数据序列的ID,用于下一个数据序列相关设置。 'data': [] # 数据序列的数组,用于储存具体的K线数据 }, { 'type': 'flags', # 数据序列,类型:'flags',在图表上显示标签,表示做多和做空。索引为1。 'onSeries': 'primary', # 这个属性表示标签显示在id为'primary'上。 'data': [] # 保存标签数据的数组。 }] }STATE_IDLE = 0 # 状态常量,表示空闲STATE_LONG = 1 # 状态常量,表示持多仓STATE_SHORT = 2 # 状态常量,表示持空仓State = STATE_IDLE # 表示当前程序状态 ,初始赋值为空闲LastBarTime = 0 # K线最后一柱的时间戳(单位为毫秒,1000毫秒等于1秒,时间戳是1970年1月1日到现在时刻的毫秒数是一个很大的正整数)UpTrack = 0 # 上轨值BottomTrack = 0 # 下轨值chart = None # 用于接受Chart这个API函数返回的图表控制对象。用该对象(chart)可以调用其成员函数向图表内写入数据。InitAccount = None # 初始账户情况LastAccount = None # 最新账户情况Counter = { # 计数器,用于记录盈亏次数 'w': 0, # 赢次数 'l': 0 # 亏次数}def GetPosition(posType): # 定义一个函数,用来存储账户持仓信息 positions = exchange.GetPosition() # exchange.GetPosition()是发明者量化的官方API,关于它的用法,请参考我的官方API文档:https://www.fmz.com/api return [{'Price': position['Price'], 'Amount': position['Amount']} for position in positions if position['Type'] == posType] # 返回各种持仓信息def CancelPendingOrders(): # 定义一个函数,专门用来撤单 while True: # 循环检查 orders = exchange.GetOrders() # 如果有持仓 [exchange.CancelOrder(order['Id']) for order in orders if not Sleep(500)] # 撤单语句 if len(orders) == 0: # 逻辑判断 break def Trade(currentState,nextState): # 定义一个函数,用来判断下单逻辑 global InitAccount,LastAccount,OpenPrice,ClosePrice # 定义全局作用域 ticker = _C(exchange.GetTicker) # 关于_C的用法,请参考:https://www.fmz.com/api slidePrice = 1 # 定义滑点值 pfn = exchange.Buy if nextState == STATE_LONG else exchange.Sell # 买卖判断逻辑 if currentState != STATE_IDLE: # 循环开始 Log(_C(exchange.GetPosition)) # 日志信息 exchange.SetDirection("closebuy" if currentState == STATE_LONG else "closesell") # 调整下单方向,特别是下过单后 while True: ID = pfn( (ticker['Last'] - slidePrice) if currentState == STATE_LONG else (ticker['Last'] + slidePrice), AmountOP) # 限价单,ID = pfn(-1, AmountOP)为市价单,ID = pfn(AmountOP)为市价单 Sleep(Interval) # 休息一阵,防止API访问频率过快,账户被封。 Log(exchange.GetOrder(ID)) # Log信息 ClosePrice = (exchange.GetOrder(ID))['AvgPrice'] # 设置收盘价 CancelPendingOrders() # 调用撤单函数 if len(GetPosition(PD_LONG if currentState == STATE_LONG else PD_SHORT)) == 0: # 撤单逻辑 break account = exchange.GetAccount() # 获取账户信息 if account['Stocks'] > LastAccount['Stocks']: # 如果当前账户币值大于之前账户币值 Counter['w'] += 1 # 盈亏计数器中,盈利次数加一 else: Counter['l'] += 1 # 否者亏损次数加一 Log(account) # log信息 LogProfit((account['Stocks'] - InitAccount['Stocks']),"收益率: