问题描述
解决方案
这是一个经典的 Dual Thrust 策略实现。Dual Thrust 是一种著名的趋势跟踪策略,它通过计算前 N 天的最高价、最低价和收盘价来确定一个波动区间(Range),然后结合当天的开盘价来确定上下轨。
策略逻辑
-
计算 Range (波动区间):
利用前 N 日的历史数据计算:- $HH$ = N日内最高价的最高值
- $HC$ = N日内收盘价的最高值
- $LC$ = N日内收盘价的最低值
- $LL$ = N日内最低价的最低值
- $Range = \max(HH - LC, HC - LL)$
-
确定上下轨:
- 上轨 (Upper Bound) = 当日开盘价 + $K_1 \times Range$
- 下轨 (Lower Bound) = 当日开盘价 - $K_2 \times Range$
-
交易信号:
- 当价格突破上轨时:做多(如果持有空单则平空开多)。
- 当价格跌破下轨时:做空(如果持有多单则平多开空)。
QMT Python 代码实现
以下代码适用于 期货 交易(因为包含做空逻辑)。如果您用于股票,需要去掉做空部分,仅保留买入和卖出逻辑。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
# --- 策略参数设置 ---
ContextInfo.N = 5 # 回溯天数,用于计算Range
ContextInfo.k1 = 0.5 # 上轨系数
ContextInfo.k2 = 0.5 # 下轨系数
ContextInfo.symbol = 'IF00.IF' # 交易标的,这里以股指期货主力合约为例
ContextInfo.account_id = 'YOUR_ACCOUNT_ID' # 请填写您的资金账号
# 设置股票池
ContextInfo.set_universe([ContextInfo.symbol])
# 设置交易账号
ContextInfo.set_account(ContextInfo.account_id)
# 初始化全局变量
ContextInfo.pos = 0 # 简单的持仓状态记录:0空仓,1多头,-1空头
def handlebar(ContextInfo):
# 获取当前K线位置,如果是回测模式,避免数据引用未来
index = ContextInfo.barpos
realtime = ContextInfo.get_bar_timetag(index)
# 1. 获取历史行情数据 (取过去 N+1 天,因为要包含昨天及之前的N天)
# 注意:我们需要的是"昨天"及之前的N天数据来计算Range,不包含"今天"
# period='1d' 获取日线
data = ContextInfo.get_market_data(
['high', 'low', 'close', 'open'],
stock_code=[ContextInfo.symbol],
period='1d',
count=ContextInfo.N + 1,
dividend_type='none'
)
# 检查数据是否足够
if ContextInfo.symbol not in data or len(data[ContextInfo.symbol]) < ContextInfo.N + 1:
return
df = data[ContextInfo.symbol]
# 2. 数据处理
# 截取前N天的数据(排除当前最新的这一根K线,因为最新的K线是今天正在走的)
# 假设df的最后一行是当前日期(实盘中通常是这样),我们需要取倒数第2行往前推N行
history_df = df.iloc[-(ContextInfo.N + 1):-1]
# 获取今天的开盘价 (df的最后一行是今天)
current_open = df.iloc[-1]['open']
# 获取当前最新价格
# 在实盘中,建议使用 get_full_tick 获取更实时的tick数据
current_price = 0
tick = ContextInfo.get_full_tick([ContextInfo.symbol])
if tick and ContextInfo.symbol in tick:
current_price = tick[ContextInfo.symbol]['lastPrice']
else:
# 如果获取不到tick,降级使用K线收盘价(回测时使用)
current_price = df.iloc[-1]['close']
# 3. 计算 Dual Thrust 的 Range
hh = history_df['high'].max() # N日High的最高价
hc = history_df['close'].max() # N日Close的最高价
lc = history_df['close'].min() # N日Close的最低价
ll = history_df['low'].min() # N日Low的最低价
# Range = Max(HH-LC, HC-LL)
r_range = max(hh - lc, hc - ll)
# 4. 计算上下轨
upper_bound = current_open + ContextInfo.k1 * r_range
lower_bound = current_open - ContextInfo.k2 * r_range
# 打印调试信息 (可选)
# print(f"Time: {realtime}, Price: {current_price}, Open: {current_open}, Range: {r_range}, Upper: {upper_bound}, Lower: {lower_bound}")
# 5. 交易逻辑
# 突破上轨,做多
if current_price > upper_bound:
if ContextInfo.pos != 1:
# 如果持有空单,先平空
if ContextInfo.pos == -1:
print(f"价格突破上轨 {upper_bound},平空")
buy_close_tdayfirst(ContextInfo.symbol, 1, 'LATEST', current_price, ContextInfo, ContextInfo.account_id)
# 开多
print(f"价格突破上轨 {upper_bound},开多")
buy_open(ContextInfo.symbol, 1, 'LATEST', current_price, ContextInfo, ContextInfo.account_id)
ContextInfo.pos = 1
# 跌破下轨,做空
elif current_price < lower_bound:
if ContextInfo.pos != -1:
# 如果持有多单,先平多
if ContextInfo.pos == 1:
print(f"价格跌破下轨 {lower_bound},平多")
sell_close_tdayfirst(ContextInfo.symbol, 1, 'LATEST', current_price, ContextInfo, ContextInfo.account_id)
# 开空
print(f"价格跌破下轨 {lower_bound},开空")
sell_open(ContextInfo.symbol, 1, 'LATEST', current_price, ContextInfo, ContextInfo.account_id)
ContextInfo.pos = -1
# 辅助函数:简单的下单封装 (QMT自带了 buy_open 等函数,这里直接调用即可)
# 注意:实盘中建议配合 get_trade_detail_data 来判断真实持仓,而不是仅依赖 ContextInfo.pos 变量
代码关键点说明:
-
数据获取 (
get_market_data):- 我们获取了
count = N + 1根K线。 - 关键逻辑:Dual Thrust 的 Range 是基于过去 N 天计算的,不包括今天。而今天的上下轨是基于今天的开盘价计算的。因此代码中
history_df取的是iloc[-(N+1):-1](过去N天),而current_open取的是iloc[-1](今天)。
- 我们获取了
-
实时价格 (
get_full_tick):- 在
handlebar中,如果是实盘运行,get_market_data获取的最新收盘价可能滞后(取决于刷新频率),使用get_full_tick获取最新的 Tick 数据(lastPrice)能更灵敏地触发突破信号。
- 在
-
交易函数:
- 使用了 QMT 提供的便捷期货交易函数:
buy_open: 买入开仓(做多)sell_close_tdayfirst: 卖出平仓(平多,优先平今)sell_open: 卖出开仓(做空)buy_close_tdayfirst: 买入平仓(平空,优先平今)
- 下单价格使用了
'LATEST'(最新价),您也可以改为'COMPETE'(对手价) 以提高成交率。
- 使用了 QMT 提供的便捷期货交易函数:
-
持仓管理 (
ContextInfo.pos):- 代码中使用了一个简单的变量
pos来记录策略状态。 - 注意:在实盘长期运行时,建议使用
get_trade_detail_data(ContextInfo.account_id, 'future', 'position')来查询真实的账户持仓,以防止策略重启后pos变量重置导致重复下单。
- 代码中使用了一个简单的变量
如何运行:
- 将代码复制到 QMT 的 Python 策略编辑器中。
- 修改
ContextInfo.account_id为您的真实资金账号。 - 修改
ContextInfo.symbol为您想交易的合约(如IF2312.IF)。 - 点击“编译”并运行。如果是回测,请确保已下载对应的日线数据。