本文将详细解析如何在迅投QMT量化平台使用Python实现一个基于KDJ随机指标的交易策略,涵盖指标的核心计算逻辑、超买超卖信号判断以及自动下单执行流程,并提供可直接运行的回测与实盘代码。
策略核心逻辑
本策略的核心思想是利用KDJ指标中的J值识别市场的超买与超卖状态,以此作为买卖依据。其具体逻辑如下:
- 指标计算:以经典参数(N=9, M1=3, M2=3)计算KDJ值。
RSV = (收盘价 - N日内最低价) / (N日内最高价 - N日内最低价) × 100
K = 2/3 × 前一日K值 + 1/3 × 当日RSV
D = 2/3 × 前一日D值 + 1/3 × 当日K值
J = 3 × K - 2 × D
- 买入信号:当
J值低于20时,判定市场处于超卖状态,策略将执行满仓买入。
- 卖出信号:当
J值高于80时,判定市场处于超买状态,策略将执行清仓卖出。
完整的策略代码实现
以下是为QMT平台编写的完整Python策略代码,你可以直接复制到策略编辑器中使用。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,策略启动时调用一次
"""
# 设置资金账号,请修改为您的实际账号
# 格式如:'6000000248'
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 策略参数设置
ContextInfo.N = 9 # 计算RSV的周期
ContextInfo.M1 = 3 # K值的平滑周期
ContextInfo.M2 = 3 # D值的平滑周期
# 设置交易标的,这里默认为当前主图显示的品种
# 如果需要固定品种,可以设置为 ContextInfo.stock_code = '600000.SH'
ContextInfo.stock_code = ContextInfo.stockcode + '.' + ContextInfo.market
print("策略初始化完成,交易标的:", ContextInfo.stock_code)
def get_kdj(df, N, M1, M2):
"""
计算KDJ指标的辅助函数
"""
# 计算 RSV
low_list = df['low'].rolling(window=N).min()
high_list = df['high'].rolling(window=N).max()
rsv = (df['close'] - low_list) / (high_list - low_list) * 100
# 填充NaN值,防止计算中断
rsv = rsv.fillna(50)
# 计算 K, D, J
# 注意:国内主流软件(如通达信、同花顺)的KDJ算法中,平滑因子通常为 1/M
# pandas的ewm中,alpha = 1/com + 1,这里使用 com = M-1 来近似 SMA 迭代逻辑
# K = (M1-1)/M1 * PrevK + 1/M1 * RSV => alpha = 1/M1
k = rsv.ewm(alpha=1/M1, adjust=False).mean()
d = k.ewm(alpha=1/M2, adjust=False).mean()
j = 3 * k - 2 * d
return k, d, j
def handlebar(ContextInfo):
"""
K线周期回调函数
"""
# 获取当前正在处理的K线索引
index = ContextInfo.barpos
# 获取历史行情数据,多取一些数据以保证指标计算准确
# 获取 Open, High, Low, Close
data_len = 100
df_dict = ContextInfo.get_market_data_ex(
['open', 'high', 'low', 'close'],
[ContextInfo.stock_code],
period=ContextInfo.period,
count=data_len,
dividend_type='follow' # 跟随主图复权
)
if ContextInfo.stock_code not in df_dict:
return
df = df_dict[ContextInfo.stock_code]
# 数据长度不足以计算指标时直接返回
if len(df) < ContextInfo.N + 2:
return
# 计算 KDJ
k, d, j = get_kdj(df, ContextInfo.N, ContextInfo.M1, ContextInfo.M2)
# 获取当前K线的 J 值
# 注意:在回测中,iloc[-1]是当前bar;在实盘中,iloc[-1]是最新的一根K线(可能未走完)
current_j = j.iloc[-1]
current_k = k.iloc[-1]
current_d = d.iloc[-1]
# 获取当前价格
current_price = df['close'].iloc[-1]
# 打印日志方便调试
# print(f"时间: {df.index[-1]}, K: {current_k:.2f}, D: {current_d:.2f}, J: {current_j:.2f}")
# --- 交易逻辑 ---
# 1. 买入信号:J < 20
if current_j < 20:
# 使用 order_target_percent 调整仓位到 100% (满仓买入)
# 如果已经是满仓,该函数不会重复下单
order_target_percent(ContextInfo.stock_code, 1.0, ContextInfo, ContextInfo.account_id)
# 只有在最后一根K线(实盘最新时刻)才打印信号,避免回测刷屏
if ContextInfo.is_last_bar():
print(f"触发买入信号 (J={current_j:.2f} < 20),执行买入。")
# 2. 卖出信号:J > 80
elif current_j > 80:
# 使用 order_target_percent 调整仓位到 0% (清仓卖出)
order_target_percent(ContextInfo.stock_code, 0.0, ContextInfo, ContextInfo.account_id)
if ContextInfo.is_last_bar():
print(f"触发卖出信号 (J={current_j:.2f} > 80),执行卖出。")
下图形象地展示了KDJ指标与股价的互动关系,有助于理解超买超卖区域:

关键代码解析与使用说明
-
编码与账号设置:
- 首行
# -*- coding: gbk -*-是必须的,用于兼容QMT Python编辑器的GBK编码环境,避免中文乱码。
- 重要:务必在
init函数中将YOUR_ACCOUNT_ID替换为你自己的真实资金账号(字符串格式)。
-
数据与指标计算:
- 代码中使用了QMT高效的
get_market_data_ex接口获取行情数据。
- 利用
Pandas库手动实现了KDJ算法。为了与国内主流交易软件(如通达信)的SMA(简单移动平均)迭代算法保持一致,这里使用ewm(指数加权移动平均)并设置alpha=1/3进行近似,确保计算出的K、D、J值具有可比性。如果你想深入学习数据分析技巧,可以探索我们社区整理的Python相关资料。
-
交易执行与仓位管理:
- 策略使用
order_target_percent函数进行交易,这是一种基于目标持仓比例的智能下单方式。
- 当
J < 20时,设置目标持仓比例为1.0(即100%),系统会自动买入至满仓。
- 当
J > 80时,设置目标持仓比例为0.0,系统会自动清仓所有持仓。
- 这种方法能有效避免在同一信号点上重复下单,简化了仓位管理逻辑。这本质上是一种基于特定市场状态(超买超卖)的固定算法规则。
-
运行模式:
- 回测:在QMT策略编辑器中点击“回测”按钮,设置好回测时间区间和基准对比即可进行历史业绩验证。
- 实盘:在策略编辑器中点击“运行”,或在“模型交易”界面加载此策略。在实盘模式下,
handlebar函数会随着行情Tick更新而触发,交易信号基于最新的K线数据生成。
常见问题与优化方向
Q:计算出的J值与行情软件显示有细微差异?
A:这通常是由于KDJ指标初始值(首根K线的K、D值)的设定方式不同导致的。随着计算数据的积累,这种差异会迅速收敛至可忽略的程度。代码中已对平滑算法进行了对齐处理。
Q:如何避免盘中信号闪烁,只在K线收盘时下单?
A:QMT实盘时,最后一根未完结的K线会随每个Tick触发handlebar。若希望基于已收盘的K线交易,可在代码中判断ContextInfo.is_new_bar(),若为新K线开始,则取上一根已收盘K线(iloc[-2])的J值作为交易信号依据。
Q:如何将此策略应用于多只股票?
A:可在init函数中使用ContextInfo.set_universe(['600000.SH', '000001.SZ'])设置股票池。随后在handlebar中遍历ContextInfo.get_universe()返回的列表,为每只股票分别获取数据、计算指标并执行独立的交易逻辑。