目录
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 的相关方案,因为拥有更高品质的股价资料库是做出最贴近现实的回测表现的重要条件!
电子报订阅