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

量化交易吧 /  数理科学 帖子:3364712 新帖:0

风险平价

SCSDV_d发表于:5 月 21 日 15:42回复(1)

风险平价

研究目的:

本文参考天风证券研报《基于半衰主成分风险平价模型的全球资产配置策略研究》和《引入衰减加权和趋势跟踪的主成分风险平价模型研究》,对研报中的结果进行分析,研究了主成分风险平价模型在全球大类资产配置领域中的应用,通过比较基于风险因子的风险平价模型和基于资产本身的风险平价模型,实证主成分风险平价模型的优点。

研究内容:

资产配置的目的在于实现高分散低风险的投资组合,以期获得稳定的财富增值。当下资产配置实践中采用较多的是风险平价模型。传统的风险平价模型会对所有资产分配投资权重,忽略了资产之间的相关性。主成分风险平价模型对原资产进行线性变换形成互不相关的主成分组合,再通过传统风险平价模型确定主成分组合的投资权重,最终反变换为原资产的投资权重,从而较好的解决了传统风险平价模型处理高相关资产配置问题的弱点。

本篇报告关注了如下几方面问题:

(1)通过主成分分析法,将资产组合变换为主成分因子组合,在传统风险平价模型求解方法的基础上构建基于主成分风险因子的风险平价模型求解方法。

(2)引入资产预期风险估计和预期走势估计,演示了如何对资产配置模型做出进一步改进。

研究结论:

(1)本文资产配置的资产池包括全球主要股票市场、国内债券、国内外商品等。在2010.1-2018.10期间,主成分风险平价模型的年化收益为5.43%,高于传统风险平价模型的4.63%。最大回撤方面,主成分风险平价模型为2.02%,也要低于传统风险平价模型的3.37%。

(2)主成分风险平价模型旨在确保主成分风险因子上的等比例风险贡献。由于投资组合中有一半的资产都是权益类资产,因此传统风险平价模型中权益类资产提供了50%的风险贡献,而计算结果表明,主成分风险平价模型中权益类资产只提供了远小于50%的风险贡献,从而使得主成分风险平价模型组合的风险要低于传统风险平价模型组合。这体现了PCRP模型在高相关资产组合配置中的优势。

(3)在战术资产配置层面,将一个简单的风险预估和走势预估模型引入资产配置模型中,使得波动率与回撤指标得到改善,加强了模型的风险控制能力。

研究耗时:

(1)数据准备部分:约3分钟 (2)主成分风险平价模型介绍部分:约2分钟 (3)主成分风险平价模型应用实证部分:约30分钟 (4)预期风险估计和预期走势估计部分:约35分钟

编者按: 研报及本文中的复现过程没有考虑手续费,部分资产指数可能没有相应基金产品,另外也没有考虑资产的申购赎回周期。研报中的通过走势预估模型引入资产的月度择时,但是相应收益率的提升可能会无法覆盖手续费成本。总之本文主要目的在于实证一种新的风险平价理论:即基于风险因子的配置,而非基于资产本身的配置。主成分风险平价模型若要真正应用于投资实践,在资产挑选和战术资产配置层面还需进一步斟酌优化。

相关研报和数据下载地址:链接: https://pan.baidu.com/s/1CssR53H41Kki2K6J7ZSNJw 提取码: qy3h

from jqlib.technical_analysis import *
security_list1 = '000001.XSHE'
# 计算并输出 security_list1 的 KDJ 值
K1,D1,J1 = KDJ(security_list1, check_date='2017-01-04', N =9, M1=3, M2=3)

引言¶

资产配置的目的在于实现高分散低风险的投资组合,以期获得稳定的财富增值。当下资产配置实践中采用较多的是风险平价模型。传统的风险平价模型会对所有资产分配投资权重,忽略了资产之间的相关性。主成分风险平价模型对原资产进行线性变换形成互不相关的主成分组合,再通过传统风险平价模型确定主成分组合的投资权重,最终反变换为原资产的投资权重,从而较好的解决了传统风险平价模型处理高相关资产配置问题的弱点。本文参考天风证券研报《基于半衰主成分风险平价模型的全球资产配置策略研究》《引入衰减加权和趋势跟踪的主成分风险平价模型研究》(作者:吴先兴)对研报中的结果进行分析,实证了主成分风险平价模型在配置高相关资产方面的优势。

总耗时70分钟左右

1 数据准备¶

该部分耗时 3分钟

本文资产配置的资产池包括:沪深300、中证500、恒生指数、标普500、纳斯达克指数、富时100、法国CAC50、德国DAX、日经225、上证5年国债、上证企债、中信沪金商品指数、中信沪铜商品指数、中信玉米商品指数、中信郑棉商品指数、中信白糖商品指数、南华商品指数、布伦特原油共18类资产,涵盖全球主要股票市场、国内债券、国内外商品等。选取2009年1月至2018年9月期间上述指数的收盘价数据,并统一以A股市场交易日为准。为方便数据处理,将上述资产的收盘价数据保存为'全球资产指数行情序列.xlsx',并放在研究环境根目录下。

'全球资产指数行情序列.xlsx'可从压缩包中获取。

import numpy as np
import pandas as pd
from scipy.optimize import minimize
import datetime as dt
from six import StringIO
from dateutil.parser import parse
import cPickle as pickle
import seaborn as sns
import matplotlib as mpl

读取全球资产指数行情序列,并查看一下。

body=read_file('全球资产指数行情序列.xlsx')
prices=pd.read_excel(StringIO(body))
# print prices.columns
columns =[u'沪深300',u'中证500',u'恒生指数',u'标普500',u'纳斯达克指数',u'富时100',u'法国CAC40',u'德国DAX',u'日经225',\
        u'上证5年国债(全)',u'上证企债',u'中信沪金商品指数',u'中信沪铜商品指数',u'中信玉米商品指数',\
        u'中信郑棉商品指数', u'中信白糖商品指数',u'南华商品指数',u'布伦特原油']
prices.set_index(u'时间',inplace=True)
prices = prices[columns]['2008-12-31':]
prices.head().append(prices.tail())
沪深300 中证500 恒生指数 标普500 纳斯达克指数 富时100 法国CAC40 德国DAX 日经225 上证5年国债(全) 上证企债 中信沪金商品指数 中信沪铜商品指数 中信玉米商品指数 中信郑棉商品指数 中信白糖商品指数 南华商品指数 布伦特原油
时间
2008-12-31 1817.7220 1939.4330 14387.4805 903.25 1577.03 4434.17 3217.97 4810.20 8859.56 115.7790 132.6430 853.5811 1277.5431 842.4340 547.9712 423.5124 885.1068 636.2685
2009-01-05 1882.9590 2020.2430 15563.3096 927.45 1628.03 4579.64 3359.92 4983.99 9043.12 115.8110 132.8290 855.2462 1351.2160 848.4436 548.6684 431.5228 908.6223 682.9312
2009-01-06 1942.7950 2091.8920 15509.5098 934.70 1652.38 4638.92 3396.22 5026.31 9080.84 115.7310 132.9220 834.9947 1401.4227 845.7119 546.8093 433.1545 935.6748 694.2728
2009-01-07 1931.1780 2104.9220 14987.4600 906.65 1599.06 4507.51 3346.09 4937.47 9239.24 115.8330 132.9740 842.9603 1431.9833 842.9803 553.0838 435.0829 938.7079 634.3235
2009-01-08 1887.9910 2052.3000 14415.9102 909.73 1617.01 4505.37 3324.33 4879.91 8876.42 115.7940 133.2200 830.2693 1418.3402 841.3413 548.9008 427.9626 923.3180 619.7290
2018-10-25 3194.3084 4234.4208 24994.4600 2705.57 7318.34 7004.10 5032.30 11307.12 21268.73 154.6607 222.9333 1097.8627 3030.3180 1189.3417 738.8500 560.9553 1457.0900 635.1500
2018-10-26 3173.6350 4233.9585 24717.6300 2658.69 7167.21 6939.56 4967.37 11200.62 21184.60 154.7103 222.9593 1097.0732 3046.3607 1188.7124 734.4724 560.9553 1456.6500 641.6300
2018-10-29 3076.8889 4162.5255 24812.0400 2641.25 7050.29 7026.32 4989.35 11335.48 21149.80 154.8274 223.0604 1100.6261 3037.8377 1189.9710 726.4467 555.1913 1441.3500 639.3600
2018-10-30 3110.2605 4204.5424 24585.5300 2682.63 7161.65 7035.85 4978.53 11287.39 21457.29 154.9415 223.1180 1095.2967 3034.1850 1198.1517 722.7987 553.3425 1442.2900 627.6700
2018-10-31 3153.8234 4272.5518 24979.6900 2711.74 7305.90 7128.10 5093.44 11447.51 21920.46 154.9582 223.1473 1089.5725 2992.7876 1191.2296 723.5283 553.9950 1433.0200 620.1900

绘制全球各类资产指数的净值走势图

通过mpl.rcParams定义绘图中的字体和负号,否则可能出现中文乱码。

mpl.rcParams['font.family']='serif'
mpl.rcParams['axes.unicode_minus']=False # 处理负号
(prices/prices.iloc[0]).plot(figsize=(15,8),grid='on')
pct_daily = prices.pct_change()

计算各类资产的相关矩阵,并以热点图形式展示。热点图表明沪深300与中证500的相关性极高,标普500和纳斯达克指数的相关性极高,富时100、法国CAC40和德国DAX的相关性极高。可见投资组合中上述三组资产可分别视作一组资产。

fig = plt.figure(figsize= (15,10))
ax = fig.add_subplot(111)
ax = sns.heatmap(pct_daily.corr(),annot=True,annot_kws={'size':9,'weight':'bold'})

风险再平衡调仓时间为每季度末,回测区间为2010年1月1日至2018年9月30日,取出调仓节点日期及回测期间交易日。

#获取指定周期的日期列表 'W、M、Q'
def get_period_date(peroid,start_date, end_date):
    #设定转换周期period_type  转换为周是'W',月'M',季度线'Q',五分钟'5min',12天'12D'
    stock_data = get_price('000001.XSHE',start_date,end_date,'daily',fields=['close'])
    #记录每个周期中最后一个交易日
    stock_data['date']=stock_data.index
    #进行转换,周线的每个变量都等于那一周中最后一个交易日的变量值
    period_stock_data=stock_data.resample(peroid,how='last')
    date=period_stock_data.index
    pydate_array = date.to_pydatetime()
    date_only_array = np.vectorize(lambda s: s.strftime('%Y-%m-%d'))(pydate_array )
    date_only_series = pd.Series(date_only_array)
    start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
    start_date=start_date-datetime.timedelta(days=1)
    start_date = start_date.strftime("%Y-%m-%d")
    date_list=date_only_series.values.tolist()
    date_list.insert(0,start_date)
    return date_list

def get_near_tradingday(date):
    return (get_price('000001.XSHG',end_date=date,count=1).index[0])


date_list = (get_period_date('Q','2010-01-01', '2018-10-01'))
# type(date_list[0])
tradingday_list_tot = pct_daily.index.tolist()
tradingday_list = [date for date in pct_daily.index.tolist() if ((date)>=get_near_tradingday(date_list[0]) \
                                                                 and (date)<=get_near_tradingday(date_list[-1]))]
np.array(date_list)
# tradingday_list
array([2009-12-31, 2010-03-31, 2010-06-30, 2010-09-30, 2010-12-31,
       2011-03-31, 2011-06-30, 2011-09-30, 2011-12-31, 2012-03-31,
       2012-06-30, 2012-09-30, 2012-12-31, 2013-03-31, 2013-06-30,
       2013-09-30, 2013-12-31, 2014-03-31, 2014-06-30, 2014-09-30,
       2014-12-31, 2015-03-31, 2015-06-30, 2015-09-30, 2015-12-31,
       2016-03-31, 2016-06-30, 2016-09-30, 2016-12-31, 2017-03-31,
       2017-06-30, 2017-09-30, 2017-12-31, 2018-03-31, 2018-06-30,
       2018-09-30], 
      dtype='|S10')

2 主成分风险平价模型¶

该部分耗时 2分钟

主成分风险平价模型本质上是对资产组合的主成分风险因子进行风险平价配置,因此先对传统风险平价模型做个简单介绍。 主成分分析是一种基于降维思想把多个资产利用数学变换转化为少数几个主成分(即综合变量)的多元统计分析方法,这些主成分能够反映原始资产的大部分信息,更具有现实意义,通常表现为原始资产的线性组合,为使得这些主成分所包含的信息互不重叠,要求各主成分之间互不相关。本报告模型的基本思路是运用主成分分析对投资组合中的标的资产进行旋转,提取所有不相关的资产进行风险平价模型分析,最后反推确定原资产的投资权重。

get_smart_weight函数中参数cov_adjusted代表是否对协方差矩阵的估计做出修正,从而对预估风险做出修正,本文的后续将对此进行讨论。

get_smart_weight函数中的的fun1用来求解传统风险平价模型,fun2用来求解主成分风险平价模型。

a为资产收益率协方差矩阵的特征值,b为对应的特征向量。

def get_smart_weight(pct, method='risk parity', cov_adjusted=False, wts_adjusted=False):
    if cov_adjusted == False:
        #协方差矩阵
        cov_mat = pct.cov()
    else:
        #调整后的半衰协方差矩阵
        cov_mat = pct.iloc[:len(pct)/4].cov()*(1/10.) + pct.iloc[len(pct)/4+1:len(pct)/2].cov()*(2/10.) +\
            pct.iloc[len(pct)/2+1:len(pct)/4*3].cov()*(3/10.) + pct.iloc[len(pct)/4*3+1:].cov()*(4/10.)
    if not isinstance(cov_mat, pd.DataFrame):
        raise ValueError('cov_mat should be pandas DataFrame!')
        
    omega = np.matrix(cov_mat.values)  # 协方差矩阵

    a, b = np.linalg.eig(np.array(cov_mat)) #a为特征值,b为特征向量
    a = matrix(a)
    b = matrix(b)
    # 定义目标函数
    
    def fun1(x):
        tmp = (omega * np.matrix(x).T).A1
        risk = x * tmp/ np.sqrt(np.matrix(x) * omega * np.matrix(x).T).A1[0]
        delta_risk = [sum((i - risk)**2) for i in risk]
        return sum(delta_risk)
    
    def fun2(x):
        tmp = (b**(-1) * omega * np.matrix(x).T).A1
        risk = (b**(-1)*np.matrix(x).T).A1 * tmp/ np.sqrt(np.matrix(x) * omega * np.matrix(x).T).A1[0]
        delta_risk = [sum((i - risk)**2) for i in risk]
        return sum(delta_risk)
    
    # 初始值 + 约束条件 
    x0 = np.ones(omega.shape[0]) / omega.shape[0]  
    bnds = tuple((0,None) for x in x0)
    cons = ({'type':'eq', 'fun': lambda x: sum(x) - 1})
    options={'disp':False, 'maxiter':1000, 'ftol':1e-20}
        
    if method == 'risk parity':
        res = minimize(fun1, x0, bounds=bnds, constraints=cons, method='SLSQP', options=options)
    elif method == 'pc risk parity':
        res = minimize(fun2, x0, bounds=bnds, constraints=cons, method='SLSQP', options=options)
    else:
        raise ValueError('method error!!!')
        
    # 权重调整
    if res['success'] == False:
        # print res['message']
        pass
    wts = pd.Series(index=cov_mat.index, data=res['x'])
    
    if wts_adjusted == True:
        wts[wts < 0.0001]=0.0
        wts = wts / wts.sum()
    elif wts_adjusted == False:
        wts = wts / wts.sum()
    else:
        raise ValueError('wts_adjusted should be True/False!')
        
    risk = pd.Series(wts * (omega * np.matrix(wts).T).A1 / np.sqrt(np.matrix(wts) * omega * np.matrix(wts).T).A1[0],index = cov_mat.index)
    risk[risk<0.0] = 0.0
    return wts,risk
pct_daily_new = pct_daily.reindex(tradingday_list) #回测期间的资产收益率
pct_daily_new.head().append(pct_daily_new.tail())
沪深300 中证500 恒生指数 标普500 纳斯达克指数 富时100 法国CAC40 德国DAX 日经225 上证5年国债(全) 上证企债 中信沪金商品指数 中信沪铜商品指数 中信玉米商品指数 中信郑棉商品指数 中信白糖商品指数 南华商品指数 布伦特原油
时间
2009-12-31 0.004728 0.008814 0.017486 -0.010050 -0.009658 0.002783 0.000211 0.000000 0.000000 0.000771 -0.000090 0.009495 0.015916 0.001113 0.001819 0.009788 0.012568 -0.001013
2010-01-04 -0.011314 0.005634 -0.002250 0.016043 0.017306 0.016158 0.019724 0.015253 0.010274 0.001260 0.002254 0.001669 0.011683 0.007271 0.011498 0.031724 0.005822 0.027222
2010-01-05 0.008149 0.010409 0.020909 0.003116 0.000126 0.004029 -0.000264 -0.002718 0.002538 -0.001145 -0.001158 0.021910 -0.000166 0.003238 -0.008376 0.015887 0.007661 0.005325
2010-01-06 -0.006260 -0.001579 0.006153 0.000546 -0.003301 0.001365 0.001186 0.000409 0.004645 -0.001794 0.000464 0.003063 0.017230 0.005277 -0.013575 -0.001009 0.008795 0.015639
2010-01-07 -0.019841 -0.020026 -0.006567 0.004001 -0.000452 -0.000600 0.001775 -0.002481 -0.004640 -0.000298 0.000232 0.004045 0.007492 -0.001050 -0.009786 -0.018684 -0.014868 -0.006302
2018-09-21 0.030319 0.015155 0.017320 -0.000369 -0.005141 0.016683 0.007811 0.008470 0.008237 -0.000196 0.000254 0.001119 0.003236 0.000000 0.002801 0.000810 0.003645 0.000327
2018-09-25 -0.008996 -0.002636 -0.016248 -0.004816 0.002568 0.002314 -0.002743 -0.004523 0.002946 0.000598 0.000609 -0.002979 0.011694 -0.011111 -0.004966 0.001215 0.000772 0.038838
2018-09-26 0.011077 0.004816 0.011545 -0.003289 -0.002136 0.000523 0.006138 0.000907 0.003907 0.000468 0.000212 0.002428 0.007971 -0.003745 -0.000624 0.010716 0.001780 -0.005729
2018-09-27 -0.003995 -0.012511 -0.003638 0.002763 0.006458 0.004520 0.005021 0.004013 -0.009863 0.000927 0.000172 -0.002795 -0.003756 -0.004296 -0.015605 0.001400 -0.005601 0.007376
2018-09-28 0.010364 0.009536 0.002628 -0.000007 0.000545 -0.004670 -0.008469 -0.015187 0.013586 0.000983 0.000312 -0.007661 -0.008335 -0.002157 -0.007926 -0.002397 0.000244 0.016649

3 主成分风险模型的应用实证¶

该部分耗时 30分钟

使用风险平价模型和主成分风险平价模型对前述资产组合进行回测检验。每季度末进行资产风险再平衡。

资产收益率协方差矩阵根据历史240个交易日进行估计。

计算过程中将每个调仓节点的资产权重(weight)和风险贡献(risk)保存下来。

def get_near_tradingday(date):
    return (get_price('000001.XSHG',end_date=date,count=1).index[0])
exchange_day = map(get_near_tradingday,date_list)
np.array(exchange_day)
array([2009-12-31 00:00:00, 2010-03-31 00:00:00, 2010-06-30 00:00:00,
       2010-09-30 00:00:00, 2010-12-31 00:00:00, 2011-03-31 00:00:00,
       2011-06-30 00:00:00, 2011-09-30 00:00:00, 2011-12-30 00:00:00,
       2012-03-30 00:00:00, 2012-06-29 00:00:00, 2012-09-28 00:00:00,
       2012-12-31 00:00:00, 2013-03-29 00:00:00, 2013-06-28 00:00:00,
       2013-09-30 00:00:00, 2013-12-31 00:00:00, 2014-03-31 00:00:00,
       2014-06-30 00:00:00, 2014-09-30 00:00:00, 2014-12-31 00:00:00,
       2015-03-31 00:00:00, 2015-06-30 00:00:00, 2015-09-30 00:00:00,
       2015-12-31 00:00:00, 2016-03-31 00:00:00, 2016-06-30 00:00:00,
       2016-09-30 00:00:00, 2016-12-30 00:00:00, 2017-03-31 00:00:00,
       2017-06-30 00:00:00, 2017-09-29 00:00:00, 2017-12-29 00:00:00,
       2018-03-30 00:00:00, 2018-06-29 00:00:00, 2018-09-28 00:00:00], dtype=object)
weight_rp_df = pd.DataFrame()
risk_rp_df = pd.DataFrame()
weight_pcrp_df = pd.DataFrame()
risk_pcrp_df = pd.DataFrame()
t1=dt.datetime.now()
for date in date_list[:]:
    t2=dt.datetime.now()
    print ('计算到%s,已耗时%s秒'%(date,(t2-t1).seconds))
    date_pre_y = tradingday_list_tot[tradingday_list_tot.index(get_near_tradingday(date))-240]
    weight_and_risk_rp = get_smart_weight(pct_daily.loc[date_pre_y:get_near_tradingday(date)],method='risk parity', cov_adjusted=0, wts_adjusted=1)#.reindex(pct_daily.columns).fillna(0)
    weight_rp_df.loc[:,get_near_tradingday(date)] = weight_and_risk_rp[0]
    risk_rp_df.loc[:,get_near_tradingday(date)] = weight_and_risk_rp[1]
    weight_and_risk_pcrp = get_smart_weight(pct_daily.loc[date_pre_y:get_near_tradingday(date)],method='pc risk parity', cov_adjusted=0, wts_adjusted=1)#.reindex(pct_daily.columns).fillna(0)
    weight_pcrp_df.loc[:,get_near_tradingday(date)] = weight_and_risk_pcrp[0]
    risk_pcrp_df.loc[:,get_near_tradingday(date)] = weight_and_risk_pcrp[1]
    
计算到2009-12-31,已耗时0秒
计算到2010-03-31,已耗时74秒
计算到2010-06-30,已耗时136秒
计算到2010-09-30,已耗时183秒
计算到2010-12-31,已耗时252秒
计算到2011-03-31,已耗时304秒
计算到2011-06-30,已耗时361秒
计算到2011-09-30,已耗时414秒
计算到2011-12-31,已耗时505秒
计算到2012-03-31,已耗时618秒
计算到2012-06-30,已耗时698秒
计算到2012-09-30,已耗时772秒
计算到2012-12-31,已耗时841秒
计算到2013-03-31,已耗时924秒
计算到2013-06-30,已耗时1026秒
计算到2013-09-30,已耗时1123秒
计算到2013-12-31,已耗时1233秒
计算到2014-03-31,已耗时1327秒
计算到2014-06-30,已耗时1441秒
计算到2014-09-30,已耗时1575秒
计算到2014-12-31,已耗时1707秒
计算到2015-03-31,已耗时1835秒
计算到2015-06-30,已耗时1955秒
计算到2015-09-30,已耗时2077秒
计算到2015-12-31,已耗时2162秒
计算到2016-03-31,已耗时2268秒
计算到2016-06-30,已耗时2360秒
计算到2016-09-30,已耗时2445秒
计算到2016-12-31,已耗时2545秒
计算到2017-03-31,已耗时2653秒
计算到2017-06-30,已耗时2772秒
weight_rp_df= weight_rp_df.T.reindex(tradingday_list).fillna(method='ffill')#风险平价模型的资产权重
weight_rp_df.head()
weight_pcrp_df= weight_pcrp_df.T.reindex(tradingday_list).fillna(method='ffill')#主成分风险平价模型的资产权重
weight_pcrp_df.head()
risk_rp_df= risk_rp_df.T.reindex(tradingday_list).fillna(method='ffill')#风险平价模型的资产风险贡献权重
risk_rp_df.head()
沪深300 中证500 恒生指数 标普500 纳斯达克指数 富时100 法国CAC40 德国DAX 日经225 上证5年国债(全) 上证企债 中信沪金商品指数 中信沪铜商品指数 中信玉米商品指数 中信郑棉商品指数 中信白糖商品指数 南华商品指数 布伦特原油
2009-12-31 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097
2010-01-04 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097
2010-01-05 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097
2010-01-06 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097
2010-01-07 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097 0.000097
risk_pcrp_df= risk_pcrp_df.T.reindex(tradingday_list).fillna(method='ffill')#主成分风险平价模型的资产风险贡献权重
risk_pcrp_df.head()
沪深300 中证500 恒生指数 标普500 纳斯达克指数 富时100 法国CAC40 德国DAX 日经225 上证5年国债(全) 上证企债 中信沪金商品指数 中信沪铜商品指数 中信玉米商品指数 中信郑棉商品指数 中信白糖商品指数 南华商品指数 布伦特原油
2009-12-31 0 0 0 0 0 0.000023 0 0 0 0.000098 0.000271 0.000139 0.000096 0.000221 0.000261 0.000088 0 0
2010-01-04 0 0 0 0 0 0.000023 0 0 0 0.000098 0.000271 0.000139 0.000096 0.000221 0.000261 0.000088 0 0
2010-01-05 0 0 0 0 0 0.000023 0 0 0 0.000098 0.000271 0.000139 0.000096 0.000221 0.000261 0.000088 0 0
2010-01-06 0 0 0 0 0 0.000023 0 0 0 0.000098 0.000271 0.000139 0.000096 0.000221 0.000261 0.000088 0 0
2010-01-07 0 0 0 0 0 0.000023 0 0 0 0.000098 0.000271 0.000139 0.000096 0.000221 0.000261 0.000088 0 0
pct_daily = pct_daily.reindex(tradingday_list)
pct_daily.head()
沪深300 中证500 恒生指数 标普500 纳斯达克指数 富时100 法国CAC40 德国DAX 日经225 上证5年国债(全) 上证企债 中信沪金商品指数 中信沪铜商品指数 中信玉米商品指数 中信郑棉商品指数 中信白糖商品指数 南华商品指数 布伦特原油
时间
2009-12-31 0.004728 0.008814 0.017486 -0.010050 -0.009658 0.002783 0.000211 0.000000 0.000000 0.000771 -0.000090 0.009495 0.015916 0.001113 0.001819 0.009788 0.012568 -0.001013
2010-01-04 -0.011314 0.005634 -0.002250 0.016043 0.017306 0.016158 0.019724 0.015253 0.010274 0.001260 0.002254 0.001669 0.011683 0.007271 0.011498 0.031724 0.005822 0.027222
2010-01-05 0.008149 0.010409 0.020909 0.003116 0.000126 0.004029 -0.000264 -0.002718 0.002538 -0.001145 -0.001158 0.021910 -0.000166 0.003238 -0.008376 0.015887 0.007661 0.005325
2010-01-06 -0.006260 -0.001579 0.006153 0.000546 -0.003301 0.001365 0.001186 0.000409 0.004645 -0.001794 0.000464 0.003063 0.017230 0.005277 -0.013575 -0.001009 0.008795 0.015639
2010-01-07 -0.019841 -0.020026 -0.006567 0.004001 -0.000452 -0.000600 0.001775 -0.002481 -0.004640 -0.000298 0.000232 0.004045 0.007492 -0.001050 -0.009786 -0.018684 -0.014868 -0.006302

根据历史资产权重及日收益率,分别绘制风险平价(RP)模型和主成分风险平价(PCRP)模型的净值曲线图。

可以看出PCRP模型的净值曲线波动明显低于RP模型,收益率也要优于RP模型。

net_value=pd.DataFrame()
net_value['rp'] = (weight_rp_df*(pct_daily_new+1)).sum(axis=1).cumprod()
net_value['pcrp'] = (weight_pcrp_df*(pct_daily_new+1)).sum(axis=1).cumprod()

net_value['rp'] = ((weight_rp_df*pct_daily_new).sum(axis=1)+1).cumprod()
net_value['pcrp'] = ((weight_pcrp_df*pct_daily_new).sum(axis=1)+1).cumprod()

net_value[['rp','pcrp']].plot(figsize=(15,6),grid='on',rot=60)
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c98de7410>
net_value

下面分析RP模型和PCRP模型的资产权重分布图和风险贡献分布图。

资产权重方面: RP模型在国债和企债上的投资比例相对均衡,而PCRP模型主要投资于企债指数,在各资产上的投资权重变化速度也要明显高于RP模型。

风险贡献方面: RP模型中各类资产对投资组合的风险贡献比例相等,事实上RP模型各类资产的投资权重是由风险贡献比例相等反推确定的。PCRP模型旨在确保主成分风险因子上的等比例风险贡献。由于投资组合中有一半的资产都是权益类资产,因此RP模型中权益类资产提供了50%的风险贡献,而风险贡献图中表明大部分情况下PCRP模型中权益类资产只提供了远小于50%的风险贡献,从而使得PCRP组合的风险要低于RP组合。这体现了PCRP模型在高相关资产组合配置中的优势。

weight_pcrp_df.reindex(exchange_day).plot(figsize=(15,8),rot=60,kind='area',ylim=(0,1))#PCRP模型资产权重图
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c992c6890>
risk_pcrp_df.reindex(exchange_day).plot(figsize=(15,8),rot=60,kind='area')#PCRP模型资产风险贡献图
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c99e524d0>
weight_rp_df.reindex(exchange_day).plot(figsize=(15,8),rot=60,kind='area',ylim=(0,1))#RP模型资产权重图
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c98f87990>
risk_rp_df.reindex(exchange_day).plot(figsize=(15,8),rot=60,kind='area')#RP模型资产风险贡献图
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c99a97490>

4 预期风险估计和预期走势估计¶

该部分耗时 35分钟

4.1 引入预期风险估计¶

相比资产收益率的难预测性,资产的波动率具有较好的可预测性。资产波动率具有比较明显的自相关性,同时资产波动率的自相关性会呈现衰减性。

%E6%B3%A2%E5%8A%A8%E7%8E%87%E8%81%9A%E9%9B%86.png 以沪深300波动率为例,随着滞后阶数的增加,沪深300月波动率的分布越发分散,同时回归模型的拟合优度也在逐渐降低。

绝大多数资产的波动率都存在着明显的自相关性,以及自相关性随着滞后阶数的增加具有明显的衰减性。

基于资产波动率的聚集性还衰减性,可以改进对资产预期风险的估计。具体的,以60个交易日为分界,计算过去240个交易日的4个阶段协方差($\Sigma_1,\Sigma_2,\Sigma_3,\Sigma_4$),以衰退的权重($w_1,w_2,w_3,w_4$)估计预期的协方差$\Sigma = w_1\Sigma_1+w_2\Sigma_2+w_3\Sigma_3+w_4\Sigma_4$。

本文中的风险衰减系数设置为($w_1=0.4,w_2=0.3,w_3=0.2,w_4=0.1$)。

下面根据改进的预期风险估计方法对每期的协方差进行估计,形成衰减加权的风险平价(wdc_RP)模型和衰减加权的主成分风险平价(wdc_PCRP)模型。(wdc for weight decayed)

pct_daily = prices.pct_change()
weight_wdc_rp_df=pd.DataFrame()
risk_wdc_rp_df=pd.DataFrame()
weight_wdc_pcrp_df=pd.DataFrame()
risk_wdc_pcrp_df=pd.DataFrame()
t1=dt.datetime.now()
for date in date_list[:]:
    t2=dt.datetime.now()
    print ('计算到%s,已耗时%s秒'%(date,(t2-t1).seconds))
    date_pre_y = tradingday_list_tot[tradingday_list_tot.index(get_near_tradingday(date))-240]
    weight_and_risk_wdc_rp = get_smart_weight(pct_daily.loc[date_pre_y:get_near_tradingday(date)],method='risk parity', cov_adjusted=1, wts_adjusted=1)
    #衰减加权的风险平价(wdc_RP)模型资产权重
    weight_wdc_rp_df.loc[:,get_near_tradingday(date)] = weight_and_risk_wdc_rp[0]
    #衰减加权的风险平价(wdc_RP)模型风险贡献比例
    risk_wdc_rp_df.loc[:,get_near_tradingday(date)] = weight_and_risk_wdc_rp[1]
    weight_and_risk_wdc_pcrp = get_smart_weight(pct_daily.loc[date_pre_y:get_near_tradingday(date)],method='pc risk parity', cov_adjusted=1, wts_adjusted=1)
    #衰减加权的主成分风险平价(wdc_PCRP)模型资产权重
    weight_wdc_pcrp_df.loc[:,get_near_tradingday(date)] = weight_and_risk_wdc_pcrp[0]
    #衰减加权的主成分风险平价(wdc_PCRP)模型风险贡献权重
    risk_wdc_pcrp_df.loc[:,get_near_tradingday(date)] = weight_and_risk_wdc_pcrp[1]
    
weight_wdc_rp_df= weight_wdc_rp_df.T.reindex(tradingday_list).fillna(method='ffill')
weight_wdc_pcrp_df= weight_wdc_pcrp_df.T.reindex(tradingday_list).fillna(method='ffill')
计算到2009-12-31,已耗时0秒
计算到2010-03-31,已耗时28秒
计算到2010-06-30,已耗时45秒
计算到2010-09-30,已耗时71秒
计算到2010-12-31,已耗时102秒
计算到2011-03-31,已耗时121秒
计算到2011-06-30,已耗时141秒
计算到2011-09-30,已耗时160秒
计算到2011-12-31,已耗时220秒
计算到2012-03-31,已耗时249秒
计算到2012-06-30,已耗时282秒
计算到2012-09-30,已耗时326秒
计算到2012-12-31,已耗时353秒
计算到2013-03-31,已耗时385秒
计算到2013-06-30,已耗时438秒
计算到2013-09-30,已耗时480秒
计算到2013-12-31,已耗时536秒
计算到2014-03-31,已耗时590秒
计算到2014-06-30,已耗时631秒
计算到2014-09-30,已耗时680秒
计算到2014-12-31,已耗时717秒
计算到2015-03-31,已耗时761秒
计算到2015-06-30,已耗时798秒
计算到2015-09-30,已耗时843秒
计算到2015-12-31,已耗时883秒
计算到2016-03-31,已耗时919秒
计算到2016-06-30,已耗时956秒
计算到2016-09-30,已耗时993秒
计算到2016-12-31,已耗时1031秒
计算到2017-03-31,已耗时1071秒
计算到2017-06-30,已耗时1113秒
计算到2017-09-30,已耗时1164秒
计算到2017-12-31,已耗时1210秒
计算到2018-03-31,已耗时1271秒
计算到2018-06-30,已耗时1315秒
计算到2018-09-30,已耗时1364秒

引入预期风险估计的资产配置模型与传统资产配置模型相比,虽然年化收益率没有提升,但是使得模型的年化波动率和最大回撤降低,从而在一定程度上提升投资组合的收益风险比。

#衰减加权的风险平价(wdc_RP)模型净值
net_value['wdc_rp'] = ((weight_wdc_rp_df*pct_daily_new).sum(axis=1)+1).cumprod()
#衰减加权的主成分风险平价(wdc_PCRP)模型净值
net_value['wdc_pcrp'] = ((weight_wdc_pcrp_df*pct_daily_new).sum(axis=1)+1).cumprod()
net_value[['wdc_pcrp','pcrp']].plot(figsize=(15,6),grid='on',rot=60)
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c98580410>

4.2 引入预期走势估计¶

趋势跟踪是一种利用在不同市场交易的长期变化进行投资的交易技术,可应用于所有时间交易模型。其基本假设是市场会保持上涨或者下跌的趋势,即在上涨时做多,在下跌时做空,并预期这种趋势会继续保持。本文以简单的均线法为例,测算预期走势估计对资产配置模型的改进作用。具体的,当资产价格上穿周期移动平均线时,可以判断为买入信号;当资产价格下穿周期移动平均线时,可以判断为卖出信号。

$$w^{'}_{t,i}=logical(p_{t,i}-MA(n)_{t,i})\times{w_{t,i}}$$

其中$logical(x)$为逻辑函数,$logical(x\geq0)=1$;$logical(x\leq0)=0$。

虽然移动平均线所反映的信息存在一定的时滞性,但移动平均线是对历史趋势的有效呈现, 在一定程度上可以降低噪音对市场趋势判断的负面影响。

本文以月末资产价格为代理变量进行研究,均线窗口期为3个月,当预期走势看空时,选择卖出该资产,并以无风险资产(日收益率简化为1.25%%)进行替代。

#按月末价格进行重采样
prices_M = prices.resample('M',how='last')
#根据价格与均线关系判断择时信号
TF=(prices_M>pd.rolling_mean(prices_M,3)).shift(1)
#将择时信号与交易日进行对齐
TF['near_date']=map(get_near_tradingday,TF.index)
#根据择时信号调整资产权重(若该资产看多:仓位为100%或看空:仓位为0%)
weight_adjusted=(TF.set_index('near_date').reindex(tradingday_list).fillna(method='ffill'))
#无风险收益率
pct_norisk = 1.25e-4
#引入预期走势估计的RP模型净值,等式右边第二项为用无风险资产替换看空仓位部分的收益
net_value['TF_rp']=((weight_adjusted*weight_rp_df*pct_daily_new).sum(axis=1)+\
                    (1-(weight_adjusted*weight_rp_df).sum(axis=1))*pct_norisk+1).cumprod()
#引入预期走势估计的PCRP模型净值,等式右边第二项为用无风险资产替换看空仓位部分的收益
net_value['TF_pcrp']=((weight_adjusted*weight_pcrp_df*pct_daily_new).sum(axis=1)+\
                    (1-(weight_adjusted*weight_pcrp_df).sum(axis=1))*pct_norisk+1).cumprod()

#引入预期走势估计的wdc_RP模型净值,等式右边第二项为用无风险资产替换看空仓位部分的收益
net_value['TF_wdc_rp']=((weight_adjusted*weight_wdc_rp_df*pct_daily_new).sum(axis=1)+\
                    (1-(weight_adjusted*weight_wdc_rp_df).sum(axis=1))*pct_norisk+1).cumprod()
#引入预期走势估计的wdc_PCRP模型净值,等式右边第二项为用无风险资产替换看空仓位部分的收益
net_value['TF_wdc_pcrp']=((weight_adjusted*weight_wdc_pcrp_df*pct_daily_new).sum(axis=1)+\
                    (1-(weight_adjusted*weight_wdc_pcrp_df).sum(axis=1))*pct_norisk+1).cumprod()
net_value[['TF_rp','rp']].plot(figsize=(15,6),grid='on',rot=60)

net_value[['TF_pcrp','pcrp']].plot(figsize=(15,6),grid='on',rot=60)
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c983e1610>

绘制衰减加权的风险平价(wdc_RP)模型的资产权重图

weight_wdc_rp_df.reindex(exchange_day).plot(figsize=(15,8),rot=60,kind='area',ylim=(0,1))
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c9831abd0>

绘制引入预期走势估计的衰减加权风险平价(TF_wdc_RP)模型的资产权重图。可见在引入预期走势后,模型在大致预判各类资产未来走势的同时, 大幅度降低投资组合的风险,例如股灾期间对权益类资产的看空,避免了组合的较大回撤。

(weight_adjusted*weight_wdc_rp_df).reindex(exchange_day).plot(figsize=(15,8),rot=60,kind='area',ylim=(0,1))
<matplotlib.axes._subplots.AxesSubplot at 0x7f8c98f0b590>

为了方便后续对数据进行处理,将数据保存为pkl文件,数据类型为dict,key为数据种类,value为相应数据的时间序列。

#使用pickle模块将数据对象保存到文件
save_data={}
save_data['weight_rp'] = weight_rp_df
save_data['weight_pcrp'] = weight_pcrp_df
save_data['weight_wdc_rp'] = weight_wdc_rp_df
save_data['weight_wdc_pcrp'] = weight_wdc_pcrp_df
save_data['risk_rp'] = risk_rp_df
save_data['risk_pcrp'] = risk_pcrp_df
save_data['risk_wdc_rp'] = risk_wdc_rp_df
save_data['risk_wdc_pcrp'] = risk_wdc_pcrp_df
save_data['net_value'] = net_value
content = pickle.dumps(save_data) 
write_file('save_data_v4.pkl', content, append=False)
pkl_file_read = read_file('save_data_v4.pkl')
load_data = pickle.load(StringIO(pkl_file_read))
net_value = load_data['net_value']
weight_rp_df = load_data['weight_rp']
weight_pcrp_df = load_data['weight_pcrp']
weight_wdc_rp_df = load_data['weight_wdc_rp']
weight_wdc_pcrp_df = load_data['weight_wdc_pcrp']

4.3 改进的主成分风险平价模型收益风险指标汇总¶

class backtest_result():
    def __init__(self,data):
        self.data = data
        self.total_returns = data.iloc[-1]-1
        self.annualized_returns = data.iloc[-1]**(250./len(data))-1
        self.annualized_volatility = data.pct_change().std()*(250.**0.5)
    def Max_Drawback(self):
        net_value=self.data
        max_value=0
        df_tmp=pd.DataFrame(net_value)
        df_tmp.columns=['value']
        for j in range(0,len(net_value),1):
            max_value=max(max_value,df_tmp.ix[j,'value'])
            df_tmp.ix[j,'drawback']=1-df_tmp.ix[j,'value']/max_value
            drawback=df_tmp['drawback'].max()
        return drawback
    def Sharpe(self):
        net_value=self.data
        bench_pct=0.03
        df_tmp=pd.DataFrame(net_value)
        df_tmp.columns=['value']
        df_tmp['pct']=df_tmp['value'].pct_change()
        annual_pct = df_tmp.ix[-1,'value']**(250./len(df_tmp))-1
        sharpe = (annual_pct-bench_pct)/(df_tmp['pct'].std()*250**0.5)
        return sharpe
    def Calmar(self):
        clamar = self.annualized_returns/self.Max_Drawback()
        return clamar


results=pd.DataFrame()
for c in ['rp','pcrp','wdc_rp','wdc_pcrp','TF_pcrp','wdc_pcrp','TF_wdc_pcrp']:
    nv = net_value[c]
    results.loc['累计收益率',c]=backtest_result(nv).total_returns
    results.loc['年化收益率',c]=backtest_result(nv).annualized_returns
    results.loc['年化波动率',c]=backtest_result(nv).annualized_volatility
    results.loc['最大回撤',c]=backtest_result(nv).Max_Drawback()
    results.loc['夏普比率',c]=backtest_result(nv).Sharpe()
    results.loc['Calmar比率',c]=backtest_result(nv).Calmar()
results
rp pcrp wdc_rp wdc_pcrp TF_pcrp TF_wdc_pcrp
累计收益率 0.471092 0.568645 0.481557 0.528262 0.580823 0.547624
年化收益率 0.046392 0.054315 0.047264 0.051090 0.055274 0.052645
年化波动率 0.019564 0.012801 0.018862 0.012054 0.010113 0.009500
最大回撤 0.033662 0.020180 0.033950 0.016934 0.016157 0.011075
夏普比率 0.837864 1.899482 0.915267 1.749604 2.499080 2.383653
Calmar比率 1.378185 2.691593 1.392186 3.017035 3.421076 4.753563

5 总结¶

主成分风险平价模型关注的是资产背后的主成分风险因子,因此在收益和风险等诸多方面相对于传统风险平价模型均具有明显的优势。

引入风险预估和走势预估后,波动率与回撤指标得到改善,加强了模型的风险控制能力。当然,不同类型资产的风险预估和走势预估存在一定的差异性,本文在此仅是简单的统一处理,还有许多可改进的空间。

全部回复

0/140

量化课程

    移动端课程