🚀 AI 一键生成 qmt 策略代码

QMT 策略实战:如何编写 MACD 与 KDJ 指标共振的买入信号?

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

问题描述

帮我写一个 MACD 和 KDJ 同时金叉的策略

解决方案

这是一个基于 QMT 平台的 MACD 和 KDJ 双指标共振(同时金叉) 策略。

策略逻辑说明

  1. 核心思想
    • MACD (趋势指标):代表中长期趋势。当 DIF 上穿 DEA 时(金叉),视为趋势转强信号。
    • KDJ (摆动指标):代表短期超买超卖。当 K 线 上穿 D 线时(金叉),视为短期买入信号。
    • 共振买入:当两者在同一根 K 线(例如同一天)同时发生金叉时,意味着短期爆发力与中长期趋势形成共振,可靠性理论上高于单一指标。
  2. 买入条件
    • MACD 发生金叉(当前 DIF > DEA 且 上一周期 DIF <= 上一周期 DEA)。
    • KDJ 发生金叉(当前 K > D 且 上一周期 K <= 上一周期 D)。
  3. 卖出条件
    • 为防止风险,当任一指标出现死叉(MACD 死叉 或 KDJ 死叉)时,执行平仓。

策略代码

# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import talib

def init(ContextInfo):
    # 1. 设置资金账号 (请替换为您自己的资金账号)
    ContextInfo.accID = 'YOUR_ACCOUNT_ID'
    ContextInfo.set_account(ContextInfo.accID)
    
    # 2. 设置股票池 (示例:平安银行)
    ContextInfo.target_list = ['000001.SZ', '600000.SH']
    ContextInfo.set_universe(ContextInfo.target_list)
    
    # 3. 设置策略运行周期,这里以日线为例
    ContextInfo.period = '1d'
    
    # 4. 设置一些参数
    ContextInfo.macd_fast = 12
    ContextInfo.macd_slow = 26
    ContextInfo.macd_signal = 9
    ContextInfo.kdj_n = 9
    ContextInfo.kdj_m1 = 3
    ContextInfo.kdj_m2 = 3

def handlebar(ContextInfo):
    # 获取当前设置的股票池
    stock_list = ContextInfo.get_universe()
    
    # 获取当前 K 线位置,如果是回测,barpos 会随时间递增
    index = ContextInfo.barpos
    
    # 获取当前时间,用于日志输出
    realtime = ContextInfo.get_bar_timetag(index)
    date_str = timetag_to_datetime(realtime, '%Y-%m-%d')
    
    # 遍历每只股票进行计算
    for stock in stock_list:
        # 1. 获取历史行情数据
        # 为了计算 MACD 和 KDJ,我们需要足够多的历史数据,这里取 100 根
        # 注意:get_market_data_ex 返回的是 dict {code: dataframe}
        data_map = ContextInfo.get_market_data_ex(
            ['open', 'high', 'low', 'close'], 
            [stock], 
            period=ContextInfo.period, 
            count=100, 
            dividend_type='front' # 前复权
        )
        
        if stock not in data_map:
            continue
            
        df = data_map[stock]
        
        # 数据长度不足,无法计算指标,跳过
        if len(df) < 30:
            continue
            
        # 2. 计算 MACD 指标
        close_prices = df['close'].values
        # 使用 talib 计算 MACD
        # diff 对应 DIF, dea 对应 DEA, macd 对应 MACD柱
        diff, dea, macd = talib.MACD(
            close_prices, 
            fastperiod=ContextInfo.macd_fast, 
            slowperiod=ContextInfo.macd_slow, 
            signalperiod=ContextInfo.macd_signal
        )
        
        # 3. 计算 KDJ 指标
        # 由于 talib 的 STOCH 计算方式与国内软件(如通达信)略有差异,
        # 这里使用自定义函数计算符合国内习惯的 KDJ
        k_vals, d_vals, j_vals = calc_kdj_china(df, ContextInfo.kdj_n, ContextInfo.kdj_m1, ContextInfo.kdj_m2)
        
        # 4. 获取当前和上一周期的指标值
        # -1 代表当前最新一根 K 线,-2 代表上一根
        curr_diff, prev_diff = diff[-1], diff[-2]
        curr_dea, prev_dea = dea[-1], dea[-2]
        
        curr_k, prev_k = k_vals[-1], k_vals[-2]
        curr_d, prev_d = d_vals[-1], d_vals[-2]
        
        # 5. 判断金叉与死叉
        # MACD 金叉: 上一刻 DIF <= DEA, 当前 DIF > DEA
        macd_golden_cross = (prev_diff <= prev_dea) and (curr_diff > curr_dea)
        # MACD 死叉
        macd_dead_cross = (prev_diff >= prev_dea) and (curr_diff < curr_dea)
        
        # KDJ 金叉: 上一刻 K <= D, 当前 K > D
        kdj_golden_cross = (prev_k <= prev_d) and (curr_k > curr_d)
        # KDJ 死叉
        kdj_dead_cross = (prev_k >= prev_d) and (curr_k < curr_d)
        
        # 6. 获取当前持仓
        position = get_position(ContextInfo, stock)
        
        # 7. 交易逻辑
        
        # 买入逻辑:MACD 和 KDJ 同时金叉 (双金叉共振)
        if macd_golden_cross and kdj_golden_cross:
            if position == 0:
                # 全仓买入 (示例逻辑,实际请做好资金管理)
                # 这里简单演示,每次买入 1000 股
                order_shares(stock, 1000, 'fix', df['close'].iloc[-1], ContextInfo, ContextInfo.accID)
                print(f"{date_str} [买入] {stock}: MACD与KDJ同时金叉")
        
        # 卖出逻辑:任一指标死叉则平仓 (止盈/止损)
        elif (macd_dead_cross or kdj_dead_cross):
            if position > 0:
                order_shares(stock, -position, 'fix', df['close'].iloc[-1], ContextInfo, ContextInfo.accID)
                print(f"{date_str} [卖出] {stock}: 出现死叉信号 (MACD:{macd_dead_cross}, KDJ:{kdj_dead_cross})")

def get_position(ContextInfo, stock_code):
    """
    获取某只股票的持仓数量
    """
    # 获取持仓对象列表
    positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
    for pos in positions:
        if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock_code:
            return pos.m_nVolume
    return 0

def calc_kdj_china(df, n=9, m1=3, m2=3):
    """
    计算符合国内行情的 KDJ 指标
    RSV = (CLOSE - LOW_N) / (HIGH_N - LOW_N) * 100
    K = (M1-1)/M1 * PREV_K + 1/M1 * RSV
    D = (M2-1)/M2 * PREV_D + 1/M2 * K
    J = 3*K - 2*D
    """
    high = df['high']
    low = df['low']
    close = df['close']
    
    # 计算 RSV
    low_list = low.rolling(window=n, min_periods=1).min()
    high_list = high.rolling(window=n, min_periods=1).max()
    rsv = (close - low_list) / (high_list - low_list) * 100
    # 处理除以0的情况
    rsv = rsv.fillna(0)
    
    # 计算 K, D, J
    k_vals = []
    d_vals = []
    
    # 初始值通常设为 50
    k = 50
    d = 50
    
    for i in range(len(rsv)):
        rsv_val = rsv.iloc[i]
        # 迭代计算
        k = (m1 - 1) / m1 * k + 1 / m1 * rsv_val
        d = (m2 - 1) / m2 * d + 1 / m2 * k
        k_vals.append(k)
        d_vals.append(d)
        
    k_series = pd.Series(k_vals, index=df.index)
    d_series = pd.Series(d_vals, index=df.index)
    j_series = 3 * k_series - 2 * d_series
    
    return k_series.values, d_series.values, j_series.values

关键点解析

  1. 数据获取 (get_market_data_ex)
    • 使用了 get_market_data_ex 接口,这是 QMT 推荐的高效数据接口。
    • count=100:为了保证 MACD(需要较长周期平滑)和 KDJ 计算准确,至少需要获取 100 根 K 线的数据。
  2. 指标计算
    • MACD:直接使用了 QMT 内置的 talib 库,计算速度快且标准。
    • KDJ:为了匹配国内行情软件(如通达信、同花顺)的算法,我手写了一个 calc_kdj_china 函数。talib 自带的 STOCH 函数计算逻辑与国内略有不同,直接使用可能会导致信号对不上。
  3. 共振逻辑
    • 代码中使用了 if macd_golden_cross and kdj_golden_cross: 来判断。
    • 注意:严格要求“同一天”双金叉在实际行情中出现的频率较低。如果您发现买入信号太少,可以放宽条件,例如:MACD 处于金叉状态(DIF>DEA)且 KDJ 刚刚金叉
  4. 交易执行
    • 使用了 order_shares 进行下单。
    • 使用了 get_trade_detail_data 来查询当前持仓,确保不会重复买入或卖空。

如何使用

  1. 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
  2. 新建一个 Python 策略。
  3. 将上述代码完整复制进去。
  4. 修改 ContextInfo.accID 为您的实际资金账号。
  5. 点击【编译】检查无误后,可以进行【回测】或【运行】。

Q&A

Q: 为什么 KDJ 指标不用 talib.STOCH?
A: talib.STOCH 是国际标准算法,而国内 A 股软件普遍采用的是一种平滑移动平均算法(迭代法)。直接用 talib 算出来的数值会和您在看盘软件上看到的不一样,导致信号偏差。代码中提供的 calc_kdj_china 函数模拟了国内软件的算法。

Q: 策略没有信号怎么办?
A: "同时金叉"是一个非常严格的条件。如果回测没有交易,建议放宽条件。例如:只要 MACD 趋势向上(DIF > DEA),此时 KDJ 发生金叉即可买入。您可以将买入判断修改为:

# 宽松版买入逻辑
if (curr_diff > curr_dea) and kdj_golden_cross:
    # 买入...

Q: 如何在回测中查看买卖点?
A: 代码中包含了 print 语句,回测时请查看底部的“日志”窗口,或者在图表上右键选择“叠加模型”来查看信号。