🚀 AI 一键生成 qmt 策略代码

QMT 量化交易:如何实现 KDJ 超买超卖策略?

qmt | 发布时间: 2025/11/26
以下内容由 EasyQuant 生成。

问题描述

编写一个 KDJ 指标的量化交易策略,当 J 值低于20时买入,高于80时卖出。

解决方案

这是一个基于 QMT 平台 Python API 编写的 KDJ 指标量化策略。

策略逻辑

  1. 指标计算:计算 KDJ 指标(N=9, M1=3, M2=3)。
    • $RSV = (收盘价 - N日内最低价) / (N日内最高价 - N日内最低价) \times 100$
    • $K = 2/3 \times 前一日K + 1/3 \times 当日RSV$
    • $D = 2/3 \times 前一日D + 1/3 \times 当日K$
    • $J = 3K - 2D$
  2. 买入条件:当 $J$ 值小于 20 时,认为超卖,执行买入(满仓)。
  3. 卖出条件:当 $J$ 值大于 80 时,认为超买,执行卖出(空仓)。

策略代码

# -*- 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),执行卖出。")

代码使用说明

  1. 编码格式:代码第一行 # -*- coding: gbk -*- 是必须的,因为 QMT 的 Python 编辑器默认使用 GBK 编码,防止中文注释乱码。
  2. 账号设置:请务必将 init 函数中的 YOUR_ACCOUNT_ID 替换为您实际的资金账号(字符串格式)。
  3. 数据获取:使用了 get_market_data_ex 接口,这是 QMT 推荐的高效数据接口。
  4. 指标计算:代码中手动实现了 KDJ 算法,使用了 Pandas 的 ewm (指数加权移动平均) 来模拟国内通用的 SMA 迭代算法,确保计算结果与行情软件一致。
  5. 交易函数:使用了 order_target_percent
    • 当 J < 20 时,目标仓位设为 1.0(100%),即全仓买入。
    • 当 J > 80 时,目标仓位设为 0.0(0%),即清仓卖出。
    • 这种方式比单纯的 buy/sell 更容易管理仓位,避免重复下单。
  6. 运行模式
    • 回测:在策略编辑器中点击“回测”,设置好时间区间和基准即可。
    • 实盘:在策略编辑器中点击“运行”,或者在“模型交易”中加载此策略。实盘时,handlebar 会随行情 Tick 更新,但交易信号通常基于 K 线数据。

常见问题 Q&A

Q: 为什么 J 值计算出来和看盘软件上不一样?
A: KDJ 的初始值(第一天的数据)处理方式不同会导致前期数据有差异,但随着数据量增加,差异会迅速收敛。此外,国内软件通常使用 SMA(权重为1/3的移动平均),代码中已通过 ewm(alpha=1/3) 进行了对齐。

Q: 如何只在 K 线走完那一刻下单(避免盘中信号闪烁)?
A: 可以在 handlebar 中添加判断。QMT 的机制是实盘中最后一根 K 线每个 Tick 都会触发 handlebar。如果希望 K 线收盘确认后再发单,可以使用 ContextInfo.is_new_bar() 判断是否是新的一根 K 线开始,然后基于 iloc[-2] (上一根K线) 的 J 值进行交易;或者使用 passorder 配合 quickTrade=0 参数。上述代码使用的是实时信号(盘中 J 值触及即触发)。

Q: 如何交易多只股票?
A: 可以在 init 中设置 ContextInfo.set_universe(['600000.SH', '000001.SZ']),然后在 handlebar 中遍历 ContextInfo.get_universe(),对每只股票分别获取数据和计算指标。