3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
Dual Thrust 是一种经典的日内趋势跟踪策略,由 Michael Chalek 开发。它通过计算过去 N 天的最高价(High)、最低价(Low)和收盘价(Close)来构建一个震荡区间(Range),并结合当天的开盘价(Open)来确定上轨(Buy Line)和下轨(Sell Line)。
以下是在 JoinQuant 聚宽平台上实现的 Dual Thrust 期货策略代码。
# -*- coding: utf-8 -*-
from jqdata import *
def initialize(context):
"""
初始化函数,设定基准、手续费、全局变量等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定操作的标的:沪深300股指期货主力连续合约
g.security = 'IF9999.CCFX'
# 设定期货账户(必须设定,否则默认是股票账户无法交易期货)
set_subportfolios([SubPortfolioConfig(cash=context.portfolio.starting_cash, type='index_futures')])
# 设定期货交易手续费(以中金所为例,平今仓手续费较高,这里简化设置)
set_order_cost(OrderCost(open_commission=0.000023, close_commission=0.000023, close_today_commission=0.0023, min_commission=0), type='index_futures')
# 策略参数
g.N = 5 # 回溯天数
g.k1 = 0.5 # 上轨系数
g.k2 = 0.5 # 下轨系数
# 全局变量记录轨道
g.range = 0
g.buy_line = 0
g.sell_line = 0
# 每天开盘前运行,计算Range
run_daily(calculate_range, time='before_open')
def calculate_range(context):
"""
每天开盘前计算过去N天的Range
"""
# 获取过去N天的历史数据(不包含今天)
# fields: high, low, close
hist = attribute_history(g.security, g.N, '1d', ['high', 'low', 'close'], skip_paused=True)
if len(hist) < g.N:
return
# 计算四个关键价格
HH = hist['high'].max()
HC = hist['close'].max()
LC = hist['close'].min()
LL = hist['low'].min()
# 计算Range
# Range = Max(HH-LC, HC-LL)
g.range = max(HH - LC, HC - LL)
# 打印日志方便调试
# log.info("今日 Range: %.2f" % g.range)
def handle_data(context, data):
"""
盘中运行函数,每分钟执行一次(分钟回测模式下)
"""
security = g.security
# 获取当前时间,避免在非交易时间段产生信号(如集合竞价期间)
current_dt = context.current_dt
# 获取当天的开盘价
# 注意:get_current_data() 获取的是当天的快照数据
current_data = get_current_data()
day_open = current_data[security].day_open
# 如果开盘价为nan(尚未开盘),则直接返回
if np.isnan(day_open):
return
# 计算上下轨
# 上轨 = TodayOpen + K1 * Range
# 下轨 = TodayOpen - K2 * Range
g.buy_line = day_open + g.k1 * g.range
g.sell_line = day_open - g.k2 * g.range
# 获取当前最新价格
current_price = data[security].close
# 获取当前持仓情况
# long_positions 和 short_positions 是字典,key是标的代码
long_pos = context.portfolio.long_positions[security]
short_pos = context.portfolio.short_positions[security]
# 交易逻辑
# 1. 突破上轨,做多
if current_price > g.buy_line:
# 如果持有空单,先平空
if short_pos.total_amount > 0:
order_target(security, 0, side='short')
log.info("价格突破上轨,平空单")
# 如果没有多单,则开多
if long_pos.total_amount == 0:
# 全仓开多(这里简单演示,实际需考虑资金管理)
# 使用 order_value 开仓,注意期货保证金
# 这里简单开1手演示
order(security, 1, side='long')
log.info("价格突破上轨,开多单,价格: %.2f, 上轨: %.2f" % (current_price, g.buy_line))
# 2. 跌破下轨,做空
elif current_price < g.sell_line:
# 如果持有多单,先平多
if long_pos.total_amount > 0:
order_target(security, 0, side='long')
log.info("价格跌破下轨,平多单")
# 如果没有空单,则开空
if short_pos.total_amount == 0:
# 开1手空单
order(security, 1, side='short')
log.info("价格跌破下轨,开空单,价格: %.2f, 下轨: %.2f" % (current_price, g.sell_line))
def after_trading_end(context):
"""
收盘后运行
"""
pass
账户类型设置:
在 initialize 中必须使用 set_subportfolios 将账户类型设置为 index_futures(金融期货)或 futures(商品期货),否则默认是股票账户,无法进行期货开平仓操作。
Range 的计算:
使用了 attribute_history 获取过去 g.N 天的日线数据。这个计算放在 run_daily(calculate_range, time='before_open') 中,确保每天开盘前更新一次震荡幅度。
上下轨的动态计算:
Dual Thrust 的核心在于轨道是基于当天开盘价浮动的。在 handle_data 中,我们通过 get_current_data()[security].day_open 获取当天的开盘价。
注意:在回测中,分钟级别的 handle_data 能够获取到当天的 day_open。
交易执行:
order(security, amount, side='long'/'short') 进行下单。side='long' 表示多头方向(买入开多或卖出平多)。side='short' 表示空头方向(卖出开空或买入平空)。标的选择:
代码中使用 IF9999.CCFX(沪深300主力连续合约)。在实际回测中,主力连续合约是由聚宽根据持仓量拼接而成的虚拟合约,适合策略研究。
Q: 如何修改策略以交易商品期货(如螺纹钢)?
A: 将 g.security 修改为对应的商品期货主力合约代码,例如 'RB9999.XSGE'。同时,建议在 initialize 中将 set_subportfolios 的 type 改为 'futures',并根据该品种调整 set_order_cost 中的手续费率。
Q: 为什么使用 get_current_data() 获取开盘价而不是 data 对象?
A: handle_data 中的 data 对象主要用于获取上一分钟(或单位时间)的收盘价、高低价等历史切片数据。而当天的开盘价(Open)属于当天的属性,通过 get_current_data()[security].day_open 获取最为准确和方便。
Q: K1 和 K2 参数如何设置?
A: K1 和 K2 是策略的敏感度参数。
Q: 策略是否包含止损?
A: 上述基础代码是纯粹的翻单策略(Reverse Strategy),即持有直到反向信号出现才平仓反手。如果需要止损,可以在 handle_data 中加入逻辑:判断当前价格与开仓均价(context.portfolio.long_positions[security].avg_cost)的偏差,超过一定比例则强制平仓。