顺势指标CCI由唐纳德拉姆伯特所创,是通过测量股价的波动是否已超出其正常范围,来预测股价变化趋势的技术分析指标。由于选用的计算周期不同,顺势指标CCI也包括日CCI指标、周CCI指标、年CCI 指标以及分钟CCI指标等很多种类型。经常被用于股市研判的是日CCI指标和。虽然它们计算时取值有所不同,但基本方法一样。
计算方法:
第一步:
#计算CCI
from jqdata import *
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
start = '2005-09-01'
end = '2019-7-12'
n = 20
a= 0.015
index = '000300.XSHG'
def cal_meandev(tp,matp):
mean_dev = (n-1)*[np.nan]
for i in range(len(tp)-n+1):
mean_dev.append(np.mean(abs(tp[i:i+n]-matp[i+n-1])))
return np.array(mean_dev)
extended_days = get_trade_days(end_date = start,count=n-1)
start=extended_days[0]
def get_data(start,end,n,a,index):
df = get_price(index,start_date=start,end_date=end,fields = ['close','high','low'] )
df['tp'] = (df['close']+df['high']+df['low'])/3
df['matp'] = df['tp'].rolling(n).mean()
mean_dev = cal_meandev(df['tp'],df['matp'])
df['mean_dev'] = mean_dev
df['cci'] = (df['tp']-df['matp'])/(a*df['mean_dev'])
return df
df = get_data(start,end,n,a,index)
df.tail(10)
close | high | low | tp | matp | mean_dev | cci | |
---|---|---|---|---|---|---|---|
2019-07-01 | 3935.81 | 3936.67 | 3886.91 | 3919.796667 | 3722.628833 | 86.435050 | 152.073982 |
2019-07-02 | 3937.17 | 3942.43 | 3918.94 | 3932.846667 | 3737.305333 | 93.096867 | 140.027150 |
2019-07-03 | 3893.53 | 3927.44 | 3878.84 | 3899.936667 | 3751.984500 | 94.680833 | 104.176077 |
2019-07-04 | 3873.10 | 3906.89 | 3854.69 | 3878.226667 | 3765.470667 | 92.470267 | 81.291716 |
2019-07-05 | 3893.20 | 3900.31 | 3857.93 | 3883.813333 | 3780.949000 | 85.730533 | 79.990430 |
2019-07-08 | 3802.79 | 3875.75 | 3775.03 | 3817.856667 | 3791.796000 | 75.320200 | 23.066558 |
2019-07-09 | 3793.13 | 3811.10 | 3774.76 | 3792.996667 | 3797.113500 | 68.925133 | -3.981937 |
2019-07-10 | 3786.74 | 3810.40 | 3774.23 | 3790.456667 | 3801.854333 | 64.077667 | -11.858179 |
2019-07-11 | 3785.22 | 3830.74 | 3774.41 | 3796.790000 | 3807.739667 | 58.192333 | -12.544226 |
2019-07-12 | 3808.73 | 3820.24 | 3774.52 | 3801.163333 | 3814.207167 | 51.724833 | -16.811826 |
标的为沪深300指数日频数据(2005-09-01至2019-07-12)
当CCI上穿100,买入,信号为1;当CCI下穿-100,卖出,信号为-1。参数n为20。策略表现如下。
我们可以看出,双边交易策略的收益主要来自于几次牛市行情,在震荡行情中的表现普通,而这也是一般趋势类择时策略的特点。策略总收益7.68,夏普比率0.70,表现可以说不错。但是最大回撤达到了47%,这是我们不希望看到的。
#CCI策略双边交易表现
threshold = 100
def gen_signal(cci,threshold):
signal = n*[0]
for i in range(n,len(cci)):
if cci[i]>threshold and cci[i-1]<threshold:
signal.append(1)
elif cci[i]<-threshold and cci[i-1]>-threshold:
signal.append(-1)
else:
signal.append(0)
return np.array(signal)
def gen_position(signal,short):
position = [0]
if short:
short_pos =-1
else:
short_pos = 0
for i in range(1,len(signal)):
if signal[i] == 1:
position.append(1)
elif signal[i] == 0:
position.append(position[-1])
else:
position.append(short_pos)
return np.array(position)
def cal_ret(df):
ret =n*[0]
for i in range(n,len(df)):
ret.append(df['position'][i-1]*df['index_ret'][i])
ret = np.array(ret)
return ret
def cal_cum_ret(ret):
cum_ret = [1]
for i in range(len(ret)-1):
cum_ret.append(cum_ret[-1]*(1+ret[i]))
cum_ret = np.array(cum_ret)
return cum_ret
def back_test(df,threshold,short,plot=True):
df['index_ret'] = np.concatenate(([np.nan],np.array(df['close'][1:])/np.array(df['close'][:-1])))-1
df['signal'] = gen_signal(df['cci'],threshold)
df['position'] = gen_position(df['signal'],short)
ret = cal_ret(df)
df['ret'] = ret
cum_ret = cal_cum_ret(ret)
#计算指标
sum_dict={}
sum_dict['总收益']=cum_ret[-1]-1
sum_dict['日胜率'] = len(ret[ret>0])/(len(ret[ret>0])+len(ret[ret<0]))
max_nv = np.maximum.accumulate(cum_ret)
mdd = -np.min(cum_ret/max_nv-1)
sum_dict['最大回撤']=mdd
sum_dict['夏普比率']=ret.mean()/ret.std()*np.sqrt(240)
sum_df = pd.DataFrame(sum_dict,index=[0])
if plot:
#作图
plt.figure(1,figsize=(20,10))
plt.title('策略表现',fontsize=18)
plt.plot(df.index,cum_ret)
plt.plot(df.index,df['close']/df['close'][0])
plt.legend(['CCI策略','HS300'],fontsize=14)
plt.show()
return sum_df
back_test(df,threshold,True)
总收益 | 日胜率 | 最大回撤 | 夏普比率 | |
---|---|---|---|---|
0 | 7.610018 | 0.533868 | 0.471381 | 0.701032 |
我们通过寻找夏普比率最大的参数n来优化参数,最优参数为22。
#敏感性分析
ns = list(range(15,31))
sharpe_list =[]
for n in ns:
df2 = get_data(start,end,n,a,index)
summary = back_test(df2,threshold,True,False)
sharpe_list.append(summary['夏普比率'][0])
plt.figure(figsize=(15,8))
plt.plot(ns,sharpe_list)
[<matplotlib.lines.Line2D at 0x7f3600696358>]
优化后的策略成功避开了2009-2010年这一段时间的回撤,但仍未避开2018年底和2019年初的大回撤。但总的来说,相对于未优化的策略还是有所改善,总收益达到11.55,夏普比率0.80。
#双边交易最优参数回测
df2 = get_data(start,end,22,a,index)
back_test(df2,threshold,True,True)
总收益 | 日胜率 | 最大回撤 | 夏普比率 | |
---|---|---|---|---|
0 | 11.548231 | 0.538507 | 0.444329 | 0.800002 |
通过类似的参数优化方法,我们得到单边交易策略的最优参数22。 单边交易策略的总收益低于双边交易,为8.37,但收益更稳定:最大回撤仅为29%,夏普比率达到0.96。
#单边交易最优参数回测
df3 = get_data(start,end,22,a,index)
back_test(df2,threshold,False,True)
总收益 | 日胜率 | 最大回撤 | 夏普比率 | |
---|---|---|---|---|
0 | 8.368407 | 0.570156 | 0.298012 | 0.966564 |
#单边交易敏感性分析
ns = list(range(15,31))
sharpe_list =[]
for n in ns:
df3 = get_data(start,end,n,a,index)
summary = back_test(df3,threshold,False,False)
sharpe_list.append(summary['夏普比率'][0])
plt.figure(figsize=(15,8))
plt.title('单边交易敏感性分析')
plt.plot(ns,sharpe_list)
[<matplotlib.lines.Line2D at 0x7f3600272080>]
从该指标的设计来看,在一般常态行情下,CCI指标不会发生作用。当CCI扫描到异常股价波动时,也就是当CCI突破+/-100时,可以抓住市场趋势。但是在上涨/下跌行情中出现短暂反方向运动时,该策略会出现误判,又因为策略的信号频率不高,也因此带来比较大的回撤。
以下从这一点出发来构建的策略二:CCI指标上穿100买入,信号为1;当CCI指标回到100,并距离前次上穿100在m天之内,我们卖出,信号为-1。否则信号不变,直到下穿-100才卖出。(这里我们认为如果是短暂的上穿下穿,上涨的趋势并不明显,所以就卖出。如果是之前长期在100以上,说明是个长期的趋势,这时下穿100,并不马上卖出,要等到下穿-100才确认卖出)
下穿-100情况同上。测得最优参数为n=20,m=7
优化后的双边交易策略总收益大幅提升,达到25.49,夏普比率也达到0.99。策略成功避开了几次熊市的回撤。但是在2012-2014这一段震荡行情中,策略遭遇了52%的大幅回撤,这是优化策略更高的换手率造成的:高换手率可以避开熊市的回撤,但在震荡市中,过高的换手同样会造成回撤。
#优化的CCI择时策略(双边交易)
def gen_signal_2(cci,threshold,m):
signal = n*[0]
up_list=[]
down_list=[]
flag=0
for i in range(n,len(cci)):
if cci[i]>threshold and cci[i-1]<threshold:
signal.append(1)
up_list.append(i)
elif cci[i]<-threshold and cci[i-1]>-threshold:
signal.append(-1)
down_list.append(i)
elif cci[i]<threshold and cci[i-1]>threshold:
try:
if up_list[-1]<i-m:
signal.append(0)
else:
signal.append(-1)
except:
signal.append(0)
elif cci[i]>-threshold and cci[i-1]<-threshold:
try:
if down_list[-1]<i-m:
signal.append(0)
else:
signal.append(1)
except:
signal.append(0)
else:
signal.append(0)
print(len(signal))
return np.array(signal)
def back_test_2(df,threshold,m,short,plot=True):
df['index_ret'] = np.concatenate(([np.nan],np.array(df['close'][1:])/np.array(df['close'][:-1])))-1
df['signal'] = gen_signal_2(df['cci'],threshold,m)
df['position'] = gen_position(df['signal'],short)
ret = cal_ret(df)
df['ret'] = ret
cum_ret = cal_cum_ret(ret)
#计算指标
sum_dict={}
sum_dict['总收益']=cum_ret[-1]-1
sum_dict['日胜率'] = len(ret[ret>0])/(len(ret[ret>0])+len(ret[ret<0]))
max_nv = np.maximum.accumulate(cum_ret)
mdd = -np.min(cum_ret/max_nv-1)
sum_dict['最大回撤']=mdd
sum_dict['夏普比率']=ret.mean()/ret.std()*np.sqrt(240)
sum_df = pd.DataFrame(sum_dict,index=[0])
if plot:
#作图
plt.figure(1,figsize=(20,10))
plt.plot(df.index,cum_ret)
plt.plot(df.index,df['close']/df['close'][0])
plt.show()
return sum_df
df4 = get_data(start,end,20,a,index)
back_test_2(df4,threshold,7,True)
3388
总收益 | 日胜率 | 最大回撤 | 夏普比率 | |
---|---|---|---|---|
0 | 25.489206 | 0.528818 | 0.521558 | 0.992611 |
我们进一步考察优化后的单边交易策略。一般来说,单边交易策略的收益回比双边交易策略低,但它的收益会更稳定。优化的单边交易策略总收益同样大幅提升,达到12.65,夏普比率达到1.12。但其同样没能避开2012-1014年的这一段回撤,最大回撤达到40%。
#优化的CCI择时策略(单边交易)
df4 = get_data(start,end,20,a,index)
back_test_2(df4,threshold,False)
3388
总收益 | 日胜率 | 最大回撤 | 夏普比率 | |
---|---|---|---|---|
0 | 12.659258 | 0.559588 | 0.403086 | 1.115412 |
总的来说,CCI可以捕捉到趋势行情。普通的CCI择时策略能捕捉到长期趋势,但对于突然的趋势反转反应不足,会在熊市遭受回撤。而优化后的CCI策略捕捉短期趋势的能力更强,但会在横盘震荡行情由于频繁换仓而遭遇较大的回撤。。但是不管哪种策略,指标的钝化,以及脉冲式的行情两点造成的错判都是不可避免的。那么按照这个思路,对于指标钝化未来我们将加入与之互补的指标加以配合,而对于脉冲式行情,我们考虑运用高频数据去捕捉“瞬间”的机会。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程