请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  数理科学 帖子:3365777 新帖:9

技术指标研报复现—CCI的顺势而为

量化客发表于:7 月 19 日 16:00回复(1)

CCI的顺势而为¶

研究思路¶

顺势指标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之间为震荡区。

#计算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)
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
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)
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
总收益 日胜率 最大回撤 夏普比率
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)
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
总收益 日胜率 最大回撤 夏普比率
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)
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
总收益 日胜率 最大回撤 夏普比率
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扫描到异常股价波动时,也就是当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
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
总收益 日胜率 最大回撤 夏普比率
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
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
总收益 日胜率 最大回撤 夏普比率
0 12.659258 0.559588 0.403086 1.115412

研究结论¶

总的来说,CCI可以捕捉到趋势行情。普通的CCI择时策略能捕捉到长期趋势,但对于突然的趋势反转反应不足,会在熊市遭受回撤。而优化后的CCI策略捕捉短期趋势的能力更强,但会在横盘震荡行情由于频繁换仓而遭遇较大的回撤。。但是不管哪种策略,指标的钝化,以及脉冲式的行情两点造成的错判都是不可避免的。那么按照这个思路,对于指标钝化未来我们将加入与之互补的指标加以配合,而对于脉冲式行情,我们考虑运用高频数据去捕捉“瞬间”的机会。

全部回复

0/140

量化课程

    移动端课程