问题描述
代码:
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 的底层算法与国内炒股软件(如通达信、同花顺)的指标算法存在本质差异。
主要差异原因分析
-
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权重分配不完全一致。 -
J 值的缺失:
TA-Lib 的STOCH函数只返回 K 和 D 两个值,不计算 J 值。通达信中 J 值的标准公式是J = 3 * K - 2 * D。 -
数据预热长度(截断效应):
通达信的指标是从股票上市第一天开始递推计算的。你的代码中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 值就能与通达信客户端显示的数据精确对齐(误差通常在小数点后两位以内)。