KDJ是短线交易者的利器。本文带你深入理解其计算原理,用Python从零实现,并识别关键交易信号。
一、KDJ指标是什么?
KDJ(随机指标)由George Lane在1950年代提出,是技术分析中最受欢迎的动量指标之一。
核心思想
KDJ通过比较收盘价与一定周期内价格范围的关系,来判断市场的超买超卖状态:
- • K线(快线):反映当前价格在近期区间中的相对位置
- • D线(慢线):K线的平滑移动平均,信号更稳定
- • J线(方向线):K与D的差值放大,反映趋势强度
取值范围与含义
| 区域 | 数值范围 | 市场含义 |
|---|
| 超买区 | K、D > 80 | 可能回调,考虑卖出 |
| 超卖区 | K、D < 20 | 可能反弹,考虑买入 |
| 徘徊区 | 20-80之间 | 趋势延续,观望为主 |
二、KDJ计算原理详解
计算公式
第一步:计算RSV(未成熟随机值)
$$
RSV = \frac{当日收盘价 - N日内最低价}{N日内最高价 - N日内最低价} \times 100
$$
第二步:计算K值
$$
K = \frac{2}{3} \times 前一日K + \frac{1}{3} \times 当日RSV
$$
第三步:计算D值
$$
D = \frac{2}{3} \times 前一日D + \frac{1}{3} \times 当日K
$$
第四步:计算J值
$$
J = 3K - 2D
$$
默认参数:N=9(周期),K和D的初始值通常设为50
三、Python实现KDJ指标
3.1 环境准备
pip install akshare pandas numpy matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple
3.2 从零实现KDJ计算
import pandas as pd
import numpy as np
import akshare as ak
def calculate_kdj(df, n=9, m1=3, m2=3):
"""
计算KDJ指标
Parameters:
df: DataFrame,包含'最高', '最低', '收盘'列
n: RSV计算周期,默认9
m1: K值平滑系数,默认3
m2: D值平滑系数,默认3
Returns:
DataFrame,新增K、D、J列
"""
df = df.copy()
# 计算N日内的最高价和最低价
df['low_n'] = df['最低'].rolling(window=n, min_periods=1).min()
df['high_n'] = df['最高'].rolling(window=n, min_periods=1).max()
# 计算RSV
df['rsv'] = (df['收盘'] - df['low_n']) / (df['high_n'] - df['low_n']) * 100
# 初始化K、D值
df['K'] = 50.0
df['D'] = 50.0
# 迭代计算K和D
for i in range(1, len(df)):
df.loc[df.index[i], 'K'] = (m1 - 1) / m1 * df['K'].iloc[i-1] + 1 / m1 * df['rsv'].iloc[i]
df.loc[df.index[i], 'D'] = (m2 - 1) / m2 * df['D'].iloc[i-1] + 1 / m2 * df['K'].iloc[i]
# 计算J值
df['J'] = 3 * df['K'] - 2 * df['D']
# 清理中间列
df = df.drop(['low_n', 'high_n', 'rsv'], axis=1)
return df
# 获取股票数据
print("正在获取股票数据...")
df = ak.stock_zh_a_hist(
symbol="600519", # 贵州茅台
period="daily",
start_date="20241001",
end_date="20260324",
adjust="qfq"
)
# 计算KDJ
df = calculate_kdj(df)
# 查看结果
print("\n最近5日KDJ数据:")
print(df[['日期', '收盘', 'K', 'D', 'J']].tail())
输出示例:
最近5日KDJ数据:
日期 收盘 K D J
118 2026-03-18 1590.00 75.234567 68.890123 88.923456
119 2026-03-19 1585.50 72.456789 70.078901 77.212345
120 2026-03-20 1595.00 76.123456 72.093827 84.182715
121 2026-03-21 1588.00 70.567890 71.585185 68.533300
122 2026-03-24 1592.50 73.456123 72.208831 75.950707
3.3 使用TA-Lib快速计算(可选)
# 安装:pip install ta-lib
import talib
def calculate_kdj_talib(df, n=9, m1=3, m2=3):
"""使用TA-Lib计算KDJ"""
# TA-Lib的STOCH函数直接返回K和D
df['K'], df['D'] = talib.STOCH(
df['最高'], df['最低'], df['收盘'],
fastk_period=n,
slowk_period=m1,
slowk_matype=0,
slowd_period=m2,
slowd_matype=0
)
df['J'] = 3 * df['K'] - 2 * df['D']
return df
四、KDJ交易信号识别
4.1 金叉与死叉信号
def find_cross_signals(df):
"""
识别KDJ金叉和死叉信号
金叉:K线上穿D线,买入信号
死叉:K线下穿D线,卖出信号
"""
df = df.copy()
# 计算K与D的差值
df['kd_diff'] = df['K'] - df['D']
# 金叉:前一日K<D,当日K>D
df['golden_cross'] = (df['kd_diff'] > 0) & (df['kd_diff'].shift(1) <= 0)
# 死叉:前一日K>D,当日K<D
df['death_cross'] = (df['kd_diff'] < 0) & (df['kd_diff'].shift(1) >= 0)
# 清理中间列
df = df.drop('kd_diff', axis=1)
return df
# 应用信号识别
df = find_cross_signals(df)
# 查看最近的金叉信号
print("\n最近的金叉信号(买入):")
golden = df[df['golden_cross']][['日期', '收盘', 'K', 'D', 'J']].tail(5)
print(golden)
print("\n最近的死叉信号(卖出):")
death = df[df['death_cross']][['日期', '收盘', 'K', 'D', 'J']].tail(5)
print(death)
4.2 超买超卖信号
def find_extreme_signals(df, overbought=80, oversold=20):
"""
识别超买超卖信号
"""
df = df.copy()
# 超买:K、D都大于80
df['overbought'] = (df['K'] > overbought) & (df['D'] > overbought)
# 超卖:K、D都小于20
df['oversold'] = (df['K'] < oversold) & (df['D'] < oversold)
# J值极端情况(J>100严重超买,J<0严重超卖)
df['j_extreme_high'] = df['J'] > 100
df['j_extreme_low'] = df['J'] < 0
return df
# 应用极端信号识别
df = find_extreme_signals(df)
print("\n超买区域(K,D>80):")
print(df[df['overbought']][['日期', '收盘', 'K', 'D', 'J']].tail(3))
print("\n超卖区域(K,D<20):")
print(df[df['oversold']][['日期', '收盘', 'K', 'D', 'J']].tail(3))
五、KDJ可视化实战
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
# 转换日期
df['日期'] = pd.to_datetime(df['日期'])
# 创建图表
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10),
gridspec_kw={'height_ratios': [2, 1]})
# 绘制股价
ax1.plot(df['日期'], df['收盘'], label='收盘价', color='#0F4C81', linewidth=1.5)
ax1.set_title('贵州茅台(600519) KDJ指标分析', fontsize=16, fontweight='bold')
ax1.set_ylabel('价格(元)')
ax1.legend(loc='upper left')
ax1.grid(True, alpha=0.3)
# 绘制KDJ
ax2.plot(df['日期'], df['K'], label='K', color='blue', linewidth=1.2)
ax2.plot(df['日期'], df['D'], label='D', color='orange', linewidth=1.2)
ax2.plot(df['日期'], df['J'], label='J', color='purple', linewidth=1, alpha=0.7)
# 添加超买超卖线
ax2.axhline(y=80, color='red', linestyle='--', alpha=0.5, label='超买线(80)')
ax2.axhline(y=20, color='green', linestyle='--', alpha=0.5, label='超卖线(20)')
ax2.axhline(y=50, color='gray', linestyle=':', alpha=0.3)
# 标记金叉死叉
golden_dates = df[df['golden_cross']]['日期']
golden_prices = df[df['golden_cross']]['K']
ax2.scatter(golden_dates, golden_prices, color='green', marker='^',
s=100, label='金叉', zorder=5)
death_dates = df[df['death_cross']]['日期']
death_prices = df[df['death_cross']]['K']
ax2.scatter(death_dates, death_prices, color='red', marker='v',
s=100, label='死叉', zorder=5)
ax2.set_ylabel('KDJ值')
ax2.set_xlabel('日期')
ax2.legend(loc='upper left')
ax2.grid(True, alpha=0.3)
ax2.set_ylim(0, 100)
# 格式化日期
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax2.xaxis.set_major_locator(mdates.MonthLocator(interval=1))
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('/Users/dingjiamao/.openclaw/workspace/post-to-wechat/股票知识/2026-03-24/imgs/kdj_chart.png',
dpi=150, bbox_inches='tight')
plt.show()
print("\n✅ 图表已保存至 imgs/kdj_chart.png")
六、完整实战策略
"""
KDJ交易策略完整实现
- 买入信号:金叉 + 超卖区反弹
- 卖出信号:死叉 + 超买区回落
"""
class KDJStrategy:
def __init__(self, overbought=80, oversold=20):
self.overbought = overbought
self.oversold = oversold
def analyze(self, df):
"""分析并生成交易信号"""
df = calculate_kdj(df)
df = find_cross_signals(df)
df = find_extreme_signals(df, self.overbought, self.oversold)
return df
def generate_signals(self, df):
"""生成买卖建议"""
signals = []
for i in range(1, len(df)):
row = df.iloc[i]
prev = df.iloc[i-1]
# 买入信号:金叉且在超卖区附近
if row['golden_cross'] and row['D'] < 50:
signals.append({
'日期': row['日期'],
'信号': '买入',
'价格': row['收盘'],
'K': round(row['K'], 2),
'D': round(row['D'], 2),
'理由': 'KDJ金叉,低位反弹'
})
# 卖出信号:死叉且在超买区附近
elif row['death_cross'] and row['D'] > 50:
signals.append({
'日期': row['日期'],
'信号': '卖出',
'价格': row['收盘'],
'K': round(row['K'], 2),
'D': round(row['D'], 2),
'理由': 'KDJ死叉,高位回落'
})
# J值极端反转
elif row['J'] < 0 and prev['J'] < 0 and row['J'] > prev['J']:
signals.append({
'日期': row['日期'],
'信号': '关注',
'价格': row['收盘'],
'K': round(row['K'], 2),
'D': round(row['D'], 2),
'理由': 'J值严重超卖后反弹'
})
return pd.DataFrame(signals)
# 使用策略分析
strategy = KDJStrategy()
df_analyzed = strategy.analyze(df)
signals = strategy.generate_signals(df_analyzed)
print("\n" + "="*70)
print("KDJ交易信号汇总")
print("="*70)
print(signals.tail(10).to_string(index=False))
# 统计信号数量
print(f"\n信号统计:")
print(f"- 买入信号:{len(signals[signals['信号']=='买入'])} 次")
print(f"- 卖出信号:{len(signals[signals['信号']=='卖出'])} 次")
print(f"- 关注信号:{len(signals[signals['信号']=='关注'])} 次")
七、KDJ使用技巧与注意事项
7.1 最佳实践
| 场景 | 建议 |
|---|
| 趋势行情 | KDJ在50以上金叉更可靠 |
| 震荡行情 | 超买超卖信号更有效 |
| 结合均线 | KDJ金叉+股价站上MA20,成功率更高 |
| 多周期验证 | 日线+周线KDJ共振,信号更强 |
7.2 常见问题
Q1: KDJ钝化怎么办?
在强势上涨或下跌中,KDJ可能长期停留在超买超卖区。此时应结合趋势指标(如MACD)判断。
Q2: KDJ参数如何调整?
# 短线交易(更敏感)
calculate_kdj(df, n=5, m1=3, m2=3)
# 中长线投资(更平滑)
calculate_kdj(df, n=21, m1=5, m2=5)
Q3: 如何提高准确率?
- • 结合成交量验证
- • 多指标共振(KDJ+MACD+RSI)
- • 关注背离信号(价格新高,KDJ未新高)
八、下节预告
下一期我们将学习: 《Python量化回测入门:用Backtrader验证KDJ策略》
- • Backtrader框架快速上手
- • KDJ策略回测实现
- • 绩效指标解读(收益率、夏普比率、最大回撤)
- • 参数优化方法
完整代码已整理,复制即可运行。建议先用模拟盘验证策略,再考虑实盘应用。
本文代码基于Python 3.8+、AKShare 1.15+环境测试通过。