Dual Thrust 策略

ZaynPei Lv6

Dual Thrust 策略(双向推力策略)由 Michael Chalek 于 20 世纪 80 年代提出,是一种旨在捕捉日内突破的趋势跟踪系统。它利用前一个交易周期(通常是前一日)的价格波动信息来确定当前交易日的开仓区间。

该策略的关键在于建立一个动态的交易区间,包含一个上限(阻力线)和一个下限(支撑线)。

  • 突破上限(阻力线):被视为强烈的向上动能,产生做多信号。
  • 跌破下限(支撑线):被视为强烈的向下动能,产生做空信号。

区间边界的计算(策略核心)

上下限的设定是 Dual Thrust 策略的核心,它决定了突破的敏感度。它结合了前一个交易周期的最高价、最低价和收盘价,以确定一个有效的波动幅度。

  • HH: N 日内的最高价的最高值 (Highest High)。
  • HC: N 日内的收盘价的最高值 (Highest Close)。
  • LC: N 日内的收盘价的最低值 (Lowest Close)。
  • LL: N 日内的最低价的最低值 (Lowest Low)。

波动范围的计算遵循以下公式:

这个公式试图捕捉过去 N 天内市场的最大价格运动幅度。 HH - LC 代表了“最大上涨力量”(从最低收盘价到最高价的距离)。 HC - LL 代表了“最大下跌力量”(从最高收盘价到最低价的距离)。 取这两者中的较大值,可以更稳健地代表近期的市场波动性。

设定上下轨 (Trigger Lines):

  • 上轨 (买入触发线):
  • 下轨 (卖出触发线):

这里的 K1 和 K2 是敏感度系数。它们控制了交易区间的宽度。K 值越小,区间越窄,交易信号越频繁,但也越容易被市场噪音触发。K 值越大,区间越宽,信号更少,但一旦触发,可能代表着更强的趋势。

一个有趣的应用是,当交易者对后市的上涨和下跌预期不对称时,可以设置不同的 K1 和 K2。例如,如果认为上涨趋势更容易形成,可以设置一个较小的 K1 和一个较大的 K2。

策略逻辑

策略的日内执行逻辑是一个清晰的“停止-反转”(Stop-And-Reverse)系统。

每日开盘前:

  • 第一步: 设置好核心参数 N, K1, K2。
  • 第二步: 获取过去 N 天的日线数据。
  • 第三步: 根据公式计算出当日的 Range 值。

当日开盘后:

  • 第四步: 获取当日的开盘价 Open。
  • 第五步: 计算出当日的上轨 BuyLine 和下轨 SellLine。这两条线在当天是固定不变的。

盘中持续监控:

  • 第六步: 监控实时价格。

  • 做多信号: 当价格向上突破 BuyLine 时:

    • 如果当前持有空仓,则先平掉空仓,再开立多仓。

    • 如果当前无持仓,则直接开立多仓。

    • 如果当前已持有多仓,则无操作。

  • 做空信号: 当价格向下突破 SellLine 时:

    • 如果当前持有多仓,则先平掉多仓,再开立空仓。

    • 如果当前无持仓,则直接开立空仓。

    • 如果当前已持有空仓,则无操作。

策略代码

下面的代码示例展示了如何实现 Dual Thrust 交易策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *
"""
Dual Thrust是一个趋势跟踪系统
计算前N天的最高价-收盘价和收盘价-最低价。然后取这2N个价差的最大值,乘以k值。把结果称为触发值。
在今天的开盘,记录开盘价,然后在价格超过上轨(开盘+触发值)时马上买入,或者价格低于下轨(开盘-触发值)时马上卖空。
没有明确止损。这个系统是反转系统,也就是说,如果在价格超过(开盘+触发值)时手头有空单,则平空开多。
同理,如果在价格低于(开盘-触发值)时手上有多单,则平多开空。
选用了SHFE的rb2010 在2020-02-07 15:00:00 到 2020-04-15 15:00:00' 进行回测。
注意:
1:为回测方便,本策略使用了on_bar的一分钟来计算,实盘中可能需要使用on_tick。
2:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交
"""
# 策略中必须有init方法
def init(context):
# 设置要进行回测的合约(可以在掘金终端的仿真交易中查询标的代码)
context.symbol = 'SHFE.rb2010' # 订阅&交易标的, 此处订阅的是上期所的螺纹钢 2010
# 设置参数
context.N = 5
context.k1 = 0.2
context.k2 = 0.2
# 获取当前时间
time = context.now.strftime('%H:%M:%S')
# 如果策略执行时间点是交易时间段,则直接执行algo定义buy_line和sell_line,以防直接进入on_bar()导致context.buy_line和context.sell_line未定义
if '09:00:00' < time < '15:00:00' or '21:00:00' < time < '23:00:00':
algo(context)
# 如果是交易时间段,等到开盘时间确保进入algo()
schedule(schedule_func = algo, date_rule = '1d', time_rule = '09:00:00')
schedule(schedule_func = algo, date_rule = '1d', time_rule = '21:00:00')
# 只需要最新价,所以只需要订阅一个, 如果用tick,次数太多,用一分钟线代替
subscribe(symbols=context.symbol, frequency='60s', count = 1)
def algo(context):
# 取历史数据
data = history_n(symbol=context.symbol, frequency='1d', end_time=context.now,
fields='symbol,open,high,low,close', count=context.N + 1, df=True)
# 取开盘价
# 回测模式下,开盘价可以直接用history_n取到
if context.mode == 2:
# 获取当天的开盘价
current_open = data['open'].loc[context.N]
# 去掉当天的实时数据
data.drop(context.N, inplace = True)
# 如果是实时模式,开盘价需要用current取到
else:
# 获取当天的开盘价
current_open = current(context.symbol)[0]['open']
# 计算Dual Thrust 的上下轨
HH = data['high'].max()
HC = data['close'].max()
LC = data['close'].min()
LL = data['low'].min()
range = max(HH - LC, HC - LL)
context.buy_line = current_open + range * context.k1 # 上轨
context.sell_line = current_open - range * context.k2 # 下轨
def on_bar(context, bars):
# 取出订阅的这一分钟的bar
bar = bars[0]
# 取出买卖线
buy_line = context.buy_line
sell_line = context.sell_line
# 获取现有持仓
position_long = context.account().position(symbol=context.symbol, side=PositionSide_Long)
position_short = context.account().position(symbol=context.symbol, side=PositionSide_Short)
# 交易逻辑部分
# 如果超过range的上界
if bar.close > buy_line:
if position_long: # 已经持有多仓,直接返回
return
elif position_short: # 已经持有空仓,平仓再做多。
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
print('市价单平空仓', context.symbol)
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
print('市价单开多仓', context.symbol)
else: # 没有持仓时,市价开多。
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
print('市价单开多仓', context.symbol)
# 如果低于range的下界
elif bar.close < sell_line:
if position_long: # 已经持有多仓, 平多再开空。
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
order_type=OrderType_Market, position_effect=PositionEffect_Close)
print('市价单平多仓', context.symbol)
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
print('市价单开空仓', context.symbol)
elif position_short: # 已经持有空仓,直接返回。
return
else: # 没有持仓,直接开空
order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell,
order_type=OrderType_Market, position_effect=PositionEffect_Open)
print('市价单开空仓', context.symbol)
if __name__ == '__main__':
'''
strategy_id策略ID,由系统生成
filename文件名,请与本文件名保持一致
mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
token绑定计算机的ID,可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
'''
run(strategy_id='strategy_id',
filename='main.py',
mode=MODE_BACKTEST,
token='token_id',
backtest_start_time='2020-02-07 15:00:00',
backtest_end_time='2020-04-15 15:00:00',
backtest_initial_cash= 30000,
backtest_commission_ratio=0.0001,
backtest_slippage_ratio=0.0001)