问题描述
解决方案
这是一个基于 QMT (Quantitative Trading Platform) 的 肯特纳通道 (Keltner Channel) 趋势突破交易策略。
策略逻辑说明
肯特纳通道(Keltner Channel)是一种趋势跟随指标,类似于布林带,但它使用 ATR (平均真实波幅) 来设定通道宽度,而不是标准差。
- 中轨 (Middle Line): 收盘价的指数移动平均线 (EMA),通常周期为 20。
- 上轨 (Upper Band): 中轨 + (ATR * 乘数)。
- 下轨 (Lower Band): 中轨 - (ATR * 乘数)。
- 交易信号:
- 买入: 当收盘价突破上轨时,视为上升趋势确立,全仓买入。
- 卖出: 当收盘价跌破下轨时,视为趋势反转,清仓卖出。
QMT 策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数和变量
"""
# 1. 设置策略参数
ContextInfo.period = 20 # EMA和ATR的计算周期
ContextInfo.multiplier = 2.0 # 通道宽度的ATR倍数
ContextInfo.stock_code = '600000.SH' # 示例标的:浦发银行
# 2. 设置股票池
ContextInfo.set_universe([ContextInfo.stock_code])
# 3. 设置资金账号 (请替换为您自己的实盘或模拟账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 4. 设定回测相关参数 (仅回测有效)
ContextInfo.set_commission(0, [0.0001, 0.0001, 0.0003, 0.0003, 0, 5]) # 设置费率
ContextInfo.capital = 1000000 # 初始资金
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 获取当前正在运行的K线索引
index = ContextInfo.barpos
# 获取当前图表的周期 (例如 '1d')
period = ContextInfo.period
# 获取当前主图代码
stock = ContextInfo.stock_code
# 1. 获取历史行情数据
# 我们需要足够的数据来计算EMA和ATR,所以取 period + 10 根K线
# 使用 get_market_data_ex 获取数据 (推荐方式)
data_len = ContextInfo.period + 20
market_data = ContextInfo.get_market_data_ex(
['open', 'high', 'low', 'close'],
[stock],
period=period,
count=data_len,
dividend_type='front' # 前复权
)
if stock not in market_data:
return
df = market_data[stock]
# 数据长度不足时不计算
if len(df) < ContextInfo.period + 1:
return
# 2. 计算肯特纳通道指标
# 2.1 计算中轨: EMA(Close, 20)
df['ema'] = df['close'].ewm(span=ContextInfo.period, adjust=False).mean()
# 2.2 计算 ATR (平均真实波幅)
# TR = Max(High-Low, Abs(High-PrevClose), Abs(Low-PrevClose))
df['prev_close'] = df['close'].shift(1)
df['h_l'] = df['high'] - df['low']
df['h_pc'] = abs(df['high'] - df['prev_close'])
df['l_pc'] = abs(df['low'] - df['prev_close'])
df['tr'] = df[['h_l', 'h_pc', 'l_pc']].max(axis=1)
# ATR通常使用简单移动平均(SMA)或EMA,这里使用SMA
df['atr'] = df['tr'].rolling(window=ContextInfo.period).mean()
# 2.3 计算上轨和下轨
df['upper'] = df['ema'] + (df['atr'] * ContextInfo.multiplier)
df['lower'] = df['ema'] - (df['atr'] * ContextInfo.multiplier)
# 获取最新一根K线的数据(用于判断信号)
current_close = df['close'].iloc[-1]
current_upper = df['upper'].iloc[-1]
current_lower = df['lower'].iloc[-1]
# 打印调试信息 (可选)
# print(f"时间: {df.index[-1]}, 收盘: {current_close:.2f}, 上轨: {current_upper:.2f}, 下轨: {current_lower:.2f}")
# 3. 获取当前持仓
# 注意:get_trade_detail_data 返回的是列表,需要遍历查找
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
current_holding = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock:
current_holding = pos.m_nVolume
break
# 4. 交易逻辑
# 信号生成:价格突破上轨买入,跌破下轨卖出
# 买入条件:收盘价 > 上轨 且 当前无持仓
if current_close > current_upper and current_holding == 0:
print(f"买入信号触发: 价格 {current_close} > 上轨 {current_upper}")
# 全仓买入 (目标仓位 100%)
order_target_percent(stock, 1.0, ContextInfo, ContextInfo.account_id)
# 卖出条件:收盘价 < 下轨 且 当前有持仓
elif current_close < current_lower and current_holding > 0:
print(f"卖出信号触发: 价格 {current_close} < 下轨 {current_lower}")
# 清仓卖出 (目标仓位 0%)
order_target_percent(stock, 0.0, ContextInfo, ContextInfo.account_id)
代码关键点解析
- 编码格式: 第一行
# -*- coding: gbk -*-是必须的,因为 QMT 的 Python 环境默认使用 GBK 编码,防止中文注释乱码。 - 数据获取 (
get_market_data_ex):- 这是 QMT 推荐的新版数据接口。
- 我们获取了
high,low,close用于计算 ATR 和 EMA。 dividend_type='front'设置为前复权,这对于技术指标计算非常重要,避免分红除权导致的价格跳空影响指标。
- 指标计算 (Pandas):
- EMA: 使用
df['close'].ewm(span=n).mean()计算指数移动平均。 - ATR: 手动计算 TR (True Range),然后使用
rolling(window=n).mean()计算 ATR。这样避免了依赖第三方库(如 TA-Lib),使代码兼容性更好。
- EMA: 使用
- 交易函数 (
order_target_percent):- 这是一个非常方便的高级函数。
1.0代表调整仓位至总资产的 100%(全仓买入)。0.0代表调整仓位至 0%(清仓卖出)。- 它会自动计算需要买入或卖出的股数,并处理下单逻辑。
如何使用
- 打开 QMT 客户端,进入 “模型研究” -> “策略编辑器”。
- 新建一个 Python 策略。
- 将上述代码复制粘贴到编辑器中。
- 修改账号: 将
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'中的'YOUR_ACCOUNT_ID'替换为你实际的资金账号。 - 回测: 点击编辑器上方的“回测”按钮,选择回测区间和周期(建议日线),即可查看策略表现。
- 实盘: 如果要在实盘运行,请确保在“模型交易”界面加载该策略,并将运行模式设置为“实盘”。
常见问题 (Q&A)
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 较新的接口,返回的数据结构更统一(Pandas DataFrame),且支持更丰富的参数(如复权方式、订阅开关),处理起来更方便。
Q: 如何修改通道的灵敏度?
A: 修改 init 函数中的 ContextInfo.period (周期) 和 ContextInfo.multiplier (倍数)。
- 增大
period或multiplier会使通道变宽,交易频率降低,过滤震荡,但可能滞后。 - 减小
period或multiplier会使通道变窄,交易频率增加,更敏感,但假信号可能增多。
Q: 这个策略可以用于期货吗?
A: 可以,但需要修改以下几点:
stock_code改为期货合约代码 (如IF00.IF)。order_target_percent在期货中可能不如buy_open/sell_close等函数精确,建议针对期货改写下单逻辑(涉及开平仓方向)。- 期货通常需要做空逻辑(价格跌破下轨开空),上述代码仅实现了股票的多头逻辑(跌破下轨平仓)。