Table of Contents
KD指標是技術分析常見的指標之一,主要用於判斷股價當前的強弱程度、可能反轉的時機,接著再產生進出場的訊號。以下為KD指標的計算流程:
RSV = ((當日收盤價-近N日的最低價)/(近N日的最高價-近N日的最低價))*100
K值 = 昨日K值 × (2/3) + 當日RSV × (1/3)
D值 = 昨日D值 × (2/3) + 當日K值 × (1/3)
從算式來看,可以把RSV解讀成當日股價相較於近N日 (本文N = 9)股價,是屬於較強勢還是弱勢。K值,又被稱為快線,因為受到當日股價強弱的影響較大;而D值計算的原理如同再進行一次平滑,故對當前股價變化反應較慢。而本文採用以下策略進行回測:
K ≤ 20,買入1000股,因其代表股價處於較弱、市場過冷
K ≥ 80,賣出1000股,代表市場過熱,因此選擇獲利了結
本文使用 Windows OS 並以 Jupyter Notebook 作為編輯器
#基本功能 import pandas as pd import numpy as np import copy
#繪圖 import plotly.graph_objects as go import plotly.express as px
#TEJ import tejapi tejapi.ApiConfig.api_key = 'Your Key' tejapi.ApiConfig.ignoretz = True
stock_data = tejapi.get('TWN/APRCD', coid= '2002', mdate={'gte': '2021-01-01'}, opts={'columns': ['coid', 'mdate', 'open_d', 'close_d']}, chinese_column_name=True, paginate=True)
本文以撈取以中鋼(2002)於2021年初至今的股價做為示範,欄位選擇開盤價與收盤價,前者計算買賣價格與成本,後者用於計算買賣訊號
kd_data = copy.deepcopy(stock_data)
#設9天 kd_data['9_high'] = kd_data['收盤價(元)'].rolling(9).max() kd_data['9_low'] = kd_data['收盤價(元)'].rolling(9).min()
#rsv kd_data['rsv'] = ((kd_data['收盤價(元)']-kd_data['9_low'])/(kd_data['9_high']-kd_data['9_low']))*100 kd_data['rsv'] = kd_data['rsv'].fillna(50)
因為之後還需要用到乾淨的 stock_data
,所以一開始利用 deepcopy()
複製其的內容到 kd_data
,接著計算出近9日的最高、最低股價。最後即可計算出RSV值,缺值的話以50填充
#初始化kd與買賣股數 kd_data['k'] = 0 kd_data['d'] = 0 kd_data['買賣股數'] = 0
#迴圈修正k d值 hold = 0 for i in range(len(kd_data)): #先算出當期的k d 值 if i == 0: kd_data.loc[i,'k'] = 50 kd_data.loc[i,'d'] = 50 else: kd_data.loc[i,'k'] = (2/3* kd_data.loc[i-1,'k']) + (kd_data.loc[i,'rsv']/3) kd_data.loc[i,'d'] = (2/3* kd_data.loc[i-1,'d']) + (kd_data.loc[i,'k']/3) if kd_data.loc[i, 'k'] <= 20: if hold == 0: kd_data.loc[i+1, '買賣股數'] = 1000 hold = 1 elif kd_data.loc[i,'k'] >= 80: if hold > 0: kd_data.loc[i+1,'買賣股數'] = -1000 hold = 0
在初始化KD值與買賣股數之後,進入到迴圈計算每日的KD值,與此同時標示出訊號出現時的買賣股數。而因為KD值皆需要用到前一筆的資訊,所以先將第一筆的K、D值預設為50。這邊裡用 hold
表示手中是否有持股 (1 =有,0代表沒有),若遇到買點時 (K ≤ 20)、且手中無持股時,則於隔日開盤時買入1000股;遇到賣點時 (K ≥ 80)、且手中有持股,則於隔日開盤賣出
在MACD指標回測實戰裡,我們有詳細介紹考慮手續費、現金部位計算報酬率的方式,因為流程固定,本文以下方函數包裝。
def ret(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
只要準備好擁有買賣股數的資料,以及選擇期初的現金部位大小,即可算出交易成本以及報酬率。本文選擇投入30000元
kd_return = ret(data = kd_data, principal = 30000)
另外,為了看出此策略的相對表現,我們以買入持有策略、市場表現作為比較基準
bh_data = copy.deepcopy(stock_data) bh_data['買賣股數'] = 0 bh_data.loc[0, '買賣股數'] = 1000 bh_data.loc[len(bh_data)-1, '買賣股數'] = -1000 bh_return = ret(data=bh_data, principal = 30000)
這邊也是利用 deepcopy()
取得股價資料,期初買入、期末賣出,期初現金亦為30000
market = tejapi.get('TWN/APRCD', coid = 'Y9997', mdate = {'gte':'2021-01-01'}, opts = {'columns':['coid','mdate', 'roi']}, chinese_column_name = True) market['累計報酬(%)'] = (market['報酬率%'].apply(lambda x:0.01*x +1).cumprod() - 1)*100
撈取市場報酬指數 (Y9997)的報酬率來代表市場的表現,並計算出累計報酬
可以看到今年台股市場的報酬表現相對平淡,買入持有策略的報酬基本上是跟著股價連動,因此受惠於今年中鋼5月左右的漲勢;而KD策略報酬除了考慮股價,亦考慮手中是否有股票部位,像是5月時的累計報酬率沒有變化,代表當時是滿手現金,而錯過了這波上漲的行情。兩者的表現於近期才開始收斂。
雖說KD策略與買入持有的年化報酬接近,但如果考慮風險後,KD策略的整體表現較佳,也優於2021年市場的表現。
透過以上的流程,相信讀者除了更了解KD指標的內涵,也能發現KD指標的侷限性。例如KD指標會在市場過熱時出現賣出訊號,然而面對大多頭的行情時,可能會因此錯過後續的漲幅,而過度頻繁的交易,也會驅使累計報酬率跟買入持有相當,這樣根本就是白忙一場。因此還是需要搭配其他指標來進行輔助進出場判斷,若讀者對於技術指標的回測有興趣,歡迎選購 TEJ E-Shop 的相關方案,因為擁有更高品質的股價資料庫是做出最貼近現實的回測表現的重要條件!
電子報訂閱