点赞、关注再看,养成良好习惯
Life is short, U need Python
初学量化投资实战,[快来点我吧]
由于最近博主比较忙,所以有一段时间没写博文了;另外,量化投资基础系列以及量化投资实战系列是博主擅长和看重的栏目,故迟迟未与大家见面。由于前段时间学院和一个公司合作搭建量化投资虚拟仿真实验中心,需要我提供搭建示例(思路)以及一些选股因子指标和择时买卖因子指标的制定(提供用Python写的指标因子代码以及量化交易策略回测代码等),由于是为了教学服务,公司不建议做的那么复杂(可能工作量太大吧),在择时交易指标上就准备先做一个因子(选股因子还是有4-6个因子的,虽然也很少),于是博主就选择了KDJ技术指标。接下来,博主就以KDJ交易策略作为量化投资实战系列的开篇之作展现给博友们(时间仓促,如若不对之处,请博友们留言指正,先谢谢大家了)!
KDJ指标又叫随机指标,是一种相当新颖、实用的技术分析指标,它起先用于期货市场的分析,后被广泛用于股市的中短期趋势分析,是期货和股票市场上最常用的技术分析工具。
随机指标KDJ一般是用于股票分析的统计体系,根据统计学原理,通过一个特定的周期(常为9日、9周等)内出现过的最高价、最低价及最后一个计算周期的收盘价及这三者之间的比例关系,来计算最后一个计算周期的未成熟随机值RSV,然后根据平滑移动平均线的方法来计算K值、D值与J值,并绘成曲线图来研判股票价格走势。
随机指标KDJ是以最高价、最低价及收盘价为基本数据进行计算,得出的K值、D值和J值分别在指标的坐标上形成的一个点,连接无数个这样的点位,就形成一个完整的、能反映价格波动趋势的KDJ指标。它主要是利用价格波动的真实波幅来反映价格走势的强弱和超买超卖现象,在价格尚未上升或下降之前发出买卖信号的一种技术工具。它在设计过程中主要是研究最高价、最低价和收盘价之间的关系,同时也融合了动量观念、强弱指标和移动平均线的一些优点,因此,能够比较迅速、快捷、直观地研判行情。由于KDJ线本质上是一个随机波动的观念,故其对于掌握中短期行情走势比较准确。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 读取标普500的指数数据
GSPC = pd.read_csv('GSPC.csv',index_col='Date')
GSPC = GSPC.iloc[:,1:]
GSPC.index = pd.to_datetime(GSPC.index)
# 提取收盘价、最高价、最低价数据
close = GSPC.Close
high = GSPC.High
low = GSPC.Low
# 获取日期数据
date = close.index.to_series()
ndate = len(date)
# 定义初始变量最高价High,取值为0
periodHigh = pd.Series(np.zeros(ndate-8),index=date.index[8:])
# 定义初始变量最低价Low,取值为0
periodLow = pd.Series(np.zeros(ndate-8),index=date.index[8:])
# 定义初始变量RSV,取值为0
RSV = pd.Series(np.zeros(ndate-8),index=date.index[8:])
# 计算RSV的值
for j in range(8,ndate):
period = date[j-8:j+1]
i = date[j]
periodHigh[i] = high[period].max()
periodLow[i] = low[period].min()
RSV[i] = 100*(close[i]-periodLow[i])/(periodHigh[i]-periodLow[i])
periodHigh.name = 'periodHigh'
periodLow.name = 'periodLow'
RSV.name = 'RSV'
# 绘制标普500指数收盘价曲线图和RSV曲线图
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
close1 = close['2015']
RSV1 = RSV['2015']
Cl_RSV = pd.DataFrame([close1,RSV1]).transpose()
Cl_RSV.plot(subplots=True,title='未成熟随机指标RSV')
plt.show()
结果如图所示:
从上可以归纳出一个规律,当市场处于连续上涨行期时,未成熟随机指标 RSV 取值也逐渐增大,而且可能在较多日期中取值为 100;当市场处于连续下跌行期时,RSV 取值可能在较多日期中取值为0。当 RSV 连续多期取值 100 或者 0 时,RSV 则会出现所谓“钝化”的现象,例如,当收盘价在上涨行情高位变化时,RSV 在一段时间内取值均为 100,不随收盘价的变化而波动,则失去了捕捉收盘价变化的作用。此外,从上图中可以观察到,RSV 值的波动幅度较大,也可能会造成“假信号”。其中一种可能状况是,在上涨行期中收盘价上涨幅度稍微增大,则可能造成 RSV 取值过大,进而释放出市场处于“超买”行期的“假信号”。
为了解决 RSV 波动幅度较大的问题,引入 K 指标,它是对 RSV 值进行平滑得到的结果。
K 值由前一日的 K 值和当期 RSV 值经过一定权重调整后相加而得到,一般来说,K 值的计算为:
其中, K t K_t Kt为第 t t t日 K K K 值, K t − 1 K_{t-1} Kt−1为第 t − 1 t-1 t−1日 K K K值,RSV t _t t 表示第 t t t日的 RSV 值。
D 值由前一日的 D 值和当期 K 值经过一定权重调整后相加而得到,一般来说,D 值的计算为:
其中, D t D_t Dt为第 t 日 D 值, D t − 1 D_{t-1} Dt−1为第 t-1 日 D 值, K t K_t Kt 表示第 t 日的 K 值。
此外,在计算第1期的 K 值和 D 值时,如果没有指定,则 K 值和 D 值默认取值为50。在K值和D值的求解过程中,平滑权重 2/3 和 1/3 是较为常用的权重,这两个权重也可以根据股价走势的特点进行适当修改。
令 K 1 K_1 K1 = 50, D 1 D_1 D1= 50,通过递归和迭代,可以推出K值是由未成熟随机指标 RSV 通过指数移动平均而得到。同理,D 值是 K 值的指数移动平均数(Exponential Moving Average, EMA)。
# 在RSV基础上增加前2期的数据=50
RSV1 = pd.Series([50,50],index=date[6:8]).append(RSV)
RSV1.name = 'RSV'
# 利用 RSV 值计算 K 值
KValue = pd.Series(0.0,index=RSV1.index)
KValue[0] = 50
for i in range(1,len(RSV1)):
KValue[i] = 2/3*KValue[i-1]+RSV1[i]/3
KValue.name = 'KValue'
# 利用 K 值计算 D 值
DValue = pd.Series(0.0,index=RSV1.index)
DValue[0] = 50
for i in range(1,len(RSV1)):
DValue[i] = 2/3*DValue[i-1]+KValue[i]/3
DValue.name='DValue'
# 提取 K 值和 D 值
KValue = KValue[1:]
DValue = DValue[1:]
# 直观展示 RSV、K 值和 D 值
import matplotlib.pyplot as plt
plt.figure(figsize=(10,6))
plt.subplot(211)
plt.title('2015年标准普尔500的收盘价')
plt.plot(close['2015'])
plt.subplot(212)
plt.title('2015年标准普尔500的RSV与KD线')
plt.plot(RSV['2015'])
plt.plot(KValue['2015'],linestyle='dashed')
plt.plot(DValue['2015'],linestyle='-.')
plt.subplots_adjust(hspace=0.4) # 子图间隔设置
plt.legend()
plt.show()
J 指标是 KD 指标的辅助指标,进一步反映了 K 指标和 D 指标的偏离程度。第 t 日 J 值计算公式为:
其中, J t J_t Jt 为第 t 日 J 值, K t K_t Kt 为第 t 日 K 值, D t D_t Dt 为第 t 日 D 值。
# 计算J值:J = 3K-2D
JValue = 3*KValue - 2*DValue
JValue.name = 'JValue'
不再多说了,直接上代码分析吧!
# 绘制 KDJ 指标曲线图
import matplotlib
matplotlib.rcParams['axes.unicode_minus']=False # 负号显示
import matplotlib.pyplot as plt
plt.figure(figsize=(10,8))
plt.subplot(211)
plt.title('2015年标准普尔500的收盘价')
plt.plot(close['2015'])
plt.subplot(212)
plt.title('2015年标准普尔500的RSV与KDJ线')
plt.plot(RSV['2015'])
plt.plot(KValue['2015'],linestyle='dashed')
plt.plot(DValue['2015'],linestyle='-.')
plt.plot(JValue['2015'],linestyle='--')
plt.subplots_adjust(hspace=0.4) # 子图间隔设置
plt.legend(loc='upper left')
plt.show()
结果如图所示:
KDJ 指标的四种线图与收盘价曲线走势大致相同,在 KDJ 指标的四种线图中,RSV线的波动幅度较大,K 线与 D 线的走势很类似,J 线与 K 线、D 线走势相比波动略大。在四种指标的取值上,RSV、K 值和 D 值的取值范围都在 0~100 之间;而 J 值的取值可以超过 100,也可以低于0,例如,从 J 线图中可以看出J值的取值范围为 -20~120。
【1】在 KDJ 指标的取值上,K 值与 D 值的取值范围是 0~100。
【2】对于J 值,当J值大于 100,可以视为超买区,当J值小于 0,视为超卖区。
【3】此外,在 K 线、D 线的交叉情况也可以释放出买入、卖出信号。
【1】KD 指标交易策略
将 KDJ 指标运用于标普500中,通过 K 线、D 线分别捕捉超买点和超卖点,构造交易策略函数,计算 KD 指标交易策略的收益率,再对 KD 指标交易策略进行评价。
# K、D值捕捉超买、超卖信号
# K值大于85,超买,signal=-1
# K值小于20,超卖,signal=1
# D值大于80,超买,signal=-1
# D值小于20,超卖,signal=1
KSignal = KValue.apply(lambda x: -1 if x>85 else 1 if x<20 else 0)
DSignal = DValue.apply(lambda x: -1 if x>80 else 1 if x<20 else 0)
KDSignal = KSignal + DSignal
KDSignal.name = 'KDSignal'
KDSignal[KDSignal>=1] == 1
KDSignal[KDSignal<=-1] == -1
# 定义交易策略函数
def trade(signal,price):
ret = ((price-price.shift(1))/price.shift(1))[1:]
ret.name = 'ret'
signal = signal.shift(1)[1:]
tradeRet = ret * signal + 0
tradeRet.name = 'tradeRet'
Returns = pd.merge(pd.DataFrame(ret),pd.DataFrame(tradeRet),left_index=True,
right_index=True).dropna()
return(Returns)
KDtrade = trade(KDSignal,close)
KDtrade.rename(columns={'ret':'Ret','tradeRet':'KDtradeRet'},inplace=True)
import ffn # 事先安装:pip install ffn
# 构造回测函数
def backtest(ret,tradeRet):
def performance(x):
winpct=len(x[x>0])/len(x[x!=0])
annRet=(1+x).cumprod()[-1]**(245/len(x))-1
sharpe=ffn.calc_risk_return_ratio(x)
maxDD=ffn.calc_max_drawdown((1+x).cumprod())
perfo=pd.Series([winpct,annRet,sharpe,maxDD],index=['win rate','annualized return','sharpe ratio','maximum drawdown'])
return(perfo)
BuyAndHold=performance(ret)
Trade=performance(tradeRet)
return(pd.DataFrame({ret.name:BuyAndHold,tradeRet.name:Trade}))
# 对KD交易策略进行回测
import matplotlib.pyplot as plt
plt.figure(figsize=(10,6))
cumRets1=(1+KDtrade).cumprod()
plt.plot(cumRets1.Ret,label='Ret')
plt.plot(cumRets1.KDtradeRet,'--',label='KDtradeRet')
plt.title('KD指标交易策略绩效表现')
plt.legend()
plt.show()
结果如图所示:
汉代散文家桓宽曾提出:“明者因时而变,知者随事而制”。在金融市场投资实战中,更是如此。对比上图可以看出,KD 指标在2014年上半年绩效表现优秀,但在2014年下半年和2015年表现较差。在实际运用 KD 指标时,除了谨记指标有一定的适用情境以外,更要因时制宜,才能趋利避害。
【2】KDJ 指标交易策略
J 线综合了 K 线和 D 线的信息,对于市场超买、超卖行情的判断也有一定的作用。J 值取值范围不局限于0~100之间,但 J 值低于 0 或者高于 100 出现的时机不多,当 J 值低于 0 时或者高于100 时,预示着市场多空双方的力量可能会出现一些微妙的变化,该指标往往会有较高的可靠程度。接下来,在 KD 指标的基础上,加入 J 指标交易策略,修改买卖点交易信号,并进行交易后测。
# J值捕捉超买、超卖信号
# J值大于100,超买,signal=-1
# J值小于0 ,超卖,signal= 1
KSignal = KValue.apply(lambda x: -1 if x>85 else 1 if x<20 else 0)
DSignal = DValue.apply(lambda x: -1 if x>80 else 1 if x<20 else 0)
JSignal = JValue.apply(lambda x: -1 if x>100 else 1 if x<0 else 0)
KDJSignal = KSignal + DSignal + JSignal
KDJSignal = KDJSignal.apply(lambda x: 1 if x>=2 else -1 if x<=-2 else 0)
KDJtrade = trade(KDJSignal,close)
KDJtrade.rename(columns={'ret':'Ret','tradeRet':'KDJtradeRet'},inplace=True)
backtest(KDJtrade.Ret,KDJtrade.KDJtradeRet)
KDJCumRet = (1+KDJtrade).cumprod()
# 对KDJ交易策略进行回测
import matplotlib.pyplot as plt
plt.figure(figsize=(10,6))
plt.plot(KDJCumRet.Ret,label='Ret')
plt.plot(KDJCumRet.KDJtradeRet,'--',label='KDJtradeRet')
plt.title('KDJ指标交易策略绩效表现')
plt.legend(loc='upper left')
plt.show()
结果如图所示:
从上图可以看出,KDJ 指标和KD指标交易策差别并没体现出来(有待进一步探讨及设置新的投资策略)。其实,如果仔细观察2014年10月10日之前的绩效,反而KD指标交易策略比KDJ指标交易策略要好的(当然这与对应策略买卖点的策略值的设置有关,感兴趣的博友不妨编个程序遍历跑一下,看看能不能选到最优的策略值)!
下一步,博主将继续探讨KDJ指标的其它交易策略(其他买卖策略值的选取以及KDJ策略的‘金叉’和‘死叉’等策略),敬请期待中!
- 写作不易,切勿白剽
- 博友们的点赞和关注就是对博主坚持写作的最大鼓励
- 持续更新,未完待续…