1. 程式人生 > >以股票RSI指標為例,學習Python傳送郵件功能(含RSI指標確定賣點策略)

以股票RSI指標為例,學習Python傳送郵件功能(含RSI指標確定賣點策略)

    本人之前寫過若干“給程式設計師加財商”的系列文,目的是通過股票案例講述Python知識點,讓大家在學習Python的同時還能掌握相關的股票知識,所謂一舉兩得。

    在之前的系列文裡,大家能看到K線,均線,成交量的案例,在本文裡,大家能看到通過RSI案例講述Python郵件程式設計的知識點,在後繼系列文裡,大家還能看到MACD,BIAS,KDJ等指標相關案例。

1  RSI指標的原理和演算法描述

    相對強弱指標(RSI)是通過比較某個時段內單股價格的漲跌幅度來判斷多空雙方的強弱程度,以此來預測未來走勢。從數值上看,它體現出某股的買賣力量,所以投資者能據此預測未來價格的走勢,在實踐中,通常與移動平均線配合使用,以提高分析的準確性。

    RSI指標的計算公式如下所示。

    第一步,RS(相對強度)=N日內收盤價漲數和的均值÷N日內收盤價跌數和的均值

    第二步,RSI(相對強弱指標)=100-100÷(1+RS)

    請注意,這裡“均值“的計算方法可以是簡單移動平均(SMA),也可以是加權移動平均(WMA)指數移動平均(EMA)。本書採用的是比較簡單的簡單移動平均演算法,有些軟體採用的是後兩種平均演算法。採用不同的平均演算法會導致RSI的值不同,但趨勢不會改變,對交易的指導意義也不會變。

    以6日RSI指標為例,從當日算起向前推算6個交易日,獲取到包括本日在內的7個收盤價,用每一日的收盤價減去上一交易日的收盤價,以此方式得到6個數值,這些數值中有正有負。隨後再按如下四個步驟計算RSI指標。

    第一步,up=6個數字中正數之和的平均值。

    第二步,down=先取6個數字中負數之和的絕對值,再對絕對值取平均值。

    第三步,RS=up除以down,RS表示相對強度

    第四步,RSI(相對強弱指標)=100-100÷(1+RS)

    如果再對第四步得出的結果進行數學變換,能進一步約去RS因素,得到如下的結論:RSI=100x(up) ÷(up+down),也就是說,RSI等於100乘以up除以(up和down的和)。

    從本質上來看,RSI反映了某階段內(比如6個交易日內)由價格上漲引發的波動佔總波動的百分比率,百分比越大,說明這個時間段內股票越強勢,反之如果百分比越小,則說明股票弱勢程度強。

    從上述公式中我們能看到,RSI的值介於0到100之間,目前比較常見的基準週期為6日\12日和24日。把每個交易日的RSI值在座標圖上的點連成曲線,即能繪製成RSI指標線,也就是說,目前滬深股市中RSI指標線是由三根曲線構成,如下圖所示。

  

2  把用Matplotlib繪製的RSI指標圖存為圖片

    在如下的DrawRSI.py案例中,我們將根據上述演算法繪製600584(長電科技)從2018年9月到2019年5月間的的6日、12日和24日的RSI指標。

    本例的資料來自csv檔案,而該檔案的資料來自網路股票介面,相關內容大家可以閱讀之前博文。在本案例中,還會把由matplotlib生成的圖形存為png格式,以方便之後用郵件的形式傳送。    

1	#!/usr/bin/env python
2	#coding=utf-8
3	import pandas as pd
4	import matplotlib.pyplot as plt
5	#計算RSI的方法,入參periodList傳入週期列表 
6	def calRSI(df,periodList):
7	    #計算和上一個交易日收盤價的差值
8	    df['diff'] = df["Close"]-df["Close"].shift(1) 
9	    df['diff'].fillna(0, inplace = True)    
10	    df['up'] = df['diff']
11	    #過濾掉小於0的值
12	    df['up'][df['up']] = 0
13	    df['down'] = df['diff']
14	    #過濾掉大於0的值
15	    df['down'][df['down']] = 0
16	    #通過for迴圈,依次計算periodList中不同週期的RSI等值
17	    for period in periodList:
18	        df['upAvg'+str(period)] = df['up'].rolling(period).sum()/period
19	        df['upAvg'+str(period)].fillna(0, inplace = True)
20	        df['downAvg'+str(period)] = abs(df['down'].rolling(period).sum()/period)
21	        df['downAvg'+str(period)].fillna(0, inplace = True)
22	        df['RSI'+str(period)] = 100 - 100/((df['upAvg'+str(period)]/df['downAvg'+str(period)]+1))
23	    return df

    在第5行裡,我們定義了用於計算RSI值的calRSI方法,該方法第一個引數是包含日期收盤價等資訊的dataframe型別的df物件,第二個引數是週期列表。

    在第8行裡,我們把本交易日和上個交易日收盤價的差價存入了'diff'列,這裡是用shift(1)來獲取df裡上一行(即上個交易日)的收盤價。由於第一行的diff值是NaN,所以需要用第9行的fillna方法把NaN值更新成0。

    在第11行裡,在df物件裡建立了up列,該列的值暫時和diff值相同,有正有負,但馬上就通過第12行的df['up'][df['up']<0] = 0程式碼,把up列中的負值設定成0,這樣一來,up列裡就只包含了“N日內收盤價的漲數”。在第13行和第15行裡,用同樣的方法,在df物件中建立了down列,並在其中存入了“N日內收盤價的跌數”。

    隨後是通過第17行的for迴圈,遍歷儲存在periodList中的週期物件,其實通過下面第26行的程式碼,我們能看到計算RSI的週期分別是6天、12天和24天。針對每個週期,先是在第18行,算出了這個週期內收盤價漲數和的均值,並把這個均值存入df物件中的'upAvg'+str(period)列中,比如當前週期是6,那麼該漲數的均值是存入df[‘upAvg6‘]列。在第20行,則算出該週期內的收盤價跌數的均值,並存入'downAvg'+str(period)列中。最後在第22行,算出本週期內的RSI值,並放入df物件中的'RSI'+str(period)裡。 

24    filename='D:\\stockData\ch10\\6005842018-09-012019-05-31.csv'
25    df = pd.read_csv(filename,encoding='gbk')
26    list = [6,12,24] #週期列表
27    #呼叫方法計算RSI
28    stockDataFrame = calRSI(df,list) 
29    #print(stockDataFrame)
30    #開始繪圖
31    plt.figure()
32    stockDataFrame['RSI6'].plot(color="blue",label='RSI6')
33    stockDataFrame['RSI12'].plot(color="green",label='RSI12')
34    stockDataFrame['RSI24'].plot(color="purple",label='RSI24')
35    plt.legend(loc='best') #繪製圖例       
36    #設定x軸座標標籤和旋轉角度
37    major_index=stockDataFrame.index[stockDataFrame.index==0]
38    major_xtics=stockDataFrame['Date'][stockDataFrame.index==0]
39    plt.xticks(major_index,major_xtics)
40    plt.setp(plt.gca().get_xticklabels(), rotation=30) 
41    #帶網格線,且設定了網格樣式
42    plt.grid(linestyle='-.') 
43    plt.title("RSI效果圖")
44    plt.rcParams['font.sans-serif']=['SimHei']
45    plt.savefig('D:\\stockData\ch10\\6005842018-09-012019-05-31.png')
46    plt.show()

     在第25行裡,我們從指定csv檔案裡得到了包含日期收盤價等資訊的資料,並在第26行指定了三個計算週期。在第28行裡,我們呼叫了calRSI方法計算了三個週期的RSI值,並存入stockDataFrame物件,當前第29行的輸出語句是註釋掉的,在開啟後,大家能看到計算後的結果值,其中包含upAvg6、downAvg6和RSI6等列。

    在得到RSI資料後,從第31行開始繪圖,其中比較重要的步驟是通過第32行到第34行的程式碼,用plot方法繪製三根曲線,隨後通過第35行的legend方法設定圖例,通過第37行和第38行的程式碼設定x軸刻度的文字以及旋轉效果,通過第42行的程式碼設定網格樣式,通過第43的程式碼設定標題。

    在第46行通過show方法繪圖前,我們通過第45行的程式碼,用savefig方法把圖形儲存到了指定目錄,請注意這句話需要放在show方法前,否則儲存的圖片就會是空的。

    執行上述程式碼,能看到如下圖所示的RSI效果圖。需要說明的是,由於本例在計算收盤價漲數和均值和收盤價跌數和均值時,用的是簡單移動平均演算法,所以繪製出來的圖形可能和一些軟體裡的不一致,但趨勢相同。而且,在指定目錄裡,能看到png圖片。

     

3  整合K線後用郵件傳送

    在DrawKwithRSI.py程式碼裡,我們將完成如下三個工作,第一,計算6日、12日和24日的RSI值。第二,繪製K線加均線加RSI指標圖,並把結果儲存為png格式圖片。第三,傳送郵件,並把png圖片以富文字的格式展示在郵件正文中。

1    #!/usr/bin/env python
2    #coding=utf-8
3    import pandas as pd
4    import matplotlib.pyplot as plt 
5    from mpl_finance import candlestick2_ochl
6    from matplotlib.ticker import MultipleLocator
7    import smtplib
8    from email.mime.text import MIMEText
9    from email.mime.image import MIMEImage
10    from email.mime.multipart import MIMEMultipart
11    #計算RSI的方法,入參periodList傳入週期列表 
12    def calRSI(df,periodList):
13        和DrawRSI.py案例中的一致    

    從第3行到第10行,我們引入了相關的庫檔案,第12行定義的calRSI方法和之前案例中的完全一致,所以就不再給出程式碼。    

14	filename='D:\\stockData\ch10\\6005842018-09-012019-05-31.csv'
15	df = pd.read_csv(filename,encoding='gbk')
16	list = [6,12,24] #週期列表
17	#呼叫方法計算RSI
18	stockDataFrame = calRSI(df,list) 
19	figure = plt.figure()
20	#建立子圖     
21	(axPrice, axRSI) = figure.subplots(2, sharex=True)
22	#呼叫方法,在第axPrice子圖裡繪製K線圖 
23	candlestick2_ochl(ax = axPrice, opens=df["Open"].values, closes=df["Close"].values, highs=df["High"].values, lows=df["Low"].values,width=0.75, colorup='red', colordown='green')
24	axPrice.set_title("K線圖和均線圖")#設定子圖示題
25	stockDataFrame['Close'].rolling(window=3).mean().plot(ax=axPrice,color="red",label='3天均線')
26	stockDataFrame['Close'].rolling(window=5).mean().plot(ax=axPrice,color="blue",label='5天均線')
27	stockDataFrame['Close'].rolling(window=10).mean().plot(ax=axPrice,color="green",label='10天均線')
28	axPrice.legend(loc='best') #繪製圖例
29	axPrice.set_ylabel("價格(單位:元)")
30	axPrice.grid(linestyle='-.') #帶網格線        
31	#在axRSI子圖裡繪製RSI圖形
32	stockDataFrame['RSI6'].plot(ax=axRSI,color="blue",label='RSI6')
33	stockDataFrame['RSI12'].plot(ax=axRSI,color="green",label='RSI12')
34	stockDataFrame['RSI24'].plot(ax=axRSI,color="purple",label='RSI24')
35	plt.legend(loc='best') #繪製圖例
36	plt.rcParams['font.sans-serif']=['SimHei']       
37	axRSI.set_title("RSI圖")#設定子圖的標題
38	axRSI.grid(linestyle='-.') #帶網格線
39	#設定x軸座標標籤和旋轉角度
40	major_index=stockDataFrame.index[stockDataFrame.index%7==0]
41	major_xtics=stockDataFrame['Date'][stockDataFrame.index%7==0]
42	plt.xticks(major_index,major_xtics)
43	plt.setp(plt.gca().get_xticklabels(), rotation=30) 
44	plt.savefig('D:\\stockData\ch10\\600584RSI.png')

    在第18行裡,我們通過呼叫calRSI方法得到了三個週期的RSI資料。在第21行裡,設定了axPrice和axRSI這兩個子圖共享x軸標籤,在第23行裡繪製了K線圖,在第25行到第27行裡,繪製了3日、5日和10日的均線,在第32行到第34行裡,繪製了6日、12日和24日的三根RSI指標圖。在第44行裡通過savefig方法,把包含K線、均線和RSI指標線的圖形存在指定目錄中。     

45	#傳送郵件
46	def sendMail(username,pwd,from_addr,to_addr,msg):
47	    和之前sendMailWithPicAttachment.py案例中的一致
48	def buildMail(HTMLContent,subject,showFrom,showTo,attachfolder,attachFileName):
49	    message = MIMEMultipart()
50	    body = MIMEText(HTMLContent, 'html', 'utf-8')
51	    message.attach(body)
52	    message['Subject'] = subject
53	    message['From'] = showFrom
54	    message['To'] = showTo
55	    imageFile = MIMEImage(open(attachfolder+attachFileName, 'rb').read())
56	    imageFile.add_header('Content-ID', attachFileName)
57	    imageFile['Content-Disposition'] = 'attachment;filename="'+attachFileName+'"'
58	    message.attach(imageFile)
59	    return message

 第46行定義的sendMail方法和之前案例中的完全一致,所以就不給出詳細的程式碼。本案例與之前不同的是,在第48行裡,專門定義了buildMail方法,用來組裝郵件物件,郵件的諸多要素由該方法的引數所定義。

    具體而言,在第49行裡定義郵件型別是MIMEMultipart,也就是說帶附件的郵件,在第50行和第51行裡,根據引數HTMLContent構建了郵件的正文,在第52行到第54行裡,設定了郵件的相關屬性值,從第55行到第57行,根據入參構建了MIMEImage型別的圖片類附件,在第58行裡,通過attach方法把附件併入郵件正文。    

60	subject='RSI效果圖'
61	attachfolder='D:\\stockData\\ch10\\'
62	attachFileName='600584RSI.png'
63	HTMLContent = '<html><head></head><body>'\
64	 '<img src="cid:'+attachFileName+'"/>'\
65	 '</body></html>'
66	message = buildMail(HTMLContent,subject,'[email protected]','[email protected]',attachfolder,attachFileName) 
67	sendMail('hsm_computer','xxx','[email protected]','[email protected]',message.as_string())  
68	#最後再繪製
69	plt.show()

 在第60行到第66行,我們設定了郵件的相關屬性值,並在第66行裡,通過呼叫buildMail方法建立了郵件物件message,在第第67行裡,通過呼叫sendMail方法傳送郵件,最後在第69行通過show方法繪製了圖形。請大家注意本案例中的三個細節。

    第一,           第64行cid的值需要和第56行的Content-ID值一致,否則圖片只能以附件的形式傳送,無法在郵件正文裡以富文字的格式展示。

    第二,           我們是先構建和傳送郵件,再通過第69行的程式碼繪製圖形,如果次序顛倒先繪製圖形後傳送郵件的話,show方法被呼叫後程序會阻塞在這個位置,程式碼無法繼續執行。要等到手動關閉掉由show方法彈出的視窗後,才會觸發sendMail方法傳送郵件。

    第三,           在本案例的第48行,我們專門封裝了用於構建郵件物件的buildMail方法,在其中通過引數動態地構建郵件,這樣如果要傳送其它郵件,則可以呼叫該方法,從而能提升程式碼的重用性。

    執行上述程式碼,我們能在彈出的窗口裡看到K線、均線和RSI指標圖整合後的效果圖,而且能在郵件的正文裡看到相同的圖。

     

4  RSI指標對買賣點的指導意義

    一般來說,我們把6日、12日和24日的RSI指標稱為為短期、中期和長期指標。和KDJ指標一樣,RSI指標也有超買和超賣區。

    具體而言,當RSI值在50到70間波動時,表示當前屬於強勢狀態,如繼續上升,超過80時,則到超買區,極可能在短期內轉升為跌。反之RSI值在20到50之間時,說明當前市場處於相對弱勢,如下降到20以下,則進入超賣區,股價可能出現反彈。

    在講述RSI交易策略前,我們先來講述下在實際操作中總結出來的RSI指標的缺陷。

    第一,週期較短(比如6日)的RSI指標比較靈敏,但快速震盪的次數較多,可靠性相對差些,而週期較長(比如24日)的RSI指標可靠性強,但靈敏度不夠,經常會“滯後”的情況。

    第二,當數值在40到60間波動時,往往參考價值不大,具體而言,當數值向上突破50臨界點時,表示股價已轉強,反之向下跌破50時則表示轉弱,不過在實踐過程中,經常會出現RSI跌破50後股價卻不下跌,而突破50後股價不漲。

    綜合RSI演算法、相關理論以及缺陷,我們來講述下實際操作中常用的基於該指標的賣策略。

    第一,RSI短期指標(6日)在20以下超賣區與中長期RSI(12日或24日)發生黃金交叉,即6日線上穿12日或24日線,則說明即將發生反彈行情,如果其它技術或政策等方面沒太大問題,可以適當買進。

    第二,反之,RSI短期指標(6日)在80以上超買區與中長期RSI(12日或24日)發生死亡交叉,即6日線下穿12日或24日線,則說明可能會出現高位反轉的情況,如果沒有其它利好訊息,可以考慮賣出。

5 計算賣點,以郵件的形式傳送

    根據上文描述,這裡採用的基於RSI的買點策略是,RSI6日線在20以下與中長期RSI(12日或24日)發生黃金交叉。在如下的calRSIBuyPoints.py案例中,我們據此策略計算600584(長電科技)從2018年9月到2019年5月間的賣點,而且通過郵件傳送買點日期。     

1	#!/usr/bin/env python
2	#coding=utf-8
3	import pandas as pd
4	import smtplib
5	from email.mime.text import MIMEText
6	from email.mime.image import MIMEImage
7	from email.mime.multipart import MIMEMultipart
8	#計算RSI的方法,入參periodList傳入週期列表 
9	def calRSI(df,periodList):
10	    和DrawRSI.py案例中的一致,省略相關程式碼
11	filename='D:\\stockData\ch10\\6005842018-09-012019-05-31.csv'
12	df = pd.read_csv(filename,encoding='gbk')
13	list = [6,12,24] #週期列表
14	#呼叫方法計算RSI
15	stockDataFrame = calRSI(df,list) 

    在上述程式碼的第15行裡,我們通過呼叫calRSI方法計算RSI指標值,這部分和10.3.2部分的calRSIBuyPoints.py相關程式碼非常相似,所以就不再重複說明。   

16	cnt=0    
17	sellDate=''
18	while cnt<=len(stockDataFrame)-1:
19	    if(cnt>=30):#前幾天有誤差,從第30天算起
20	        try:        
21	            #規則1,這天RSI6高於80
22	            if stockDataFrame.iloc[cnt]['RSI6']<80:
23	                #規則2.1,當天RSI6下穿RSI12
24	                if  stockDataFrame.iloc[cnt]['RSI6']<stockDataFrame.iloc[cnt]['RSI12'] and stockDataFrame.iloc [cnt-1]['RSI6']>stockDataFrame.iloc[cnt-1]['RSI12']:
25	                    sellDate = sellDate+stockDataFrame.iloc[cnt]['Date'] + ','
26	                    #規則2.2,當天RSI6下穿RSI24
27	                if  stockDataFrame.iloc[cnt]['RSI6']<stockDataFrame.iloc[cnt]['RSI24'] and stockDataFrame.iloc[cnt-1] ['RSI6']>stockDataFrame.iloc[cnt-1]['RSI24']:
28	                    if sellDate.index(stockDataFrame.iloc[cnt]['Date']) == -1:
29	                        sellDate = sellDate+stockDataFrame.iloc[cnt]['Date'] + ','
30	        except:
31	            pass                
32	    cnt=cnt+1
33	print(sellDate)

    在第18行到第32行的while迴圈裡,我們計算了基於RSI的賣點,在第22行的語句裡,我們制定了第一個規則:RSI6數值大於80,在第23行和第27行,我們在規則一的基礎上制定了兩個並行的規則,通過上述程式碼,我們會在sellDate物件裡存放RSI6大於80並且RSI6下穿RSI12(或RSI24)的交易日,這些交易日即為賣點。    

34	def sendMail(username,pwd,from_addr,to_addr,msg):
35	    和之前calRSIBuyPoints.py案例中的完全一致
36	def buildMail(HTMLContent,subject,showFrom,showTo,attachfolder,attachFileName):
37	    和之前calRSIBuyPoints.py案例中的完全一致
38	subject='RSI賣點分析'
39	attachfolder='D:\\stockData\\ch10\\'
40	attachFileName='600584RSI.png'
41	HTMLContent = '<html><head></head><body>'\
42	 '賣點日期' + sellDate + \
43	 '<img src="cid:'+attachFileName+'"/>'\
44	 '</body></html>'
45	message = buildMail(HTMLContent,subject,'[email protected]','[email protected]',attachfolder,attachFileName) 
46	sendMail('hsm_computer','xxx','[email protected]','[email protected]',message.as_string()) 

 第34行和第36行的兩個用於傳送郵件和構建構建的方法和之前案例的完全一致,所以就不再額外說明。

   在第38行我們定義的郵件標題是“RSI賣點分析”,在第41行定義的描述正文的HTMLContent物件裡,我們存放的也是“賣點日期”,最終是通過第46行呼叫sendMail方法傳送郵件。

    執行上述程式碼,我們能看到如下圖所示的郵件,其中包括了賣點日期和指標圖。這裡通過計算得出的賣點日期比較多,經分析,這些日期之後,股價多有下跌情況。

        

 

6 總結和版權說明

    本文是給程式設計師加財商系列,之前的系列文如下:

    以預測股票漲跌案例入門基於SVM的機器學習  

    用python的matplotlib和numpy庫繪製股票K線均線和成交量的整合效果(含量化驗證交易策略程式碼)  

    用python的matplotlib和numpy庫繪製股票K線均線的整合效果(含從網路介面爬取資料和驗證交易策略程式碼)       本文力爭做到詳細,比如程式碼按行編號,並針對行號詳細解釋,且圖文並茂,所以如果大家感覺可以,請儘量幫忙推薦一下。 本文的內容即將出書,在出版的書裡,是用股票案例和大家講述Python入門時的知識點,敬請期待     

    有不少網友轉載和想要轉載我的博文,本人感到十分榮幸,這也是本人不斷寫博文的動力。關於本文的版權有如下統一的說明,抱歉就不逐一回復了。

    1 本文可轉載,無需告知,轉載時請用連結的方式,給出原文出處,別簡單地通過文字方式給出,同時寫明原作者是hsm_computer。

    2 在轉載時,請原文轉載 ,謝絕洗稿。否則本人保留追究法律責任的權利。

&n