股票技术指标是数据分析的经典案例,本文使用Python计算常见的股票技术指标,以期结合实例进一步掌握数据分析的基础技能。
库导入和设置
使用mplfinance来绘制K线图和交易量柱状图,但是用它绘制其他技术曲线形状好像有问题!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from mootdx.reader import Readerimport numpy as npimport matplotlib.pyplot as pltimport pandas as pdplt.rcParams["font.family" ] = ["SimHei" ] plt.rcParams["axes.unicode_minus" ] = False import mplfinance as mpfmy_style = mpf.make_mpf_style( marketcolors=mpf.make_marketcolors( up='red' , down='green' , edge='inherit' , wick='inherit' , volume='inherit' ), base_mpf_style='charles' , gridcolor='#cccccc' , gridaxis='both' , rc={ 'font.sans-serif' : ['SimHei' ], 'axes.unicode_minus' : False } )
读股票数据
使用mootdx直接读取通达信的股票基本数据,在此基础上进行各种数据处理。这是我目前找到的最好用的数据来源!最基本的数据就是开盘价、最高价、最低价、收盘价、交易量和交易额。
1 2 3 4 5 6 7 8 9 10 reader = Reader.factory(market="std" , tdxdir=r"C:\new_tdx64" ) df = reader.daily(symbol="600869" ) df.rename(columns={ 'open' :'Open' , 'high' :'High' , 'low' :'Low' , 'close' :'Close' , 'volume' :'Volume' , 'amount' :'Amount' },inplace=True )
计算绘图数据
不同的均值计算
假设给定一个数据序列,序列长度是n:
P 0 、 P 1 、 P 2 、 . . . 、 P n − 1 P_0、P_1、P_2、...、P_{n-1}
P 0 、 P 1 、 P 2 、 . . . 、 P n − 1
简单移动平均(SMA)
S M A ( w ) t = ∑ i = t − w + 1 t P i w 当 t < w − 1 时 , S M A 无值。 SMA(w)_t=\frac{\sum_{i=t-w+1}^t{P_i}}{w}\\
当t<w-1时,SMA无值。
S M A ( w ) t = w ∑ i = t − w + 1 t P i 当 t < w − 1 时 , S M A 无 值 。
在Pandas中,使用series.rolling(window=w).mean()即可计算SMA。除了均值,Series也提供方法直接计算滑动窗体内各数的最大值、最小值、标准差等指标。
指数移动平均(EMA)
MACD指标主要用到这种均值。迭代计算公式:
E M A ( s ) t = α ⋅ P t + ( 1 − α ) E M A t − 1 E M A ( s ) 0 = P 0 α = 2 s + 1 EMA(s)_t=\alpha\cdot{P_t}+(1-\alpha)EMA_{t-1}\\
EMA(s)_0=P_0\\
\alpha=\frac{2}{s+1}
E M A ( s ) t = α ⋅ P t + ( 1 − α ) E M A t − 1 E M A ( s ) 0 = P 0 α = s + 1 2
Pandas中可以这样计算:series.ewm(span=n,adjust=False).mean()。
s越大,α \alpha α 越小,当前数据的影响占比越小,往期数据的影响占比越大。
平滑移动平均(SMMA)
KDJ、RSI指标计算会用到这种均值。迭代计算公式:
S M M A ( C , N , M ) t = N − M N ⋅ S M M A ( C , N , M ) t − 1 + M N ⋅ P t 关于计算起点,不同指标取法不一致。 R S I 取法: 当 t < N − 1 时 , S M M A 无值 , S M M A ( C , N , M ) N − 1 = S M A ( N ) N − 1 。 K D J 取法: S M M A ( C , N , M ) 0 = 一个初始值 SMMA(C,N,M)_t=\frac{N-M}{N}\cdot{SMMA(C,N,M)_{t-1}}+\frac{M}{N}\cdot{P_t}\\
关于计算起点,不同指标取法不一致。
\\RSI取法:\\
当t<N-1时,SMMA无值,SMMA(C,N,M)_{N-1}=SMA(N)_{N-1}。\\
KDJ取法:\\
SMMA(C,N,M)_0=一个初始值
S M M A ( C , N , M ) t = N N − M ⋅ S M M A ( C , N , M ) t − 1 + N M ⋅ P t 关 于 计 算 起 点 , 不 同 指 标 取 法 不 一 致 。 R S I 取 法 : 当 t < N − 1 时 , S M M A 无 值 , S M M A ( C , N , M ) N − 1 = S M A ( N ) N − 1 。 K D J 取 法 : S M M A ( C , N , M ) 0 = 一 个 初 始 值
通常取M=1:
S M M A ( C , N , 1 ) t = N − 1 N ⋅ S M M A ( C , N , M ) t − 1 + 1 N ⋅ P t SMMA(C,N,1)_t=\frac{N-1}{N}\cdot{SMMA(C,N,M)_{t-1}}+\frac{1}{N}\cdot{P_t}
S M M A ( C , N , 1 ) t = N N − 1 ⋅ S M M A ( C , N , M ) t − 1 + N 1 ⋅ P t
在Python中需要通过代码逻辑实现:
1 2 3 4 5 6 def smma (series, N, M ): res = series.copy() res.iloc[N-1 ] = series.iloc[:N].mean() for i in range (N, len (series)): res.iloc[i] = (res.iloc[i-1 ] * (N - M) + series.iloc[i] * M) / N return res
具体计算代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 df['ma5' ]=df['Close' ].rolling(window=5 ).mean() df['ma10' ]=df['Close' ].rolling(window=10 ).mean() df['ma20' ]=df['Close' ].rolling(window=20 ).mean() df['ma60' ]=df['Close' ].rolling(window=60 ).mean() df['boll_mid' ] = df['Close' ].rolling(window=20 ).mean() df['boll_std' ] = df['Close' ].rolling(window=20 ).std() df['boll_up' ] = df['boll_mid' ] + 2 * df['boll_std' ] df['boll_low' ] = df['boll_mid' ] - 2 * df['boll_std' ] df['vol_ma5' ]=df['Volume' ].rolling(window=5 ).mean() df['vol_ma10' ]=df['Volume' ].rolling(window=10 ).mean() df['vol_ma20' ]=df['Volume' ].rolling(window=20 ).mean() ema12=df['Close' ].ewm(span=12 ,adjust=False ).mean() ema26=df['Close' ].ewm(span=26 ,adjust=False ).mean() df['DIF' ]=ema12-ema26 df['DEA' ]=df['DIF' ].ewm(span=9 ,adjust=False ).mean() df['MACD' ]=2 *(df['DIF' ]-df['DEA' ]) def compute_kdj (series_low,series_high,series_close,n,m1,m2 ): low_n=series_low.rolling(window=n,min_periods=1 ).min () high_n=series_high.rolling(window=n,min_periods=1 ).max () rsv=(series_close-low_n)/(high_n-low_n)*100 rsv=rsv.replace([np.inf,-np.inf],np.nan) k=series_close.copy() d=series_close.copy() j=series_close.copy() k.iloc[0 ]=rsv.iloc[0 ] d.iloc[0 ]=rsv.iloc[0 ] j.iloc[0 ]=rsv.iloc[0 ] for i in range (1 ,len (series_close)): k.iloc[i]=2.0 /3.0 *k.iloc[i-1 ]+1.0 /3.0 *rsv.iloc[i] d.iloc[i]=2.0 /3.0 *d.iloc[i-1 ]+1.0 /3.0 *k.iloc[i] j.iloc[i]=3.0 *k.iloc[i]-2.0 *d.iloc[i] return k,d,j k,d,j=compute_kdj(df['Low' ],df['High' ],df['Close' ],9 ,3 ,3 ) df['K' ]=k df['D' ]=d df['J' ]=j def wilder_smooth (series, period ): res = series.copy() res.iloc[period-1 ] = series.iloc[:period].mean() for i in range (period, len (series)): res.iloc[i] = (res.iloc[i-1 ] * (period - 1 ) + series.iloc[i]) / period return res def compute_rsi (close_series, n ): lc = close_series.shift(1 ) u = (close_series - lc).mask(close_series - lc < 0 , 0 ) ud_abs = (close_series - lc).abs () sma_u = wilder_smooth(u, n) sma_ud = wilder_smooth(ud_abs, n) rsi = sma_u / sma_ud * 100 rsi = rsi.mask(sma_ud == 0 , 0 ) rsi = rsi.replace([np.inf, -np.inf], 100 ) return rsi df['rsi6' ] = compute_rsi(df['Close' ],6 ) df['rsi12' ] = compute_rsi(df['Close' ],12 ) df['rsi24' ] = compute_rsi(df['Close' ],24 )
绘制图形
K线、均线、布林带都绘制在主图中;交易量均线绘制在交易量柱状图中;其它(KDJ、MACD、RSI)图形用matplotlib库单独绘制。
绘制K线、均线、布林带
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 add_plots_ma = [ mpf.make_addplot(df["ma5" ], color='#ff4444' , width=1.0 , label="MA5" ), mpf.make_addplot(df["ma10" ], color='#ffbb33' , width=1.0 , label="MA10" ), mpf.make_addplot(df["ma20" ], color='#0099cc' , width=1.0 , label="MA20" ), mpf.make_addplot(df["ma60" ], color='#9933cc' , width=1.0 , label="MA60" ) ] add_plots_boll = [ mpf.make_addplot(df["boll_mid" ], color='#ff0000' , width=1.5 , label="BOLL_MID" ), mpf.make_addplot(df["boll_up" ], color='#ff0000' , width=1.5 , label="BOLL_UPPER" ), mpf.make_addplot(df["boll_low" ], color='#ff0000' , width=1.5 , label="BOLL_LOWER" ) ] add_plots_volma = [ mpf.make_addplot(df["vol_ma5" ], panel=1 , color='#ff6600' , width=1.0 , label="MA5" ), mpf.make_addplot(df["vol_ma10" ], panel=1 , color='#0066FF' , width=1.0 , label="MA10" ), mpf.make_addplot(df["vol_ma20" ], panel=1 , color='#ff00FF' , width=1.0 , label="MA20" ) ] all_addplot = add_plots_ma +add_plots_boll+ add_plots_volma panel_ratios = [2 , 1 ] mpf.plot( df, type ="candle" , volume=True , style=my_style, addplot=all_addplot, title="日K线" , figsize=(16 ,10 ), ylabel="价格" , ylabel_lower="VOL" , show_nontrading=False , datetime_format="%Y%m%d" , xrotation=0 , panel_ratios=panel_ratios )
绘制KDJ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 dates=df.index.strftime("%Y-%m-%d" ) step = 10 fig, ax =plt.subplots(figsize=(14 ,3 )) ax.plot(dates, df['K' ].to_numpy(), lw=1.2 , label="K" ) ax.plot(dates, df['D' ].to_numpy(), lw=1.2 , label="D" ) ax.plot(dates, df['J' ].to_numpy(), lw=1.2 , label="J" ) ax.set_xticks(dates[::step]) ax.set_xticklabels(dates[::step]) ax.spines['top' ].set_visible(True ) ax.spines['right' ].set_visible(True ) ax.spines['bottom' ].set_visible(True ) ax.spines['left' ].set_visible(True ) ax.spines['top' ].set_color('black' ) ax.spines['right' ].set_color('black' ) ax.spines['bottom' ].set_color('black' ) ax.spines['left' ].set_color('black' ) ax.spines['top' ].set_linewidth(0.5 ) ax.spines['right' ].set_linewidth(0.5 ) ax.spines['bottom' ].set_linewidth(0.5 ) ax.spines['left' ].set_linewidth(0.5 ) plt.title("KDJ" ) plt.grid(alpha=0.8 ) plt.legend() plt.tight_layout() plt.show()
绘制MACD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 dates=df.index.strftime("%Y-%m-%d" ) step = 10 fig, ax =plt.subplots(figsize=(14 ,3 )) bar_color = np.where(df["MACD" ]>0 , "#ff4444" , "#00aa44" ) ax.bar(dates, df["MACD" ].to_numpy(), width=0.5 , alpha=0.8 , label="MACD" ,color=bar_color) ax.plot(dates, df['DIF' ].to_numpy(), lw=1.2 , label="DIF" ) ax.plot(dates, df['DEA' ].to_numpy(), lw=1.2 , label="DEA" ) ax.set_xticks(dates[::step]) ax.set_xticklabels(dates[::step]) ax.spines['top' ].set_visible(True ) ax.spines['right' ].set_visible(True ) ax.spines['bottom' ].set_visible(True ) ax.spines['left' ].set_visible(True ) ax.spines['top' ].set_color('black' ) ax.spines['right' ].set_color('black' ) ax.spines['bottom' ].set_color('black' ) ax.spines['left' ].set_color('black' ) ax.spines['top' ].set_linewidth(0.5 ) ax.spines['right' ].set_linewidth(0.5 ) ax.spines['bottom' ].set_linewidth(0.5 ) ax.spines['left' ].set_linewidth(0.5 ) plt.title("MACD" ) plt.grid(alpha=0.8 ) plt.legend() plt.tight_layout() plt.show()
绘制RSI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 dates=df.index.strftime("%Y-%m-%d" ) step = 10 fig, ax =plt.subplots(figsize=(14 ,3 )) ax.plot(dates, df['rsi6' ].to_numpy(), lw=1.2 , label="RSI6" ) ax.plot(dates, df['rsi12' ].to_numpy(), lw=1.2 , label="RSI12" ) ax.plot(dates, df['rsi24' ].to_numpy(), lw=1.2 , label="RSI24" ) ax.set_xticks(dates[::step]) ax.set_xticklabels(dates[::step]) ax.spines['top' ].set_visible(True ) ax.spines['right' ].set_visible(True ) ax.spines['bottom' ].set_visible(True ) ax.spines['left' ].set_visible(True ) ax.spines['top' ].set_color('black' ) ax.spines['right' ].set_color('black' ) ax.spines['bottom' ].set_color('black' ) ax.spines['left' ].set_color('black' ) ax.spines['top' ].set_linewidth(0.5 ) ax.spines['right' ].set_linewidth(0.5 ) ax.spines['bottom' ].set_linewidth(0.5 ) ax.spines['left' ].set_linewidth(0.5 ) plt.title("RSI" ) plt.grid(alpha=0.8 ) plt.legend() plt.tight_layout() plt.show()