RSI指標回測實戰

photo by Chris Liverani on Unsplash

本文重點概要

  • 文章難度:★★☆☆☆
  • RSI指標策略的表現
  • 閱讀建議:本文以 RSI作為交易策略的判斷依據,以視覺化的方式觀察交易訊號以及買賣點,接著再利用函數計算出報酬率,做出 RSI指標交易的回測。如果對回測或是技術指標不了解的讀者,歡迎先行閱讀 技術分析簡介與回測,詳細理解回測的執行流程。

前言

RSI是判斷股市當中買賣雙方力道強弱的動量技術指標,綜合考量上漲下跌的幅度和天數,評估市場買賣超的情形,決定進出場的策略。RSI指標的計算流程如下:

  1. 依據隔日價差,計算 n日內平均的漲跌幅
  2. 計算相對強度(RS):n日內漲幅平均值 / n日內跌幅平均值
  3. 計算相對強弱指標(RSI):相對強度(RS) / (1 + 相對強度(RS)) × 100

RSI指標的判斷依據如下:

  1. RSI < 30為賣超情形,RSI > 70為買超情形,RSI在50之間波動為買賣方力量持平,上述(30,70)組合也有較保守的(20,80)判斷依據。
  2. RSI指標背離,也就是RSI走勢與股價走勢不一致,代表市場將出現反轉,是買賣訊號的一個依據。
  3. 黃金交叉,短天期 RSI向上突破長天期 RSI,代表市場即將進入多頭。
  4. 死亡交叉,短天期 RSI向下突破長天期 RSI,代表市場即將進入空頭。

RSI的指標鈍化問題:由於 RSI是將漲、跌分開來處理,因此若市場在短期內皆為漲勢,則會使跌幅的參考數據過於稀少,導致 RSI指標判斷失真,反之亦然。鈍化的情形尤其容易發生在高低檔階段,而本文也嘗試以綜合 RSI區間及長短天期 RSI交叉的方式降低鈍化影響。

編輯環境及模組需求

本文使用 MacOS 並以 Jupyter Notebook 作為編輯器

# 基本套件 import pandas as pd import numpy as np import copy # 視覺化套件 import plotly.graph_objects as go import plotly.express as px # TEJ API import tejapi tejapi.ApiConfig.api_key = 'Your Key' tejapi.ApiConfig.ignoretz = True

資料庫使用

資料處理

Step 1. 股價資料撈取

stock_data = tejapi.get('TWN/APRCD',            coid= '2324', # 仁寶            mdate={'gte': '2020-01-01', 'lte':'2021-12-14'},            opts={'columns': ['coid', 'mdate', 'open_d', 'close_d']},            chinese_column_name=True,            paginate=True)

本文以仁寶(2324)從2020年初至今(2021–12–14)的資料範圍作為示範,藉此了解筆記型電腦代工廠商在疫情下的股價波動,以及如何使用RSI,決定進出場時機。

 資料表(一)

Step 2. 計算14日RSI、7日RSI

# 計算隔日價差以及分類每日漲跌 stock_data['隔日差價'] = stock_data['收盤價(元)'].diff() stock_data['上漲'] = stock_data['隔日差價'].clip(lower = 0) stock_data['下跌'] = (-1) * stock_data['隔日差價'].clip( upper = 0) # 14日RSI指標 stock_data['14日上漲均值'] = stock_data['上漲'].ewm(com = 14, adjust = False).mean() stock_data['14日下跌均值'] = stock_data['下跌'].ewm(com = 14, adjust = False).mean() stock_data['14日相對強弱值'] = stock_data['14日上漲均值'] / stock_data['14日下跌均值'] stock_data['14日相對強弱指標'] = stock_data['14日相對強弱值'].apply(lambda rs : rs/(1+rs)*100) # 7日RSI指標 stock_data['7日上漲均值'] = stock_data['上漲'].ewm(com = 7, adjust = False).mean() stock_data['7日下跌均值'] = stock_data['下跌'].ewm(com = 7, adjust = False).mean() stock_data['7日相對強弱值'] = stock_data['7日上漲均值'] / stock_data['7日下跌均值'] stock_data['7日相對強弱指標'] = stock_data['7日相對強弱值'].apply(lambda rs : rs/(1+rs)*100)
 資料表(二)

Step 3. RSI指標走勢圖 & 買賣超區間(30,70)(詳見完整程式碼)

透過比較RSI長短天期走勢以及買賣超區間,找到黃金交叉以及死亡交叉。需注意的是高檔階段仍有較嚴重的鈍化問題,但仍要在第一次死亡交叉時就出場,畢竟在實際投資的時候,無法絕對精準得預測未來。

圖(一)

Step 4. 找出買賣訊號與視覺化買賣點(詳見完整程式碼)

賣超階段:RSI < 30,準備買入;此外,降低RSI在低檔的鈍化影響,增加一項需要7日RSI向上突破14日RSI的條件,作為價格將要反轉的黃金交叉依據。

買超階段:RSI > 70,準備賣出;此外,降低RSI在高檔的鈍化影響,增加一項需要7日RSI向下突破14日RSI的條件,作為價格將要反轉死亡交叉依據。

signal = []
trade = 0
# 交易訊號只發生在買賣超階段與黃金交叉或死亡交叉同時發生的情境下
for i in range(len(stock_data)):
    if stock_data.loc[i, '14日相對強弱指標']  <= 30 and stock_data.loc[i-1, '14日相對強弱指標'] > stock_data.loc[i-1, '7日相對強弱指標'] and stock_data.loc[i, '14日相對強弱指標'] <= stock_data.loc[i, '7日相對強弱指標'] and trade == 0:
        signal.append(1000)
        trade = trade + 1
    elif stock_data.loc[i, '14日相對強弱指標'] >= 70 and stock_data.loc[i-1, '14日相對強弱指標'] < stock_data.loc[i-1, '7日相對強弱指標'] and stock_data.loc[i, '14日相對強弱指標'] >= stock_data.loc[i, '7日相對強弱指標'] and trade == 1:
        signal.append(-1000)
        trade = trade - 1
    else:
        signal.append(0)
stock_data['買賣股數'] = signal
圖(二)

計算報酬

MACD指標回測實戰裡,我們有詳細介紹考慮手續費、現金部位計算報酬率的方式,因為流程固定,本文以下方函數包裝。

def target_return(data, principal):     #計算成本     data['手續費'] = data['開盤價(元)']* abs(data['買賣股數'])*0.001425     data['手續費'] = np.where((data['手續費']>0)&(data['手續費'] <20), 20, data['手續費'])     data['證交稅'] = np.where(data['買賣股數']<0, data['開盤價(元)']* abs(data['買賣股數'])*0.003, 0)     data['摩擦成本'] = (data['手續費'] + data['證交稅']).apply(np.floor)     #計算資產價值     data['股票價值'] = data['買賣股數'].cumsum() * data['收盤價(元)']     data['現金價值'] = principal - data['摩擦成本'] + (data['開盤價(元)']* -data['買賣股數']).cumsum()      data['資產價值'] = data['股票價值'] + data['現金價值']     #計算報酬率     data['當日價值變動(%)'] = (data['資產價值']/data['資產價值'].shift(1) - 1)*100     data['累計報酬(%)'] = (data['資產價值']/principal - 1)*100     return data

本文單純考量一般股票買賣,並無考慮融券融資情形,所以沒有計算保證金的流程,只要將買賣股數的資料整理好,並決定期初的現金部位,即可算出交易成本以及報酬率。本文選擇投入20000元

RSI_return = target_return(data = stock_data, principal = 20000)
資料表(三)

加入買入持有策略以及大盤績效,作為此次RSI交易策略的比較基準。

・買入持有策略

bh_data = copy.deepcopy(stock_data) bh_data['買賣股數'] = 0 bh_data.loc[0, '買賣股數'] = 1000 bh_data.loc[len(bh_data)-1, '買賣股數'] = -1000 # 去除RSI指標策略才會用到的參數 bh_data = bh_data.drop(['隔日差價', '上漲', '下跌', '14日上漲均值', '14日下跌均值', '14日相對強弱值', '14日相對強弱指標','7日上漲均值', '7日下跌均值', '7日相對強弱值', '7日相對強弱指標'], axis = 1) bh_return = target_return(data=bh_data, principal = 20000)
資料表(四)

・大盤績效

market = tejapi.get('TWN/APRCD',
                   coid = 'Y9997',
                   mdate = {'gte':'2020-01-01', 'lte':'2021-12-14'},
                   opts = {'columns':['coid','mdate',   'close_d','roi']},
                   chinese_column_name = True)
market['累計報酬(%)'] = (market['報酬率%'].apply(lambda x:0.01*x +1).cumprod() - 1)*100

以市場指數報酬(Y9997)代表市場的表現,並計算出累計報酬率。

資料表(五)

報酬績效比較

Step 1. 累計報酬比較(詳見完整程式碼)

圖(三)

從累計報酬率知道RSI策略交易在疫情爆發後的低檔階段進場,並且在市場賣超電子股前的高檔階段出場(因大宗商品上漲而市場轉投入傳產股),此外,也剛好避開台灣疫情徹底失守對股市的影響,可是在這之間還是有一些相對高低檔的波段,因為交易訊號的觸發標準較嚴格,而沒有抓準。

Step 2. 累計報酬比較(詳見完整程式碼)

資料表(六)

透過績效比較可以發現運用RSI的交易策略,在報酬率或波動度上的表現都優於買入持有,代表RSI是有機會找到適當的進出場時機,提高投資效益;此外,跟大盤績效的比較,RSI策略沒有突出的報酬率,但是在波動度上則更加穩定,這是因為避開2020年初的全球股災以及2021年台灣本土疫情失守的空頭階段,讓RSI策略的夏普值優於大盤績效。

結論

藉由上述內容,讀者應該可以發現RSI在高低檔的鈍化情形相當嚴重,所以本文加入長短天期交叉的條件,使RSI策略更有穩健性,當然也相對犧牲更多的交易機會,這是個別投資人需要自己去衡量的結果,畢竟技術指標的運用因人而異,所以歡迎對各種交易回測有興趣的讀者,選購TEJ E-Shop的相關方案,用高品質的資料庫,建構出適合自己的交易策略。

完整程式碼

延伸閱讀

相關連結

返回總覽頁
Procesing