研究思路
顺势指标CCI由唐纳德拉姆伯特所创,是通过测量股价的波动是否已超出其正常范围,来预测股价变化趋势的技术分析指标。由于选用的计算周期不同,顺势指标CCI也包括日CCI指标、周CCI指标、年CCI 指标以及分钟CCI指标等很多种类型。经常被用于股市研判的是日CCI指标和。虽然它们计算时取值有所不同,但基本方法一样。
计算方法:
第一步:
\(TP_i = \frac{H_i+L_i+C_i}{3}\)
第二步,计算最近n日TP的移动平均:
\(MATP(n)_t = \frac{1}{n}\sum_{i=1}^nTP_{t-i+1}\)
第三步,计算最近n日TP的一阶均差:
\(meanDev(n)_t = \frac{1}{n}\sum_{i=1}^n|TP_{t-i+1}-MATP(n)_t|\)
第四步:
\(CCI(n)_t = \frac{TP_t-MATP(n)_t}{α*meanDev(n)_t}\)
其中α一般取0.015,而n取20。 CCI指标没有运行区域的限制,在正无穷和负无穷之间变化。但是,和所有其 它没有运行区域限制的指标不一样的是,它有一个相对的技术参照区域:+100 和-100。按照指标分析的常用思路,CCI指标的运行区间也分为三类:+100以上为超买区,-100以下为超卖区,+100到-100之间为震荡区。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from jqdata import *import numpy as npimport pandas as pdimport matplotlib.pyplot as plt start = '2005-09-01' end = '2021-12-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
2021-11-29
4851.42
4858.53
4818.71
4842.886667
4868.679833
24.134167
-71.249381
2021-11-30
4832.03
4871.25
4810.22
4837.833333
4868.088500
24.725500
-81.576151
2021-12-01
4843.85
4844.52
4823.93
4837.433333
4868.557000
24.257000
-85.538653
2021-12-02
4856.16
4866.85
4830.32
4851.110000
4868.155500
24.658500
-46.084177
2021-12-03
4901.02
4901.02
4853.35
4885.130000
4869.515833
24.930833
41.753295
2021-12-06
4892.62
4943.90
4888.69
4908.403333
4872.570167
25.635500
93.186315
2021-12-07
4922.10
4934.10
4895.22
4917.140000
4876.159333
26.144400
104.498265
2021-12-08
4995.93
4995.93
4915.79
4969.216667
4884.487000
25.891000
218.170192
2021-12-09
5078.69
5114.50
4999.41
5064.200000
4894.256833
34.613200
327.318608
2021-12-10
5055.12
5060.06
5035.81
5050.330000
4902.226667
43.047667
229.363316
策略构造
标的为沪深300指数日频数据(2005-09-01至2019-07-12) 当CCI上穿100,买入,信号为1;当CCI下穿-100,卖出,信号为-1。参数n为20。策略表现如下。
策略表现
双边交易策略
我们可以看出,双边交易策略的收益主要来自于几次牛市行情,在震荡行情中的表现普通,而这也是一般趋势类择时策略的特点。策略总收益7.68,夏普比率0.70,表现可以说不错。但是最大回撤达到了47%,这是我们不希望看到的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 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 retdef 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_retdef 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
5.326277
0.526689
0.471381
0.557055
双边交易策略敏感性分析
我们通过寻找夏普比率最大的参数n来优化参数,最优参数为22。
1 2 3 4 5 6 7 8 9 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 0x7f882d2c7c88>]
优化后的策略成功避开了2009-2010年这一段时间的回撤,但仍未避开2018年底和2019年初的大回撤。但总的来说,相对于未优化的策略还是有所改善,总收益达到11.55,夏普比率0.80。
1 2 3 df2 = get_data(start,end,22 ,a,index) back_test(df2,threshold,True ,True )
总收益
日胜率
最大回撤
夏普比率
0
10.360562
0.534653
0.502721
0.693407
单边交易策略表现
通过类似的参数优化方法,我们得到单边交易策略的最优参数22。 单边交易策略的总收益低于双边交易,为8.37,但收益更稳定:最大回撤仅为29%,夏普比率达到0.96。
1 2 3 df3 = get_data(start,end,22 ,a,index) back_test(df2,threshold,False ,True )
总收益
日胜率
最大回撤
夏普比率
0
9.677065
0.564979
0.338632
0.888496
1 2 3 4 5 6 7 8 9 10 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 0x7f882e479780>]
## 优化的CCI策略
从该指标的设计来看,在一般常态行情下,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%的大幅回撤,这是优化策略更高的换手率造成的:高换手率可以避开熊市的回撤,但在震荡市中,过高的换手同样会造成回撤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 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 )
3975
总收益
日胜率
最大回撤
夏普比率
0
1.513961
0.501523
0.678913
0.342679
单边交易策略表现
我们进一步考察优化后的单边交易策略。一般来说,单边交易策略的收益回比双边交易策略低,但它的收益会更稳定。优化的单边交易策略总收益同样大幅提升,达到12.65,夏普比率达到1.12。但其同样没能避开2012-1014年的这一段回撤,最大回撤达到40%。
1 2 3 df4 = get_data(start,end,20 ,a,index) back_test_2(df4,threshold,False ,False )
3975
总收益
日胜率
最大回撤
夏普比率
0
12.694457
0.567757
0.338632
0.984271
研究结论
总的来说,CCI可以捕捉到趋势行情。普通的CCI择时策略能捕捉到长期趋势,但对于突然的趋势反转反应不足,会在熊市遭受回撤。而优化后的CCI策略捕捉短期趋势的能力更强,但会在横盘震荡行情由于频繁换仓而遭遇较大的回撤。。但是不管哪种策略,指标的钝化,以及脉冲式的行情两点造成的错判都是不可避免的。那么按照这个思路,对于指标钝化未来我们将加入与之互补的指标加以配合,而对于脉冲式行情,我们考虑运用高频数据去捕捉“瞬间”的机会。