问题描述
解决方案
这是一个基于 QMT Python API 编写的 唐奇安通道(Donchian Channel)突破策略。
策略逻辑
- 计算指标:
- 上轨(Upper Band):过去 $N$ 根 K 线的最高价的最大值(不包含当前 K 线)。
- 下轨(Lower Band):过去 $N$ 根 K 线的最低价的最小值(不包含当前 K 线)。
- 买入信号:当当前 K 线收盘价突破上轨,且当前无持仓时,买入。
- 卖出信号:当当前 K 线收盘价跌破下轨,且当前有持仓时,卖出平仓。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数和账户
"""
# --- 策略参数设置 ---
ContextInfo.N = 20 # 唐奇安通道周期 (通常为20)
ContextInfo.stock_code = '600000.SH' # 标的代码 (示例:浦发银行)
ContextInfo.period = '1d' # 运行周期 (日线)
ContextInfo.amount = 1000 # 每次买入数量 (股)
ContextInfo.account_id = '666666' # 【重要】请替换为您的实际资金账号
ContextInfo.account_type = 'STOCK' # 账号类型:'STOCK'股票, 'FUTURE'期货
# --- 系统设置 ---
# 设定股票池
ContextInfo.set_universe([ContextInfo.stock_code])
# 绑定交易账号
ContextInfo.set_account(ContextInfo.account_id)
def handlebar(ContextInfo):
"""
K线逐根运行函数
"""
# 获取当前K线索引
index = ContextInfo.barpos
# 如果K线数量不足以计算周期,直接返回
if index < ContextInfo.N:
return
# --- 1. 获取行情数据 ---
# 获取过去 N+1 根K线的数据 (包含当前K线和过去N根)
# count = N + 1,是为了取过去N根计算通道,第N+1根(即当前根)用于比较价格
data = ContextInfo.get_market_data_ex(
['high', 'low', 'close'],
[ContextInfo.stock_code],
period=ContextInfo.period,
count=ContextInfo.N + 1,
dividend_type='front' # 前复权
)
# 数据校验
if ContextInfo.stock_code not in data:
return
df = data[ContextInfo.stock_code]
# 再次确保数据长度足够
if len(df) < ContextInfo.N + 1:
return
# --- 2. 计算唐奇安通道 ---
# 提取用于计算通道的历史数据 (排除当前最新的一根K线,避免未来函数)
history_highs = df['high'].iloc[:-1]
history_lows = df['low'].iloc[:-1]
# 获取当前最新价格
current_close = df['close'].iloc[-1]
current_time = df.index[-1]
# 计算上轨和下轨
upper_band = history_highs.max()
lower_band = history_lows.min()
# --- 3. 绘图 (可选,用于回测界面展示) ---
ContextInfo.paint('Upper_Band', upper_band, -1, 0, 'red')
ContextInfo.paint('Lower_Band', lower_band, -1, 0, 'green')
# --- 4. 获取当前持仓状态 ---
# 注意:回测模式和实盘模式获取持仓的方式略有不同,这里使用通用接口
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
current_pos = 0
for pos in positions:
if pos.m_strInstrumentID == ContextInfo.stock_code:
current_pos = pos.m_nVolume
break
# --- 5. 交易逻辑 ---
# 信号1:突破上轨 -> 买入
if current_close > upper_band and current_pos == 0:
print(f"[{current_time}] 价格 {current_close} 突破上轨 {upper_band},触发买入")
# 下单函数 passorder
# 23: 买入, 1101: 单股单账号, 5: 最新价
passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.amount, ContextInfo)
# 信号2:跌破下轨 -> 卖出
elif current_close < lower_band and current_pos > 0:
print(f"[{current_time}] 价格 {current_close} 跌破下轨 {lower_band},触发卖出")
# 下单函数 passorder
# 24: 卖出, 1101: 单股单账号, 5: 最新价
passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, current_pos, ContextInfo)
代码关键点说明
-
数据获取 (
get_market_data_ex):- 我们获取
N + 1条数据。 - 使用
iloc[:-1]切片来获取过去 N 天的数据计算通道。这是为了避免“未来函数”,即不能用今天的最高价来判断今天是否突破了今天的最高价(这在逻辑上是死循环),而是要看今天是否突破了过去 N 天的最高价。
- 我们获取
-
交易函数 (
passorder):23代表股票买入,24代表股票卖出。1101代表单账号、按股数下单。5代表使用最新价下单(市价成交的模拟)。- 注意:在实盘中,请确保
ContextInfo.account_id填写正确。
-
绘图 (
paint):- 代码中包含了
ContextInfo.paint函数,这样你在 QMT 的回测界面运行策略时,可以直接在 K 线图上看到红色的上轨和绿色的下轨,方便验证策略逻辑。
- 代码中包含了
如何使用
- 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略。
- 将上述代码复制粘贴进去。
- 修改
init函数中的ContextInfo.account_id为你的真实或模拟账号。 - 点击【运行】或【回测】即可。
Q&A
Q: 如何将此策略改为期货策略?
A: 需要修改以下几点:
ContextInfo.account_type改为'FUTURE'。passorder的操作类型代码需要修改:开多(0), 平多(6), 开空(3), 平空(9)。- 逻辑判断中需要增加做空的逻辑(跌破下轨开空,突破上轨平空)。
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版接口,返回的数据结构更清晰(字典包含 DataFrame),且支持更多参数配置(如复权方式、停牌填充等),处理效率更高。
Q: 这里的 N=20 是什么意思?
A: 这是唐奇安通道的经典参数,代表过去20个交易日(约一个月)的价格极值。你可以根据需要修改为 10、55 等其他数值。