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

量化交易吧 /  数理科学 帖子:3352573 新帖:4

风险收益一致性

爱汇小王子发表于:7 月 19 日 17:59回复(1)

华泰风险收益一致性择时模型¶

研究目的¶

本文参考华泰证券《华泰风险收益一致性择时模型》,采用研报内的方法对风险收益一致性进行研究。根据研报分析,当行业的收益率与其贝塔呈现较好的正相关时,可以认为市场收益率为正,市场处于上涨状态;当行业的收益率与其贝塔呈现负相关时,可以认为市场收益率为负,市场处于下跌状态,利用这种关系即可构造择时模型。这是华泰风险收益一致性择时的基本思想。
根据此结论,本文试图对研报里面的结果进行了复现并分析,并对风险收益一致性进行了研究,从而其构建择时信号。另外,本文还试图将风险收益一致性信号与均线信号相结合,探究均线策略能否进一步改善风险收益一致性策略。

研究思路¶

自从20世纪50年代资本资产定价模型提出之后,人们习惯使用贝塔来代表资产与市场组合之间的关系。根据资本资产定价模型,假设资产的贝塔值是稳定的,那么在市场上涨的时候,贝塔高的资产应该收获更高的收益,但是市场下跌的时候也会承担更多的损失,所以贝塔值代表了资产承担市场风险的大小。通过资本资产定价模型我们找到了市场中存在的一种结构,不同资产的涨跌幅与市场组合的涨跌幅会存在相对固定的对应关系。如果反 过来使用这种对应关系,就得到了一种观察市场的方法,比如当发现高贝塔的资产收益较高,低贝塔的资产收益较低时,那市场大概率处于一种上升状态,当发现高贝塔的资产收益较低,低贝塔的资产收益较高时,市场可能处于一种下跌状态,如此我们可以构造一个择时模型。
贝塔是一项资产或投资组合对市场投资组合方差 的贡献程度,也即该资产或组合相对于市场波动性的敏感程度。贝塔越高,表明该资产或 组合受市场波动的影响越大,从而带来更大的风险溢价(即$β_p(R_m-R_f)$,括号内的部分为市场风险溢价)。在市场上涨(或下跌)时,高贝塔的资产由于承担了更多的市场风险, 其收益的变动会比低贝塔的资产更为剧烈。
在资本资产定价模型的收益率公式中,如果贝塔是固定的,那资本的收益率主要取决于市场的收益率,所以市场上涨高贝塔行业涨幅更大,市场下跌同样高贝塔行业会下跌更多。借助于这一点,可以尝试逆向推断市场的涨跌,当行业涨幅与其贝塔状态基本一致的时候说明市场是上涨的,相反的时候说明市场是下跌的。

数据获取¶

研报中市场指数选用万得全A指数,行业指数选用中信一级行业指数。由于数据来源限制,此处行业指数我们选择申万一级行业指数,而市场指数选取国证全A指数。数据时间段为2005年2月3日至今,频率为周度。我们需要用指数的周收益率来计算beta。

#数据获取
from jqdata import *
import time,datetime
import numpy as np
import pandas as pd
from jqdata import finance

# #获取每周最后一个交易日的申万一级行业指数收盘价
industries_df = get_industries("sw_l1")
deprecated_industries = ['801060','801070','801090','801100','801190','801220']
industries = list(industries_df.index)
start_dates = list(industries_df['start_date'])
trade_days = get_trade_days(start_date='2005-02-03')
ret_dict={}
for code,start_date in zip(industries,start_dates):
    if code in deprecated_industries:
        continue
    #国证A指在2005年2月3日后才有数据
    start_date = trade_days[0]
    q=query(finance.SW1_DAILY_PRICE).filter(finance.SW1_DAILY_PRICE.date>=start_date,finance.SW1_DAILY_PRICE.code==code)
    df=finance.run_query(q)
    if len(df)!=len(trade_days):
        trade_days=trade_days[0:-1]
    df_date=list(df['date'])
    df_date_weekday =np.array([i.weekday() for i in df_date])
    b=df_date_weekday[0:-1]-df_date_weekday[1:]
    c=df['date']
    close=np.array(df[0:-1][b!=-1]['close'])
    ret = close[1:]/close[:-1]-1
    ret_dict[code]=ret
    
ret_dict['date'] = df[0:-1][b!=-1]['date'][1:]

#获取每周最后一个交易日的国证A指
mkt_index = get_price('399317.XSHE',start_date='2005-02-03',end_date=trade_days[-1],fields=['close'])
mkt_close = np.array(mkt_index[0:-1][b!=-1]['close'])
mkt_ret =mkt_close[1:]/mkt_close[0:-1]-1
ret_dict['mkt'] = mkt_ret
ret_df = pd.DataFrame(ret_dict)

ret_df.head(10)
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
801010 801020 801030 801040 801050 801080 801110 801120 801130 801140 801150 801160 801170 801180 801200 801210 801230 801710 801720 801730 801740 801750 801760 801770 801780 801790 801880 801890 date mkt
4 0.016752 -0.000055 -0.002821 -0.019222 0.004743 0.003767 -0.001150 -0.008497 0.008307 -0.003766 0.000467 -0.005438 -0.014379 -0.011145 0.003902 -0.004498 0.003895 -0.000345 0.013141 0.005468 0.010417 -0.005446 -0.003698 -0.004274 -0.024481 -0.003074 -0.008177 0.002992 2005-02-18 -0.004816
9 0.063175 0.053068 0.059062 0.035376 0.074910 0.049745 0.056839 0.049664 0.053907 0.047423 0.048626 0.038252 0.034873 0.034023 0.058412 0.070708 0.063751 0.058779 0.058697 0.079068 0.067695 0.077919 0.070035 0.030865 0.024434 0.037494 0.040080 0.056129 2005-02-25 0.048701
14 -0.002303 -0.029427 -0.014190 -0.032868 -0.016789 -0.024223 -0.034625 -0.015581 -0.017670 -0.025470 -0.017187 0.000461 -0.014089 -0.024456 -0.023079 0.001736 -0.022184 -0.029780 -0.011949 -0.027190 -0.027025 -0.023410 -0.013838 -0.009385 -0.037843 -0.025196 -0.040671 -0.022206 2005-03-04 -0.020454
19 -0.011705 -0.018241 0.015400 0.005215 0.019510 0.011692 0.018466 0.010708 -0.000124 -0.006557 0.018899 -0.003516 0.026794 -0.011475 0.019286 0.024578 -0.007614 0.008692 0.008764 -0.003585 0.001311 0.014821 -0.004886 -0.012585 -0.030693 -0.035479 -0.009785 0.012533 2005-03-11 0.005118
24 -0.057641 -0.048873 -0.052910 -0.032943 -0.060030 -0.083581 -0.073817 -0.013138 -0.056484 -0.051227 -0.052252 -0.058472 -0.033417 -0.076386 -0.045483 -0.046424 -0.063510 -0.057643 -0.041514 -0.063254 -0.032789 -0.073470 -0.072627 -0.046506 -0.014085 -0.073533 -0.077500 -0.056094 2005-03-18 -0.052057
29 -0.001022 -0.004992 -0.013292 -0.018703 -0.010654 -0.046608 -0.035764 -0.005726 -0.020882 -0.031384 -0.012118 -0.024944 0.007835 -0.017959 -0.020444 -0.016896 -0.027020 -0.017283 -0.018855 -0.007214 -0.014308 -0.046290 -0.037950 -0.042745 -0.022360 -0.051607 -0.029573 -0.014648 2005-03-25 -0.018570
34 -0.010949 0.043812 0.014796 0.027020 -0.010944 0.009051 0.009485 0.016377 -0.021537 0.012236 0.000475 0.014555 0.010731 -0.000426 0.002580 -0.009217 -0.012868 -0.000944 -0.005312 -0.000228 -0.009677 0.003298 0.002245 0.003748 0.030967 0.028782 -0.011591 -0.005097 2005-04-01 0.007781
39 0.012764 0.043675 0.039942 -0.016439 0.043137 0.001708 0.010010 0.026947 0.022241 0.003975 0.028170 0.026602 0.034663 0.023625 0.035504 0.044257 0.040609 0.017659 0.030992 0.006308 0.030041 0.009610 -0.003075 -0.005335 0.072019 0.053887 0.013579 0.037236 2005-04-08 0.024562
44 -0.039831 -0.045880 -0.026454 -0.009985 -0.042549 -0.042376 0.000901 -0.032222 -0.035573 -0.005496 -0.030181 -0.031662 -0.032850 -0.037683 -0.036499 -0.024911 -0.050571 -0.044134 -0.043749 -0.022515 -0.032306 -0.051727 -0.045429 -0.050117 -0.032454 -0.021380 -0.025137 -0.031692 2005-04-15 -0.032726
49 -0.077672 -0.006006 -0.054275 -0.031983 -0.050219 -0.093586 -0.044920 -0.037758 -0.057848 -0.061305 -0.039812 -0.049278 0.000008 -0.069730 -0.040218 -0.069477 -0.077392 -0.085568 -0.047821 -0.057839 -0.046698 -0.078703 -0.090011 -0.029196 -0.018651 -0.061226 -0.074159 -0.067677 2005-04-22 -0.047760

不同行业的周期特征及beta表现¶

为了直观地感受不同行业Beta值地周期特征,我们将行业贝塔在每一个截面从小到大排序,得到行业贝塔的秩次,用 rank 表示。将行业贝塔rank在同一张图中表示出来,得到下面这一系列的图。总的来看,行业beta值比较稳定,但在某些特殊时刻也存在跃迁 式的变化。例如 15 年中期,急剧的上涨行情使很多行业的贝塔发生了变化,而且这种变化也延续至今。偏向于 TMT类型的行业,计算机、传媒、电子、通信、电力设备等在15年5、6月份贝塔值急剧上升,转变为高贝塔类行业,而银行、非银等行业贝塔值快速下降,行业特性向低贝塔靠近,在此之后,行业的贝塔值在16年一直处于比较稳定的状态。
Img $$计算机行业Beta在2015年快速上升$$ Img $$银行行业Beta在2015年快速下降$$ 尽管我们使用的行业分类和市场指数与研报原文不同,但对于类似的行业,其Beta表现模式与研报中相近。

import statsmodels.api as sm
#用N周的数据计算Beta,第N周开始有beta,研报中N为98

#获取行业中文名称
ind_df=get_industries('sw_l1')
ind = ind_df.index
valid_ind = list(set(ind)^set(deprecated_industries))
valid_ind_df = ind_df.loc[valid_ind]
ind_dict = dict(zip(valid_ind_df.index,valid_ind_df['name']))

#计算行业beta
N=98
beta_dict = {}
beta_dict['date'] = ret_dict['date'][N-1:]
for k,ret in ret_dict.items():
    if k!='date'and k!='mkt':
        beta_series = []
        for i in range(len(ret)-N+1):
            x = sm.add_constant(mkt_ret[i:i+N])
            model = sm.OLS(ret[i:i+N],x)
            results = model.fit()
            beta_series.append(results.params[1])
        beta_dict[k] = beta_series

beta_df = pd.DataFrame(beta_dict)        
date = beta_df['date']
beta_df_t = beta_df.T
beta_rank=beta_df_t.iloc[1:].rank().T
plt.figure()
for i in range(1,29):
    print(i)
    #plt.subplot(28,1,i)
    plt.figure(figsize=(10,8))
    code = beta_df.columns[i]
    x = beta_dict['date']
    y1 = beta_dict[code]
    y2 = beta_rank[code]
    fig, ax1 = plt.subplots()
    ax2 = ax1.twinx()
    ax1.plot(x,y1,'g-')
    ax2.plot(x,y2,'b-')
    ax1.set_xlabel(ind_dict[code])
    ax1.set_ylabel("Beta",color='g')

    ax2.set_ylabel("Rank",color='b')

    plt.show()
1
<Figure size 432x288 with 0 Axes>
<Figure size 720x576 with 0 Axes>
2
<Figure size 720x576 with 0 Axes>
3
<Figure size 720x576 with 0 Axes>
4
<Figure size 720x576 with 0 Axes>
5
<Figure size 720x576 with 0 Axes>
6
<Figure size 720x576 with 0 Axes>
7
<Figure size 720x576 with 0 Axes>
8
<Figure size 720x576 with 0 Axes>
9
<Figure size 720x576 with 0 Axes>
10
<Figure size 720x576 with 0 Axes>
11
<Figure size 720x576 with 0 Axes>
12
<Figure size 720x576 with 0 Axes>
13
<Figure size 720x576 with 0 Axes>
14
<Figure size 720x576 with 0 Axes>
15
<Figure size 720x576 with 0 Axes>
16
<Figure size 720x576 with 0 Axes>
17
<Figure size 720x576 with 0 Axes>
18
<Figure size 720x576 with 0 Axes>
19
<Figure size 720x576 with 0 Axes>
20
<Figure size 720x576 with 0 Axes>
21
<Figure size 720x576 with 0 Axes>
22
<Figure size 720x576 with 0 Axes>
23
<Figure size 720x576 with 0 Axes>
24
<Figure size 720x576 with 0 Axes>
25
<Figure size 720x576 with 0 Axes>
26
<Figure size 720x576 with 0 Axes>
27
<Figure size 720x576 with 0 Axes>
28
<Figure size 720x576 with 0 Axes>

信号生成和择时体系构建¶

接下来我们利用行业的贝塔与其收益率之间的关系构建择时模型,由于贝塔代表行业相比于市场组合的风险承担,本质上也代表行业相对于市场组合的杠杆率,一定程度上度量了风险,所以将择时模型起名为风险收益一致性择时模型。
如前所述,资产的贝塔大小与资产收益变动幅度存在相关性,而各个行业具有特征明显且相对稳定的贝塔。基于这一性质,我们可以对行业的贝塔与收益进行观测,进而对判断市场的运行状况:
1、当贝塔与收益趋于一致,即高贝塔的行业收益更高时,认为市场表现良好,观点看多;
2、当贝塔与收益呈反向关系,即高贝塔的行业收益更低时,认为市场表现不佳,观点看空。这一策略有着清晰的逻辑,并且以行业与市场指数做比较,在长期看较为稳定,适合对市 场进行长期判断。
为了度量行业贝塔与收益的一致性,我们引入Spearman 秩相关系数作为工具。 我们利用Spearman秩相关系数度量行业Beta与收益的一致性。
利用之前两年左右的数据,我们在每周末可以得到28个行业的Beta。得到Beta后,我们计算行业Beta与行业收益率的秩相关系数。在计算时,我们假设行业贝塔在周中保持静态不变,采用29个行业当周的收益率$r_t$与上周的贝塔 $β_{t-1} $进行计算。这样的好处是贝塔中并没有包含本周的收益率信息,两者保持一定意义上的相对独立,防止某个异常值带来的贝塔偏离。 为了过滤相关系数中的噪音,得到更稳定的长周期择时信号,我们对秩相关系数取4周的滑动平均,得到滑动平均序列 𝜌̅𝑠。 根据研报,我们选取98周数据计算β,秩相关系数选取0.128作为阈值。当𝜌̅𝑠>0.128时,发出一次买入信号;当𝜌̅𝑠<-0.128时,发出一次卖出信号。若连续发出两次同向信号,则执行买入/卖出操作。 从信号发出的时间来看,使用不同的行业指数对于信号发出还是有些影响的。总的来说,使用申万行业一级指数和研报中阈值的结果不佳,年化收益过低,不能跑赢国证A指。

from scipy.stats import spearmanr
threshold=0.128
   
#threshold为阈值,print_order为是否打印买卖指令
def back_test(threshold,print_order=True):
    corr_list =[]
    #第t周收益与t-1周的beta
    for i in range(len(beta_df)-1):
        corr_list.append(spearmanr(beta_df.iloc[i][1:].astype(float),ret_df.iloc[i+N][:-2].astype(float))[0])
    corr_dict={}
    corr_dict['date']=beta_df['date'][1:]
    corr_dict['corr']=corr_list
    corr_df=pd.DataFrame(corr_dict)
    corr_df['ma_4'] = corr_df['corr'].rolling(window=4,min_periods=1).mean()
    corr_df['signal']=0
    corr_df['signal'][corr_df['ma_4']>threshold]=1
    corr_df['signal'][corr_df['ma_4']<-threshold]=-1

    position_list = [0]
    flag=0
    order_dict = {'date':[],'指令':[]}
    for i in range(1,len(corr_df['signal'])):
        if flag==0:
            if np.array(corr_df['signal'])[i-1]==1 and np.array(corr_df['signal'])[i]==1:
                position_list.append(1)
                flag=1
                order_dict['date'].append(corr_df['date'].iloc[i])
                order_dict['指令'].append('买入')
            else:
                position_list.append(0)
        elif flag==1:
            if np.array(corr_df['signal'])[i-1]==-1 and np.array(corr_df['signal'])[i]==-1:
                position_list.append(0)
                flag=0
                order_dict['date'].append(corr_df['date'].iloc[i])
                order_dict['指令'].append('卖出')
            else:
                position_list.append(1)
    order_df = pd.DataFrame(order_dict)
    if print_order:
        print(order_df)
    corr_df['position']=position_list
    corr_df['ret']=0
    corr_df['ret'].iloc[1:] = np.array(ret_df['mkt'])[N+1:]*np.array(corr_df['position'])[:-1]
    ret = list(corr_df['ret'])
    cum_ret=[]
    for i in range(len(ret)):
        if i==0:
            cum_ret.append(1+ret[i])
        else:
            cum_ret.append((1+ret[i])*cum_ret[i-1])
    corr_df['cum_ret']=cum_ret
    return corr_df

corr_df=back_test(threshold)

#计算各项指标,一年按50周计算
max_cum_ret=[]
cum_ret = np.array(corr_df['cum_ret'])
for i in range(len(cum_ret)):
    max_cum_ret.append(max(cum_ret[:i+1]))
drawdown=1-corr_df['cum_ret']/max_cum_ret

annual_ret = corr_df['cum_ret'].iloc[-1]**(1*50/len(corr_df))-1
vol = corr_df['ret'].std()*np.sqrt(50)
mdd = max(drawdown)
sharpe = annual_ret/vol
summary_df = pd.DataFrame({'年化收益':[annual_ret],'年化波动率':[vol],'最大回撤':[mdd],'夏普比率':[sharpe]})
print(summary_df)

#作图
plt.figure(figsize=(15,6))
mkt_index = get_price('399317.XSHE',start_date='2005-02-03',end_date=trade_days[-1],fields=['close'])
data=corr_df.set_index('date')
combination=data.join(mkt_index)
plt.plot(combination.index,combination['cum_ret'])
plt.plot(combination.index,combination['close']/combination['close'].iloc[0])
plt.show()
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:16: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  app.launch_new_instance()
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:17: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
/opt/conda/lib/python3.6/site-packages/pandas/core/indexing.py:189: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  self._setitem_with_indexer(indexer, value)
          date  指令
0   2007-02-16  买入
1   2007-07-20  卖出
2   2008-03-14  买入
3   2008-04-03  卖出
4   2008-09-12  买入
5   2008-11-07  卖出
6   2009-02-06  买入
7   2009-08-28  卖出
8   2009-10-30  买入
9   2010-12-03  卖出
10  2011-03-11  买入
11  2011-09-09  卖出
12  2012-02-10  买入
13  2012-06-29  卖出
14  2012-12-31  买入
15  2013-03-22  卖出
16  2014-07-11  买入
17  2015-02-06  卖出
18  2015-08-14  买入
19  2015-09-11  卖出
20  2015-10-16  买入
21  2015-12-25  卖出
22  2016-04-01  买入
23  2016-09-23  卖出
24  2017-03-24  买入
25  2017-04-21  卖出
26  2017-07-07  买入
27  2017-07-21  卖出
28  2017-09-08  买入
29  2017-10-27  卖出
30  2018-03-16  买入
31  2018-05-04  卖出
32  2018-11-16  买入
33  2019-04-26  卖出
34  2019-06-21  买入
       年化收益     年化波动率      最大回撤      夏普比率
0  0.061481  0.173475  0.387564  0.354406

敏感性分析¶

从上面的回测可以看出,应用原始阈值的择时策略效果不佳。而Spearman 秩相关系数的计算较为敏感,其假设检验的拒绝域需要精确到千分位,应用 Spearman 方法的本策略不可避免地受到这一敏感性的影响。所以,我们需要对敏感性进行分析,找出最佳阈值。敏感性分析的参数为信号触发阈值,选取0.100-0.300(步长为0.001)进行测试,选取夏普比率作为优化目标,样本内夏普比率的变动如下:

thresholds=np.array(range(100,301,1))/1000
sharpe_dict={'阈值':[],'夏普比率':[]}
for threshold in thresholds:
    corr_df=back_test(threshold,print_order=False)
    annual_ret = corr_df['cum_ret'].iloc[-1]**(1*50/len(corr_df))-1
    vol = corr_df['ret'].std()*np.sqrt(50)
    sharpe = annual_ret/vol
    sharpe_dict['阈值'].append(threshold)
    sharpe_dict['夏普比率'].append(sharpe)

sharpe_df = pd.DataFrame(sharpe_dict)
plt.figure(figsize=(20,10))
plt.plot(sharpe_df["阈值"],sharpe_df['夏普比率'])
plt.show()
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:16: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  app.launch_new_instance()
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:17: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

根据敏感性分析,我们可知最优阈值为0.142。此时夏普率为0.64,且策略回撤大幅减少,能够跑赢市场指数。

corr_df=back_test(0.142)

#计算各项指标,一年按50周计算
max_cum_ret=[]
cum_ret = np.array(corr_df['cum_ret'])
for i in range(len(cum_ret)):
    max_cum_ret.append(max(cum_ret[:i+1]))
drawdown=1-corr_df['cum_ret']/max_cum_ret

annual_ret = corr_df['cum_ret'].iloc[-1]**(1*50/len(corr_df))-1
vol = corr_df['ret'].std()*np.sqrt(50)
mdd = max(drawdown)
sharpe = annual_ret/vol
summary_df = pd.DataFrame({'年化收益':[annual_ret],'年化波动率':[vol],'最大回撤':[mdd],'夏普比率':[sharpe]})
print(summary_df)

#作图
plt.figure(figsize=(15,6))
data=corr_df.set_index('date')
combination=data.join(mkt_index)
plt.plot(combination.index,combination['cum_ret'])
plt.plot(combination.index,combination['close']/combination['close'].iloc[0])
plt.show()
corr_df.to_excel('0.142.xlsx')
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:16: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  app.launch_new_instance()
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:17: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
          date  指令
0   2007-02-16  买入
1   2007-11-02  卖出
2   2008-03-14  买入
3   2008-04-11  卖出
4   2009-02-06  买入
5   2009-08-28  卖出
6   2009-10-30  买入
7   2010-12-03  卖出
8   2011-03-11  买入
9   2011-09-09  卖出
10  2012-02-10  买入
11  2012-06-29  卖出
12  2012-12-31  买入
13  2013-03-22  卖出
14  2014-07-11  买入
15  2015-02-06  卖出
16  2015-08-14  买入
17  2015-09-11  卖出
18  2015-10-16  买入
19  2015-12-25  卖出
20  2016-04-01  买入
21  2016-09-23  卖出
22  2017-03-24  买入
23  2017-04-21  卖出
24  2017-09-08  买入
25  2017-10-27  卖出
26  2018-03-16  买入
27  2018-05-18  卖出
28  2018-11-16  买入
29  2019-04-26  卖出
       年化收益     年化波动率      最大回撤      夏普比率
0  0.110862  0.173213  0.294968  0.640035

与均线策略的结合¶

纯多策略在 15 年的下跌中出现了一个巨大的回撤。另外,择时模型给出的信号为周频信号,为了将信号扩展到日频,可以尝试加入均线。
具体做法为计算市场指数当周收盘价与20日均线之差,若差值为正,均线上给出看多信号;若差值为负,均线上给出看空信号。当均线上信号与策略择时信号一致时进行操作买入或卖出,当信号不一致时清仓,既不做多也不做空。
加入均线策略后,由于均线策略的延迟特性,组合策略未能避过2016年左右的回撤,导致年化收益和夏普率均下降。

def combination_back_test(threshold,print_order=True):
#生成均线信号
    corr_list =[]
    #第t周收益与t-1周的beta
    for i in range(len(beta_df)-1):
        corr_list.append(spearmanr(beta_df.iloc[i][1:].astype(float),ret_df.iloc[i+N][:-2].astype(float))[0])
    corr_dict={}
    corr_dict['date']=beta_df['date'][1:]
    corr_dict['corr']=corr_list
    corr_df=pd.DataFrame(corr_dict)
    corr_df['ma_4'] = corr_df['corr'].rolling(window=4,min_periods=1).mean()
    corr_df['signal']=0
    corr_df['signal'][corr_df['ma_4']>threshold]=1
    corr_df['signal'][corr_df['ma_4']<-threshold]=-1
    mkt_index = get_price('399317.XSHE',start_date='2005-02-03',end_date=trade_days[-1],fields=['close'])
    mkt_index['ma_20'] = mkt_index.rolling(window=20).mean()
    data=corr_df.set_index('date',drop=False)
    combination=data.join(mkt_index)
    combination['ma_signal']=(combination['close']>combination['ma_20']).astype(int)
    
    position_list = [0]
    flag=0
    order_dict = {'date':[],'指令':[]}
    for i in range(1,len(corr_df['signal'])):
        if flag==0:
            if np.array(combination['signal'])[i-1]==1 and np.array(combination['signal'])[i]==1:
                if np.array(combination['ma_signal'])[i]==1:
                    position_list.append(1)
                    flag=1
                    order_dict['date'].append(combination['date'].iloc[i])
                    order_dict['指令'].append('买入')
                else:
                    position_list.append(0)
            else:
                position_list.append(0)
        elif flag==1:
            if np.array(combination['signal'])[i-1]==-1 and np.array(combination['signal'])[i]==-1:
                if np.array(combination['ma_signal'])[i]==0:
                    position_list.append(0)
                    flag=0
                    order_dict['date'].append(combination['date'].iloc[i])
                    order_dict['指令'].append('卖出')
                else:
                    position_list.append(1)
            else:
                position_list.append(1)
    if print_order:
        order_df = pd.DataFrame(order_dict)
        print(order_df)
    combination['position']=position_list
    combination['ret']=0
    combination['ret'].iloc[1:] = np.array(ret_df['mkt'])[N+1:]*np.array(combination['position'])[:-1]
    ret = list(combination['ret'])
    cum_ret=[]
    for i in range(len(ret)):
        if i==0:
            cum_ret.append(1+ret[i])
        else:
            cum_ret.append((1+ret[i])*cum_ret[i-1])
    combination['cum_ret']=cum_ret
    return combination

combination = combination_back_test(0.142)
#计算各项指标,一年按50周计算
max_cum_ret=[]
cum_ret = np.array(combination['cum_ret'])
for i in range(len(cum_ret)):
    max_cum_ret.append(max(cum_ret[:i+1]))
drawdown=1-combination['cum_ret']/max_cum_ret

annual_ret = combination['cum_ret'].iloc[-1]**(1*50/len(combination))-1
vol = combination['ret'].std()*np.sqrt(50)
mdd = max(drawdown)
sharpe = annual_ret/vol
summary_df = pd.DataFrame({'年化收益':[annual_ret],'年化波动率':[vol],'最大回撤':[mdd],'夏普比率':[sharpe]})
print(summary_df)

#作图
plt.figure(figsize=(15,6))
#mkt_index = get_price('399317.XSHE',start_date='2005-02-03',end_date=trade_days[-1],fields=['close'])
#data=combination.set_index('date')
#combination=data.join(mkt_index)
plt.plot(combination.index,combination['cum_ret'])
plt.plot(combination.index,combination['close']/combination['close'].iloc[0])
plt.show()
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:13: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  del sys.path[0]
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:14: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  
          date  指令
0   2007-02-16  买入
1   2007-11-02  卖出
2   2009-02-06  买入
3   2009-08-28  卖出
4   2009-10-30  买入
5   2010-12-03  卖出
6   2011-03-11  买入
7   2011-09-09  卖出
8   2012-02-10  买入
9   2012-06-29  卖出
10  2012-12-31  买入
11  2013-03-29  卖出
12  2014-07-11  买入
13  2015-02-06  卖出
14  2015-08-14  买入
15  2015-09-11  卖出
16  2015-10-16  买入
17  2016-09-23  卖出
18  2017-03-24  买入
19  2017-04-21  卖出
20  2017-09-08  买入
21  2017-11-03  卖出
22  2018-03-16  买入
23  2018-05-25  卖出
24  2018-11-16  买入
25  2019-04-26  卖出
       年化收益     年化波动率      最大回撤     夏普比率
0  0.095759  0.180809  0.386286  0.52961
combination
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
date corr ma_4 signal close ma_20 ma_signal position ret cum_ret
date
2007-02-09 2007-02-09 0.451560 0.451560 1 1933.86 1906.4405 1 0 0.000000 1.000000
2007-02-16 2007-02-16 -0.051450 0.200055 1 2154.01 1968.2765 1 1 0.000000 1.000000
2007-03-02 2007-03-02 -0.006568 0.131180 0 2057.04 1993.8960 1 1 -0.045018 0.954982
2007-03-09 2007-03-09 0.347017 0.185140 1 2151.72 2038.6400 1 1 0.046027 0.998937
2007-03-16 2007-03-16 0.079365 0.092091 0 2170.21 2110.4810 1 1 0.008593 1.007521
2007-03-23 2007-03-23 -0.105090 0.078681 0 2287.59 2154.3260 1 1 0.054087 1.062015
2007-03-30 2007-03-30 -0.112753 0.052135 0 2330.49 2221.7930 1 1 0.018753 1.081931
2007-04-06 2007-04-06 0.409962 0.067871 0 2504.15 2306.2640 1 1 0.074517 1.162553
2007-04-13 2007-04-13 0.296661 0.122195 0 2660.87 2415.3845 1 1 0.062584 1.235310
2007-04-20 2007-04-20 0.124795 0.179666 1 2800.62 2542.0520 1 1 0.052520 1.300189
2007-04-27 2007-04-27 0.029557 0.215244 1 2963.39 2693.5535 1 1 0.058119 1.375755
2007-05-11 2007-05-11 -0.316366 0.033662 0 3152.26 2862.7660 1 1 0.063734 1.463438
2007-05-18 2007-05-18 0.126437 -0.008894 0 3224.60 3000.1095 1 1 0.022949 1.497022
2007-05-25 2007-05-25 0.100712 -0.014915 0 3452.46 3153.7600 1 1 0.070663 1.602806
2007-06-01 2007-06-01 -0.454297 -0.135878 0 3159.59 3264.1705 0 1 -0.084829 1.466841
2007-06-08 2007-06-08 0.366174 0.034756 0 3194.90 3246.7730 0 1 0.011176 1.483234
2007-06-15 2007-06-15 0.302135 0.078681 0 3427.78 3301.4600 1 1 0.072891 1.591348
2007-06-22 2007-06-22 -0.412151 -0.049535 0 3355.65 3330.0530 1 1 -0.021043 1.557862
2007-06-29 2007-06-29 -0.290640 -0.008621 0 3058.20 3280.6815 0 1 -0.088642 1.419771
2007-07-06 2007-07-06 0.240285 -0.040093 0 3026.26 3275.0230 0 1 -0.010444 1.404942
2007-07-13 2007-07-13 -0.303229 -0.191434 -1 3102.71 3202.3600 0 1 0.025262 1.440434
2007-07-20 2007-07-20 -0.210728 -0.141078 0 3221.21 3103.7140 1 1 0.038192 1.495448
2007-07-27 2007-07-27 0.322386 0.012178 0 3537.20 3168.9765 1 1 0.098097 1.642147
2007-08-03 2007-08-03 -0.258894 -0.112616 0 3734.44 3318.3430 1 1 0.055762 1.733715
2007-08-10 2007-08-10 -0.117132 -0.066092 0 3785.97 3496.0365 1 1 0.013799 1.757638
2007-08-17 2007-08-17 0.506294 0.113164 0 3741.18 3672.8300 1 1 -0.011831 1.736844
2007-08-24 2007-08-24 -0.006568 0.030925 0 4150.54 3820.0875 1 1 0.109420 1.926890
2007-08-31 2007-08-31 -0.137384 0.061303 0 4230.51 3955.4535 1 1 0.019267 1.964016
2007-09-07 2007-09-07 -0.305419 0.014231 0 4249.49 4079.1465 1 1 0.004486 1.972827
2007-09-14 2007-09-14 -0.025178 -0.118637 0 4301.75 4185.0320 1 1 0.012298 1.997089
... ... ... ... ... ... ... ... ... ... ...
2018-11-30 2018-11-30 0.000000 0.042830 0 3591.19 3644.7215 0 1 0.004394 2.521727
2018-12-07 2018-12-07 -0.172961 -0.048440 0 3619.60 3658.5615 0 1 0.007911 2.541677
2018-12-14 2018-12-14 0.021894 -0.148878 -1 3577.76 3635.7515 0 1 -0.011559 2.512297
2018-12-21 2018-12-21 0.124247 -0.006705 0 3458.96 3592.8700 0 1 -0.033205 2.428875
2018-12-28 2018-12-28 -0.153257 -0.045019 0 3419.55 3556.1705 0 1 -0.011394 2.401202
2019-01-04 2019-01-04 0.097975 0.022715 0 3458.97 3511.8855 0 1 0.011528 2.428882
2019-01-11 2019-01-11 0.412698 0.120416 0 3540.40 3489.2925 1 1 0.023542 2.486062
2019-01-18 2019-01-18 -0.496990 -0.034893 0 3587.14 3486.0530 1 1 0.013202 2.518883
2019-01-25 2019-01-25 0.042146 0.013957 0 3587.90 3515.4650 1 1 0.000212 2.519417
2019-02-01 2019-02-01 -0.327313 -0.092365 0 3585.13 3550.4305 1 1 -0.000772 2.517472
2019-02-15 2019-02-15 0.511768 -0.067597 0 3742.12 3604.9050 1 1 0.043789 2.627710
2019-02-22 2019-02-22 0.590586 0.204297 1 3960.54 3688.3215 1 1 0.058368 2.781084
2019-03-01 2019-03-01 -0.206349 0.142173 1 4209.70 3835.2345 1 1 0.062911 2.956044
2019-03-08 2019-03-08 0.513410 0.352354 1 4247.58 4033.6385 1 1 0.008998 2.982643
2019-03-15 2019-03-15 0.077723 0.243842 1 4346.07 4191.0570 1 1 0.023187 3.051802
2019-03-22 2019-03-22 0.303229 0.172003 1 4493.95 4336.4455 1 1 0.034026 3.155644
2019-03-29 2019-03-29 -0.129174 0.191297 1 4481.14 4391.0745 1 1 -0.002850 3.146648
2019-04-04 2019-04-04 0.025178 0.069239 0 4723.62 4451.2915 1 1 0.054111 3.316917
2019-04-12 2019-04-12 -0.691297 -0.123016 0 4627.30 4537.0725 1 1 -0.020391 3.249282
2019-04-19 2019-04-19 0.027915 -0.191845 -1 4752.48 4601.3600 1 1 0.027052 3.337183
2019-04-26 2019-04-26 0.018062 -0.155036 -1 4458.43 4650.4315 0 0 -0.061873 3.130701
2019-04-30 2019-04-30 -0.677614 -0.330733 -1 4418.25 4635.5265 0 0 -0.000000 3.130701
2019-05-10 2019-05-10 0.231527 -0.100027 0 4225.39 4497.4830 0 0 -0.000000 3.130701
2019-05-17 2019-05-17 -0.455939 -0.220991 -1 4135.47 4380.4205 0 0 -0.000000 3.130701
2019-05-24 2019-05-24 0.173508 -0.182129 -1 4065.06 4235.2370 0 0 -0.000000 3.130701
2019-05-31 2019-05-31 0.243569 0.048166 0 4144.35 4152.7555 0 0 0.000000 3.130701
2019-06-06 2019-06-06 -0.096880 -0.033935 0 4003.19 4140.8925 0 0 -0.000000 3.130701
2019-06-14 2019-06-14 0.209086 0.132321 0 4114.78 4123.2675 0 0 0.000000 3.130701
2019-06-21 2019-06-21 0.279693 0.158867 1 4320.22 4141.7960 1 0 0.000000 3.130701
2019-06-28 2019-06-28 -0.002737 0.097291 0 4288.33 4180.5755 1 0 -0.000000 3.130701

624 rows × 10 columns

风险收益一致性与均线结合策略的敏感性分析¶

与之前类似,我们对结合策略进行参数敏感性分析。

thresholds=np.array(range(100,301,1))/1000
sharpe_dict={'阈值':[],'夏普比率':[]}
for threshold in thresholds:
    corr_df=combination_back_test(threshold,print_order=False)
    annual_ret = corr_df['cum_ret'].iloc[-1]**(1*50/len(corr_df))-1
    vol = corr_df['ret'].std()*np.sqrt(50)
    sharpe = annual_ret/vol
    sharpe_dict['阈值'].append(threshold)
    sharpe_dict['夏普比率'].append(sharpe)

sharpe_df = pd.DataFrame(sharpe_dict)
plt.figure(figsize=(20,10))
plt.plot(sharpe_df["阈值"],sharpe_df['夏普比率'])
plt.show()
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:13: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  del sys.path[0]
/opt/conda/lib/python3.6/site-packages/ipykernel_launcher.py:14: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  

最优阈值为0.127-0.13,但夏普率仅为0.54,未能战胜原始策略。

研究结论¶

总的来说,风险收益一致性模型可以跑赢市场,收益也比较稳定,但在2015年中遭遇了大回撤。策略的大回撤主要来源于 15 年年中的急速下跌,这段时期各行业的贝塔值也发生了急剧 变化,因此导致信号出现了滞后与偏差,这是模型的主要风险,即市场风格的急剧变化。
我们试图加入简单均线策略来改善模型的表现。但由于均线策略的延迟性,策略没能避开15年底的反转带来的大回撤,表现并不如原始模型。
市场风格的变化不单单对此模型,对大部分策略都有很强的破坏性。因为模型总是基于市 场中已经存在的某种固有模式进行建模,当模式迅速切换时,模型很难及时反映。这也从另一方面证明了长期稳定策略的稀缺性。若想改善模型回撤,一个可以考虑的角度是增加 交易频率,甚至在日内做一些操作,放弃部分收益来降低波动与回撤,但这也势必带来交易成本的提高。

全部回复

0/140

达人推荐

量化课程

    移动端课程