🚀 AI 一键生成 qmt 策略代码

QMT 平台使用 TA-Lib 计算 KDJ 指标与通达信不一致的原因及 Python 实现方案

qmt | 发布时间: 2026/5/27
以下内容由 EasyQuant 生成。

问题描述

请问用TALIB获得的KDJ指标为什么与通达信下载的数据不一致?我的代码哪用的不对?

代码:

tadata_1m = C.get_market_data_ex(['close', 'high', 'low', 'open', 'volume'], [C.stock], period = '1m', count = 1000)[C.stock],

用 TALIB 获取 KD 值

tadata_1m['K'], tadata_1m['D'] = talib.STOCH(tadata_1m['high'], tadata_1m['low'], tadata_1m['close'], fastk_period=9, slowk_period=3, slowd_period=3)

解决方案

这是一个在量化开发中非常常见的问题。你在 QMT 中使用 TA-Lib 计算的 KDJ 与通达信(TDX)不一致,并不是你的代码写错了,而是因为 TA-Lib 的底层算法与国内炒股软件(如通达信、同花顺)的指标算法存在本质差异

主要差异原因分析

  1. SMA 平滑算法的定义不同
    通达信中的 KDJ 公式使用的是 SMA(X, N, M),其数学递推公式为:Y = (M * X + (N - M) * Y') / N(这实际上是一种带有权重的指数移动平均 EMA)。
    而 TA-Lib 的 STOCH 函数默认使用的是简单移动平均(Simple Moving Average, 权重均等),即使你修改 slowk_matype 参数,TA-Lib 内置的 EMA 算法也与通达信的 SMA 权重分配不完全一致。

  2. J 值的缺失
    TA-Lib 的 STOCH 函数只返回 K 和 D 两个值,不计算 J 值。通达信中 J 值的标准公式是 J = 3 * K - 2 * D

  3. 数据预热长度(截断效应)
    通达信的指标是从股票上市第一天开始递推计算的。你的代码中 count = 1000 只取了最近 1000 根 K 线。由于通达信的 SMA 算法具有长记忆性(依赖前一日的 K、D 值),初始值的差异会导致前几十根 K 线的指标值存在偏差。通常建议至少获取 250 根以上的数据来让指标“预热”收敛。

解决方案:使用 Python 手写通达信版 KDJ

为了在 QMT 中获得与通达信完全一致的 KDJ 数据,建议放弃 TA-Lib,直接使用 Pandas 和 NumPy 手写通达信的 SMA 算法。以下是完全对齐通达信的 Python 实现代码:

import pandas as pd
import numpy as np

def calc_tdx_kdj(df, n=9, m1=3, m2=3):
    """
    计算通达信版本的 KDJ 指标
    :param df: 包含 high, low, close 的 DataFrame
    :param n: RSV 的周期,默认 9
    :param m1: K 的 SMA 周期,默认 3
    :param m2: D 的 SMA 周期,默认 3
    """
    # 1. 计算 RSV
    low_list = df['low'].rolling(window=n, min_periods=1).min()
    high_list = df['high'].rolling(window=n, min_periods=1).max()
    rsv = (df['close'] - low_list) / (high_list - low_list) * 100
    rsv = rsv.fillna(0) # 避免除以0产生的NaN

    # 2. 定义通达信的 SMA 算法
    def tdx_sma(series, weight_n, weight_m):
        result = np.zeros_like(series)
        # KDJ 默认初始值为 50
        result[0] = 50.0 
        for i in range(1, len(series)):
            if pd.isna(series.iloc[i]):
                result[i] = result[i-1]
            else:
                result[i] = (weight_m * series.iloc[i] + (weight_n - weight_m) * result[i-1]) / weight_n
        return pd.Series(result, index=series.index)

    # 3. 计算 K, D, J
    df['K'] = tdx_sma(rsv, m1, 1)
    df['D'] = tdx_sma(df['K'], m2, 1)
    df['J'] = 3 * df['K'] - 2 * df['D']
    
    return df

# 在 QMT handlebar 中的使用示例
def handlebar(ContextInfo):
    if not ContextInfo.is_last_bar():
        return
        
    stock = ContextInfo.stockcode
    # 建议 count 稍微取大一点,比如 500 或 1000,让 EMA 算法充分收敛
    df = ContextInfo.get_market_data_ex(['close', 'high', 'low'], [stock], period='1m', count=1000)[stock]
    
    if len(df) > 0:
        # 计算 KDJ
        df = calc_tdx_kdj(df)
        
        # 打印最新一根 K 线的 KDJ 值
        latest_k = df['K'].iloc[-1]
        latest_d = df['D'].iloc[-1]
        latest_j = df['J'].iloc[-1]
        print(f"{stock} 最新 KDJ: K={latest_k:.2f}, D={latest_d:.2f}, J={latest_j:.2f}")

总结

使用上述 calc_tdx_kdj 函数替代 talib.STOCH,只要保证传入的 K 线数据长度足够(建议 count >= 300),计算出来的 K、D、J 值就能与通达信客户端显示的数据精确对齐(误差通常在小数点后两位以内)。