问题描述
解决方案
这是一个非常经典的趋势跟随策略。唐奇安通道(Donchian Channel)策略的核心逻辑是:当价格突破过去 N 天的最高价时买入,当价格跌破过去 N 天的最低价时卖出。
以下是基于 QMT Python API 实现的唐奇安通道突破策略。该代码适用于股票交易(做多逻辑),使用了 get_market_data_ex 获取数据,并使用 order_target_percent 进行仓位管理。
策略逻辑说明
- 上轨(Upper Band):过去
N根 K 线的最高价的最大值(不包含当前 K 线)。 - 下轨(Lower Band):过去
N根 K 线的最低价的最小值(不包含当前 K 线)。 - 买入信号:当前收盘价 > 上轨,且当前无持仓 -> 全仓买入。
- 卖出信号:当前收盘价 < 下轨,且当前有持仓 -> 清仓卖出。
QMT 策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数和变量
"""
# 策略参数:唐奇安通道周期
ContextInfo.N = 20
# 设置交易账号(请替换为您真实的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.account_type = 'STOCK' # 股票账户
# 设置基准和回测参数(仅回测有效)
ContextInfo.set_account(ContextInfo.account_id)
# 设定股票池,这里默认取当前主图的品种
# 如果需要固定股票池,可以使用 ContextInfo.set_universe(['600000.SH'])
ContextInfo.stock_code = ContextInfo.stockcode + '.' + ContextInfo.market
print("策略初始化完成,交易品种: {}, 通道周期: {}".format(ContextInfo.stock_code, ContextInfo.N))
def handlebar(ContextInfo):
"""
K线逐根运行函数
"""
# 获取当前 K 线索引
index = ContextInfo.barpos
# 获取当前图表的周期
period = ContextInfo.period
# 确保有足够的数据计算通道(至少需要 N+1 根 K 线)
if index < ContextInfo.N:
return
# 获取历史行情数据
# 我们需要获取过去 N+1 根 K 线的数据,以计算前 N 天的高低点和当前的收盘价
# count = N + 1
data_df = ContextInfo.get_market_data_ex(
fields=['high', 'low', 'close'],
stock_code=[ContextInfo.stock_code],
period=period,
end_time='',
count=ContextInfo.N + 2 # 多取一点防止边界问题
)[ContextInfo.stock_code]
# 如果数据获取失败或长度不足,直接返回
if data_df is None or len(data_df) < ContextInfo.N + 1:
return
# --- 计算唐奇安通道 ---
# 注意:为了避免未来函数,计算通道时必须使用"过去"的数据
# data_df.iloc[-1] 是当前 K 线(正在走的或刚走完的)
# data_df.iloc[-2] 是上一根 K 线
# 获取用于计算通道的历史数据片段:从倒数第 N+1 根到倒数第 2 根
# 例如 N=20,我们取当前 K 线之前的 20 根 K 线
history_highs = data_df['high'].iloc[-(ContextInfo.N + 1):-1]
history_lows = data_df['low'].iloc[-(ContextInfo.N + 1):-1]
# 计算上轨:过去 N 根 K 线的最高价的最大值
donchian_upper = history_highs.max()
# 计算下轨:过去 N 根 K 线的最低价的最小值
donchian_lower = history_lows.min()
# 获取当前最新价格(收盘价)
current_price = data_df['close'].iloc[-1]
# --- 交易逻辑 ---
# 获取当前持仓信息
positions = ContextInfo.get_position()
holding_vol = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == ContextInfo.stock_code:
holding_vol = pos.m_nVolume
break
# 1. 买入逻辑:价格突破上轨,且当前空仓
if current_price > donchian_upper and holding_vol == 0:
print("时间: {} - 价格突破上轨 ({:.2f} > {:.2f}),触发买入".format(
timetag_to_datetime(ContextInfo.get_bar_timetag(index), '%Y-%m-%d %H:%M:%S'),
current_price, donchian_upper
))
# 全仓买入 (目标仓位 100%)
order_target_percent(ContextInfo.stock_code, 1.0, ContextInfo, ContextInfo.account_id)
# 2. 卖出逻辑:价格跌破下轨,且当前持仓
elif current_price < donchian_lower and holding_vol > 0:
print("时间: {} - 价格跌破下轨 ({:.2f} < {:.2f}),触发卖出".format(
timetag_to_datetime(ContextInfo.get_bar_timetag(index), '%Y-%m-%d %H:%M:%S'),
current_price, donchian_lower
))
# 清仓卖出 (目标仓位 0%)
order_target_percent(ContextInfo.stock_code, 0.0, ContextInfo, ContextInfo.account_id)
# --- (可选) 在图表上画线方便观察 ---
# 注意:paint 函数在回测和实盘图表上绘制
ContextInfo.paint('Upper_Band', donchian_upper, -1, 0, 'red')
ContextInfo.paint('Lower_Band', donchian_lower, -1, 0, 'green')
代码关键点解析
-
数据获取 (
get_market_data_ex):- 我们使用
count=ContextInfo.N + 2来获取最近的一小段数据。 - 防未来函数:在计算
donchian_upper和donchian_lower时,使用了切片.iloc[-(ContextInfo.N + 1):-1]。这表示我们只看当前 K 线之前的 N 根 K 线。如果包含了当前 K 线,那么当天的最高价创新高时,上轨也会同步升高,导致永远无法触发突破信号。
- 我们使用
-
交易函数 (
order_target_percent):- 这是一个非常方便的高级交易函数。
1.0代表调整仓位至总资产的 100%(全仓买入)。0.0代表调整仓位至 0%(清仓卖出)。- 它会自动计算需要买卖的股数,并处理买卖方向。
-
绘图 (
paint):- 代码最后添加了
paint函数,这样你在 QMT 界面上运行策略(或回测)时,可以直接在主图上看到红色的上轨和绿色的下轨,方便验证逻辑是否正确。
- 代码最后添加了
-
编码格式:
- 第一行
# -*- coding: gbk -*-是必须的,因为 QMT 的 Python 环境默认使用 GBK 编码,否则中文字符会乱码或报错。
- 第一行
如何在 QMT 中运行
- 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略。
- 将上述代码完整复制粘贴进去。
- 修改
init函数中的ContextInfo.account_id为你的实际账号(如果是回测则不需要修改,系统会自动分配虚拟账号)。 - 点击【编译】检查是否有语法错误。
- 在左侧选择一个股票(如 600000.SH),选择周期(如日线),点击【运行】或【回测】。
Q&A
Q: 这个策略可以用于期货吗?
A: 可以,但需要修改交易逻辑。目前的逻辑是股票的“做多/平仓”。如果是期货,需要增加“开空/平空”的逻辑(即价格跌破下轨开空,突破上轨平空)。同时需要将 order_target_percent 替换为 buy_open, sell_close, sell_open, buy_close 等期货专用指令。
Q: 为什么计算通道时要排除当前 K 线?
A: 唐奇安通道的定义是基于“过去 N 天”的极值。如果在计算上轨时包含了今天的最高价,那么今天的价格永远不可能“突破”上轨(最多只能等于上轨),这样就永远不会产生买入信号。
Q: 如何修改通道的周期?
A: 修改 init 函数中的 ContextInfo.N = 20 即可。例如改为 ContextInfo.N = 55 就变成了著名的“海龟交易法则”中的长周期突破系统。