本人股市多年的老韭菜,各种股票分析书籍,技术指标书籍阅历无数,萌发想法,何不自己开发个股票预测分析软件,选择python因为够强大,它提供了很多高效便捷的数据分析工具包。
我们已经初步的接触与学习其中数据分析中常见的3大利器---Numpy,Pandas,Matplotlib库。
也简单介绍一下数据获取的二种方法,通过金融数据接口和爬虫获取。
上一章介绍了指标之王MACD,这一章我们重点介绍一下随机指标KDJ.
KDJ指标中文名叫随机指标,是由乔治·蓝恩博士(George Lane)最早提出的。该指标集中包含了强弱指标、动量概念和移动平均线的优点,可以用来衡量股价脱离正常价格范围的偏离程度。
,它起先用于期货市场的分析,后被广泛用于股市的中短期趋势分析,是期货和股票市场上最常用的技术分析工具。
随机指标KDJ一般是用于股票分析的统计体系,根据统计学原理,通过一个特定的周期(常为9日、9周等)内出现过的最高价、最低价及最后一个计算周期的收盘价及这三者之间的比例关系,来计算最后一个计算周期的未成熟随机值RSV,然后根据平滑移动平均线的方法来计算K值、D值与J值,并绘成曲线图来研判股票走势。
KDJ指标的计算过程是,首先获取指定周期(一般是9天)内出现过的股票最高价、最低价和最后一个交易日的收盘价,随后通过它们三者间的比例关系来算出未成熟随机值RSV,并在此基础上再用平滑移动平均线的方式来计算K、D和J值。计算完成后,把KDJ的值绘成曲线图,以此来预判股票走势,具体的算法如下所示。
第一步:计算周期内(n日、n周等,n一般是9)的RSV值,RSV也叫未成熟随机指标值,是计算K值、D值和J值的基础。以n日周期计算单位为例,计算公式如下所示。
n日RSV =(Cn-Ln)/(Hn-Ln)× 100
其中,Cn是第n日(一般是最后一日)的收盘价,Ln是n日范围内的最低价,Hn是n日范围内的最高价,根据上述公式可知,RSV值的取值范围是1到100。如果要计算n周的RSV值,则Cn还是最后一日的收盘价,但Ln和Hn则是n周内的最低价和最高价。
第二步:根据RSV计算K和D值,方法如下。
当日K值= 2/3 × 前一日K值+1/3 × 当日的RSV值
当日D值= 2/3 × 前一日D值+1/3 × 当日K值
在计算过程中,如果没有前一日K 值或D值,则可以用数字50来代替。
在实际使用过程中,一般是以9日为周期来计算KD线,根据上述公式,首先是计算出最近9日的RSV值,即未成熟随机值,计算公式是9日RSV = (C-L9)÷(H9-L9)× 100。其中各项参数含义在步骤一中已经提到,其次再按本步骤所示计算当日的K和D值。
需要说明的是,上式中的平滑因子2/3和1/3是可以更改的,不过在股市交易实践中,这两个值已经被默认设置为2/3和1/3。
第三步:计算J值。J指标的计算公式为:J = 3×K - 2×D。从使用角度来看,J的实质是反映K值和D值的乖离程度,它的范围上可超过100,下可低于0。
最早的KDJ指标只有K线和D线两条线,那个时候也被称为KD指标,随着分析技术的发展,KD指标逐渐演变成KDJ指标,引入J指标后,能提高KDJ指标预判行情的能力。
在按上述三个步骤计算出每天的K、D和J三个值之后,把它们连接起来,就可以看到KDJ指标线了。
python代码实现:
import pandas as pd
import talib
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111)
df = pd.read_csv("600276.csv")
df['date'] = pd.to_datetime(df['date'])
df['date'] = df['date'].apply(lambda x: x.strftime('%Y-%m-%d'))
df['K'], df['D'] = talib.STOCH(df['high'].values, df['low'].values, df['close'].values, fastk_period=9, slowk_period=3,
slowk_matype=0, slowd_period=3, slowd_matype=0)
df['K'].fillna(0,inplace=True)
df['D'].fillna(0,inplace=True)
df['J']=3*df['K']-2*df['D']
ax.plot(df["date"], df["K"], label ="K")
ax.plot(df["date"], df["D"], label ="D")
ax.plot(df["date"], df["J"], label ="J")
plt.legend()
ax.xaxis.set_major_locator(ticker.MaxNLocator(20))
def format_date(x, pos=None):
# 由于前面股票数据在 date 这个位置传入的都是int
# 因此 x=0,1,2,...
# date_tickers 是所有日期的字符串形式列表
if x < 0 or x > len(df['date']) - 1:
return ''
return df['date'][int(x)]
ax.xaxis.set_major_formatter(ticker.FuncFormatter(format_date))
plt.setp(plt.gca().get_xticklabels(), rotation=45, horizontalalignment='right')
plt.show()
在Python中,KDJ顶背离可以通过计算KDJ指标的最高点与股价的最高点之间的关系来确定。具体步骤如下:
需要注意的是,KDJ指标的计算涉及到平滑处理,因此在实际计算中需要注意平滑周期的选择。同时,顶背离并不一定意味着股价一定会下跌,还需要结合其他技术指标和基本面分析进行综合判断。
代码:
import pandas as pd
import numpy as np
# 读取数据
data = pd.read_csv('002761.csv')
# 计算KDJ指标
data['k'] = data['close'].ewm(span=9, min_periods=1, adjust=False).mean() * 3 / data['close'].ewm(span=3, min_periods=1, adjust=False).mean()
data['d'] = data['k'].ewm(span=3, min_periods=1, adjust=False).mean()
data['j'] = 3 * data['k'] - 2 * data['d']
# 计算顶背离
data['top_divergence'] = np.where((data['high'].shift(1) > data['high']) & (data['j'].shift(1) < data['j']), True, False)
# 输出结果
print(data[['high', 'j', 'top_divergence']])
KDJ底背离是指当股价K线图上的股票走势一谷比一谷低,股价在向下跌,而KDJ曲线图上的KDJ指标的走势在低位一底比一底高,这叫底背离现象。底背离现象一般是股价将低位反转的信号,表明股价中短期内即将上涨,是买入的信号。
实战技巧:
代码:
import pandas as pd
import numpy as np
# 读取数据
data = pd.read_csv('007611.csv')
# 计算KDJ指标
data['k'] = data['close'].ewm(span=9, min_periods=1, adjust=False).mean() * 3 / data['close'].ewm(span=3, min_periods=1, adjust=False).mean()
data['d'] = data['k'].ewm(span=3, min_periods=1, adjust=False).mean()
data['j'] = 3 * data['k'] - 2 * data['d']
# 计算底背离
data['bottom_divergence'] = np.where((data['low'].shift(1) < data['low']) & (data['j'].shift(1) > data['j']), True, False)
# 输出结果
print(data[['low', 'j', 'bottom_divergence']])
注释:KDJ顶背离成功率远远高于底背离
KDJ指标的超买和超卖区域的划分标准如下:
代码实现:
import pandas as pd
import numpy as np
# 读取数据
data = pd.read_csv('000786.csv')
# 计算KDJ指标
data['k'] = data['close'].ewm(span=9, min_periods=1, adjust=False).mean() * 3 / data['close'].ewm(span=3, min_periods=1, adjust=False).mean()
data['d'] = data['k'].ewm(span=3, min_periods=1, adjust=False).mean()
data['j'] = 3 * data['k'] - 2 * data['d']
# 计算超买和超卖区域
data['overbought'] = np.where((data['j'] > 80), True, False)
data['oversold'] = np.where((data['j'] < 20), True, False)
# 输出结果
print(data[['j', 'overbought', 'oversold']])
注意:超卖超买指标最好结合背离指标一起运用,才能自己成功概率!
金叉是指K线从下往上穿过D线,形成有效的向上突破。这通常被视为一个买入信号,因为它预示着股票价格的上涨趋势。实战中,当KDJ三线在超卖区形成金叉时,股价成功反弹的可能性较高。另外,KDJ金叉后出现小幅震荡整理向上的形态更好,这说明资金在不断吸筹等待后期的快速拉升。
死叉则是指K线从上往下穿过D线,形成所谓的“死叉”,表明股票价格的趋势可能向下。这通常被视为一个卖出信号,因为它预示着股票价格的下跌。当高位死叉形成后,一定要及时止盈止损。
完整代码:
# !/usr/bin/env python
# coding=utf-8
import matplotlib.pyplot as plt
import pandas as pd
import pandas
from mpl_finance import candlestick2_ochl
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter
import tkinter.messagebox
import efinance as ef
# 计算KDJ
plt.rcParams['font.family'] = 'Heiti TC'
plt.rcParams['font.sans-serif'] = ['SimHei']
def calKDJ(df):
df['MinLow'] = df['low'].rolling(9, min_periods=9).min()
# 填充NaN数据
df['MinLow'].fillna(value = df['low'].expanding().min(), inplace = True)
df['MaxHigh'] = df['high'].rolling(9, min_periods=9).max()
df['MaxHigh'].fillna(value = df['high'].expanding().max(), inplace = True)
df['RSV'] = (df['close'] - df['MinLow']) / (df['MaxHigh'] - df['MinLow']) * 100
for i in range(len(df)):
if i==0: # 第一天
df.loc[i,'K']=50
df.loc[i,'D']=50
if i>0:
df.loc[i,'K']=df.loc[i-1,'K']*2/3 + 1/3*df.loc[i,'RSV']
df.loc[i,'D']=df.loc[i-1,'D']*2/3 + 1/3*df.loc[i,'K']
df.loc[i,'J']=3*df.loc[i,'K']-2*df.loc[i,'D']
return df
# 绘制KDJ线
def drawKDJAndKLine(stockCode,startDate,endDate):
df = ef.stock.get_quote_history(stockCode)
# 将数据存储到 csv 文件中
df.to_csv(f'{stockCode}.csv', encoding='utf-8-sig', index=None)
filename=stockCode+startDate+endDate+'.csv'
getStockDataFromAPI(stockCode,startDate,endDate)
df = pd.read_csv('600276.csv')
stockDataFrame = calKDJ(df)
# 创建子图
(axPrice, axKDJ) = figure.subplots(2, sharex=True)
# 调用方法,在第axPrice子图中绘制K线图
candlestick2_ochl(ax = axPrice,
opens=stockDataFrame["open"].values, closes=stockDataFrame["close"].values,
highs=stockDataFrame["high"].values, lows=stockDataFrame["low"].values,
width=0.75, colorup='red', colordown='green')
axPrice.set_title("K线图和均线图") # 设置子图标题
stockDataFrame['close'].rolling(window=3).mean().plot(ax=axPrice,color="red",label='3日均线')
stockDataFrame['close'].rolling(window=5).mean().plot(ax=axPrice,color="blue",label='5日均线')
stockDataFrame['close'].rolling(window=10).mean().plot(ax=axPrice,color="green",label='10日均线')
axPrice.legend(loc='best') # 绘制图例
axPrice.set_ylabel("价格(单位:元)")
axPrice.grid(linestyle='-.') # 带网格线
# 在axKDJ子图中绘制KDJ
stockDataFrame['K'].plot(ax=axKDJ,color="blue",label='K')
stockDataFrame['D'].plot(ax=axKDJ,color="green",label='D')
stockDataFrame['J'].plot(ax=axKDJ,color="purple",label='J')
plt.legend(loc='best') # 绘制图例
plt.rcParams['font.family'] = 'Heiti TC'
plt.rcParams['font.sans-serif']=['SimHei']
axKDJ.set_title("KDJ图") # 设置子图的标题
axKDJ.grid(linestyle='-.') # 带网格线
# 设置x轴坐标的标签和旋转角度
major_index=stockDataFrame.index[stockDataFrame.index%5==0]
major_xtics=stockDataFrame['date'][stockDataFrame.index%5==0]
plt.xticks(major_index,major_xtics)
plt.setp(plt.gca().get_xticklabels(), rotation=30)
# 从API中获取股票数据
def getStockDataFromAPI(stockCode,startDate,endDate):
try:
# 给股票代码加ss前缀来获取上证股票的数据
stock = pandas.get_quote_history(stockCode+'.ss')
if(len(stock)<1):
# 如果没取到数据,则抛出异常
raise Exception()
# 删除最后一行,因为get_data_yahoo会多取一天的股票交易数据
stock.drop(stock.index[len(stock)-1],inplace=True) # 在本地留份csv
filename=''+stockCode+startDate+endDate+'.csv'
stock.to_csv(filename)
except Exception as e:
print('Error when getting the data of:' + stockCode)
print(repr(e))
# 设置tkinter窗口
win = tkinter.Tk()
win.geometry('925x800') # 设置大小
win.title("K线均线整合KDJ")
# 放置控件
tkinter.Label(win,text='股票代码:').place(x=10,y=20)
tkinter.Label(win,text='开始时间:').place(x=10,y=50)
tkinter.Label(win,text='结束时间:').place(x=10,y=80)
stockCodeVal = tkinter.StringVar()
startDateVal = tkinter.StringVar()
endDateVal = tkinter.StringVar()
stockCodeEntry = tkinter.Entry(win,textvariable=stockCodeVal)
stockCodeEntry.place(x=70,y=20)
stockCodeEntry.insert(0,'600640')
startDateEntry = tkinter.Entry(win,textvariable=startDateVal)
startDateEntry.place(x=70,y=50)
startDateEntry.insert(0,'2023-03-01')
endDateEntry = tkinter.Entry(win,textvariable=endDateVal)
endDateEntry.place(x=70,y=80)
endDateEntry.insert(0,'2023-06-31')
def draw(): # 绘制按钮的处理函数
plt.clf() # 先清空所有在plt上的图形
stockCode=stockCodeVal.get()
startDate=startDateVal.get()
endDate=endDateVal.get()
drawKDJAndKLine(stockCode,startDate,endDate)
canvas.draw()
tkinter.Button(win,text='绘制',width=12,command=draw).place(x=200,y=50)
def reset():
stockCodeEntry.delete(0,tkinter.END)
stockCodeEntry.insert(0,'600640')
startDateEntry.delete(0,tkinter.END)
startDateEntry.insert(0,'2023-03-01')
endDateEntry.delete(0,tkinter.END)
endDateEntry.insert(0,'2023-06-31')
plt.clf()
canvas.draw()
tkinter.Button(win,text='重置',width=12,command=reset).place(x=200,y=80)
# 以对话框的形式输出买点
def printBuyPoints():
stockCode=stockCodeVal.get()
startDate=startDateVal.get()
endDate=endDateVal.get()
filename=''+stockCode+startDate+endDate+'.csv'
getStockDataFromAPI(stockCode,startDate,endDate)
df = pd.read_csv('600276.csv')
stockDf = calKDJ(df)
cnt=0
buyDate=''
while cnt<=len(stockDf)-1:
if(cnt>=5): # 略过前几天的误差
#规则1:前一天J值大于10,当天J值小于10,是买点、
if stockDf.iloc[cnt]['J']<10 and stockDf.iloc[cnt-1]['J']>10:
buyDate = buyDate+stockDf.iloc[cnt]['date'] + ','
cnt=cnt+1
continue
# 规则2:K,D均在20之下,出现K线上穿D线的金叉现象
# 规则1和规则2是“或”的关系,所以当满足规则1时直接continue
if stockDf.iloc[cnt]['K']>stockDf.iloc[cnt]['D'] and stockDf.iloc[cnt-1]['D']>stockDf.iloc[cnt-1]['K']:
# 满足上穿条件后再判断K和D均小于20
if stockDf.iloc[cnt]['K']< 20 and stockDf.iloc[cnt]['D']<20:
buyDate = buyDate + stockDf.iloc[cnt]['date'] + ','
cnt=cnt+1
# 完成后,通过对话框的形式显示买入日期
tkinter.messagebox.showinfo('提示买点',buyDate)
tkinter.Button(win,text='计算买点',width=12,command=printBuyPoints).place(x=300,y=50)
# 以对话框的形式输出卖点
def printSellPoints():
stockCode=stockCodeVal.get()
startDate=startDateVal.get()
endDate=endDateVal.get()
filename=''+stockCode+startDate+endDate+'.csv'
getStockDataFromAPI(stockCode,startDate,endDate)
df = pd.read_csv('600276.csv')
stockDf = calKDJ(df)
cnt=0
sellDate=''
while cnt<=len(stockDf)-1:
if(cnt>=5): # 略过前几天的误差
# 规则1:前一天J值小于100,当天J值大于100,是卖点、
if stockDf.iloc[cnt]['J']>100 and stockDf.iloc[cnt-1]['J']<100:
sellDate = sellDate+stockDf.iloc[cnt]['date'] + ','
cnt=cnt+1
continue
# 规则2:K,D均在80之上,出现K线下穿D线的死叉现象
if stockDf.iloc[cnt]['K'] 80 and stockDf.iloc[cnt]['D']>80:
sellDate = sellDate + stockDf.iloc[cnt]['date'] + ','
cnt=cnt+1
# 完成后,通过对话框的形式显示买入日期
tkinter.messagebox.showinfo('kdj死叉卖点',sellDate)
tkinter.Button(win,text='kdj死叉计算卖点',width=12,command=printSellPoints).place(x=300,y=80)
# 开始整合figure和win
figure = plt.figure()
canvas = FigureCanvasTkAgg(figure, win)
canvas.get_tk_widget().config(width=875,height=600)
canvas.get_tk_widget().place(x=0,y=100)
win.mainloop()
注意:kdj的金叉和死叉一定要结合其他指标应用,经过统计这个指标成功概率不高。
kdj指标基本应用学的差不多了,希望大家交流学习