文章目录
1. 引言
1.1 前文回顾与问题提出
在之前的文章中,我们介绍了马科维茨模型在中国市场资产配置中的应用。(详情请见【基金量化研究系列】大类资产配置研究(四)——基于马科维茨模型的资产配置研究)。但是,由于马科维茨模型中存在的诸多问题,资产配置的效果却不尽如人意。
我们之前的实证研究已经提到过:波动率与未来的收益率呈现出显著的负相关关系,因此我们可以以某类资产的波动率作为先行指标:当资产波动率不断上升时,资产收益的下行风险显著增加,在这种情况下我们应该对该类资产进行减仓。我们很自然地想到一种配置思路:如果让所有的风险资产对投资组合的总风险的贡献均有相同的比例(即风险平价),那这样一种策略会不会既稳健又有较高的收益率呢?
在这篇文章中,就让我们来验证一下这样的观点吧!
1.2 数据收集与标的资产选取
本文以开源的Choice数据库作为本文的数据来源。关于Choice数据库的使用方法及说明请参考:Choice数据量化接口产品手册。
对于标的资产的选择,我们仍以“易方达沪深300ETF(510310)”与“国泰上证5年国债ETF(511010)”作为股票市场与债券市场的基准可投标的,并以“华安黄金易ETF(518880)”和“七日深圳国债逆回购R-007(131801)”作为商品和无风险资产的基准可投资标的。这些标的均可以在券商账户中进行交易。
1.3 基本假设
本文仍延续前文的基本假设,规定:
(1)市场中不允许融资做多与融券做空;
(2)建仓调仓过程中不允许借款、参与国债回购;
(3)每日仅在收盘时间以收盘价格进行交易;
(4)未特别规定,忽略调、建仓手续费。
2. 风险平价策略简介
2.1 模型的建立
所谓 风险平价(Risk Parity) ,即将 投资组合的总风险平均分散入各资产,使得每一类资产贡献的风险相等。因此,首先要解决的问题,就是如何去衡量各类资产所贡献的风险大小。
首先,组合资产的波动率满足:
σ p = ( ω T Σ ω ) 1 2 \sigma_p = (\omega^T \Sigma \omega )^{ \frac{1}{2}}σp=(ωTΣω)21
其中:
· σp 表示组合资产的标准差;
· Σ 表示风险资产的协方差矩阵;
· ω 表示风险资产的权重列向量。
定义第 i 类资产的风险贡献率(Risk Contribution)RCi:
RC i = 1 σ p ω i ∂ σ p ∂ ω i , i = 1 , 2 , . . . , N \text{RC}_i = \frac{1}{\sigma_p} \omega_i \frac{\partial\sigma_p}{\partial\omega_i}, \ i = 1, 2, ..., NRCi=σp1ωi∂ωi∂σp, i=1,2,...,N
其中:
∂ σ p ∂ ω i = ∂ ( ω T Σ ω ) 1 2 ∂ ω i = ( ω T Σ ) i ω T Σ ω , i = 1 , 2 , . . . , N \frac{\partial\sigma_p}{\partial\omega_i} = \frac{\partial(\omega^T \Sigma \omega )^{ \frac{1}{2}}}{\partial\omega_i} = \frac{(\omega^T\Sigma)_i }{\sqrt{\omega^T \Sigma \omega}} , \ i = 1, 2, ..., N∂ωi∂σp=∂ωi∂(ωTΣω)21=ωTΣω(ωTΣ)i, i=1,2,...,N
因此, RCi 可以变型为:
RC i = ω i ( ω T Σ ) i ω T Σ ω , i = 1 , 2 , . . . , N \text{RC}_i = \frac{\omega_i(\omega^T\Sigma)_i }{\omega^T \Sigma \omega}, \ i = 1, 2, ..., NRCi=ωTΣωωi(ωTΣ)i, i=1,2,...,N
上式的分子项表示由第 i 类资产贡献的方差,分母则表示组合资产的总方差。
若想让每一类风险资产对组合的风险贡献率相等,对于任意的 i,RCi 应满足:
RC i = ω i ( ω T Σ ) i ω T Σ ω = 1 N , i = 1 , 2 , . . . , N \text{RC}_i = \frac{\omega_i(\omega^T\Sigma)_i }{\omega^T \Sigma \omega}= \frac{1}{N}, \ i = 1, 2, ..., NRCi=ωTΣωωi(ωTΣ)i=N1, i=1,2,...,N
或者可以等价转换为一个 N 元非线性函数的零点问题:
f ( ω ) = ∑ i = 1 N ( RC i − 1 N ) 2 = ∑ i = 1 N ( ω i − ω T Σ ω N ( ω T Σ ) i ) 2 = 0 f(\omega) = \sum_{i=1}^{N}(\text{RC}_i - \frac{1}{N})^2 \\ = \sum_{i = 1}^{N} ( \omega_i - \frac{\omega^T \Sigma \omega}{N(\omega^T\Sigma)_i } )^2 = 0f(ω)=i=1∑N(RCi−N1)2=i=1∑N(ωi−N(ωTΣ)iωTΣω)2=0
然而,除了风险平价的限制以外,我们还对各资产的权重有限制:我们要求各权重必须非负(不可做空),且权重的和必须为1(全部投资),而这些额外的限制条件很可能导致上述函数无实数解。一种退而求其次的方法则是将上述方程转化为下列约束优化问题:
min f ( ω ) = ∑ i = 1 N ( ω i − ω T Σ ω N ( ω T Σ ) i ) 2 \text{min} \ f(\omega) = \sum_{i = 1}^{N} ( \omega_i - \frac{\omega^T \Sigma \omega}{N(\omega^T\Sigma)_i } )^2min f(ω)=i=1∑N(ωi−N(ωTΣ)iωTΣω)2
s.t.
ω T 1 − 1 = 0 ω i ≥ 0 , i = 1 , . . . , N \omega^T\bold{1} - 1 = 0 \\ \omega_i ≥ 0,i= 1, ..., NωT1−1=0ωi≥0,i=1,...,N
也可以将上述优化问题的目标函数改写成矩阵形式:
f ( ω ) = [ ω − ω T Σ ω N v ] T [ ω − ω T Σ ω N v ] f(\omega) = [ \omega - \frac{\omega^T \Sigma \omega}{N}v ]^T [ \omega - \frac{\omega^T \Sigma \omega}{N}v ]f(ω)=[ω−NωTΣωv]T[ω−NωTΣωv]
其中,v 为 N 阶列向量:
v = ( v i ) N × 1 = [ 1 ( ω T Σ ) i ] N × 1 v = (v_i)_{N\times1}= [\frac{1}{(\omega^T\Sigma)_i }] _{N\times1}v=(vi)N×1=[(ωTΣ)i1]N×1
因此,对于风险平价策略,投资于各风险资产的权重修向量 ω 即为下列约束优化问题的解:
min f ( ω ) = [ ω − ω T Σ ω N v ] T [ ω − ω T Σ ω N v ] \text{min} f(\omega) = [ \omega - \frac{\omega^T \Sigma \omega}{N}v ]^T [ \omega - \frac{\omega^T \Sigma \omega}{N}v ]minf(ω)=[ω−NωTΣωv]T[ω−NωTΣωv]
s.t.
ω T 1 − 1 = 0 ω ≥ 0 \omega^T\bold{1} - 1 = 0 \\ \omega ≥ \bold0 \\ωT1−1=0ω≥0
2.2 模型求解
由于上述问题仍然是有线性限制条件的优化问题,我们仍可延续马科维茨模型中的求解方法,使用Python的Scipy包中的minimize函数进行上述优化问题的求解。同样地,对于R语言使用者,可以使用alabama包中的constrOptim.nl函数进行求解。
3. 风险平价策略实现伪代码
风险平价策略得的伪代码如下:
Step 0:始化参数。规定调仓频率(本文以 T = 30 即30个交易日作为调仓周期进行说明)。规定 t = 0,进入Step 1;
Step 1:根据前一周期的日收盘价格,计算该周期内除无风险资产以外的各类资产日收益率 ri,t 与日波动率 σi,t,计算各类资产的风险平价权重 wi,t;
Step 2:以当日收盘价格将各类资产比例调整至目标权重;
Step 3: 令 t = t + 1,重复 Step 1 直至回测结束。
4. 基于python的模型实现
4.1 python代码
由于风险平价策略在权重的优化求解方法上和形式上与马科维茨模型如出一辙,因此本文在马科维茨模块基础上进行了改进,只需要对模块函数内的目标函数进行修改即可。(请参考【基金量化研究系列】大类资产配置研究(四)——基于马科维茨模型的资产配置研究的4.1部分)。在运行时,可将下列代码块保存为python文件“Mean_Variance.py”。
# -*- coding: utf-8 -*-
# author: Mikey_Sun time: 5/19/2020
# all copyright belongs to the author. NO COPY ALLOWED.
import math
from scipy.optimize import minimize
import numpy as np
import pandas as pd
########################
## Module 1 MV框架模块 ##
########################
def M_V(R, rf, Sigma, output_type = 'Dict', print_out = False):
'''
:param R: list格式,输入各收益序列
:param rf: num格式或list格式,输入无风险利率
:param Sigma: two-d list格式,输入各收益序列协方差矩阵
:param output_type: Bool类型,True为打印结果,Flase为不打印结果
:param : num格式或list格式
:return: dataframe
'''
# Step 1: 初始化收入变量格式
N = len(R) # 获取资产类别数
R = np.array(R).reshape(N) # 格式化收益率序列
rf = np.array(rf) # 格式化无风险利率
Sigma = np.array(Sigma).reshape(N, N) # 格式化协方差矩阵
# Step 2: 将无风险利率嵌入到收益率序列与协方差矩阵中(增广阵)
R = np.r_[R, rf] # 增广收益率序列
Sigma = np.c_[Sigma, np.zeros(N).reshape(N, 1)] # 增广协方差矩阵列
Sigma = np.r_[Sigma, np.zeros(N+1).reshape(1, N+1)] # 增广协方差矩阵行
# Step 3: 利用minimize函数进行模型求解:
eps = 1e-10 # 找到一个非常接近0的值作为误差限
w0 = np.ones(N+1) / (N+1) # 各类别资产权重参数初始化
#fun = lambda w: 0.5 * math.log(np.dot(np.dot(w, Sigma), np.transpose(w))) - math.log(
# np.dot(w, R - rf)) # 约束函数:最大化对数sharpe-ratio
fun = lambda w: - np.dot(w, R - rf) / np.dot(np.dot(w, Sigma), np.transpose(w))**0.5 # 约束函数:最大化对数sharpe-ratio
cons = ({'type': 'eq', 'fun': lambda w: np.sum(w) - 1}, # 限制条件一:全部投资
{'type': 'ineq', 'fun': lambda w: w - eps}, # 限制条件二:不可做空
)
res = minimize(fun, w0, method='SLSQP', constraints=cons)
# Step 4: 计算各指标
w_op = res.x # 获取权重数据
r_op = np.dot(res.x, R) # 计算收益率
sigma_op = np.dot(np.dot(w_op, Sigma), np.transpose(w_op)) # 计算组合波动率
SR = (r_op - rf) / sigma_op # 计算夏普比率
# Step 5: 是否打印测试
if print_out:
print('''
最优权重配比:{0:}
最优收益率:{1:.2f}
最优风险:{2:.2f}
最优夏普比率:{3:.2f}
'''.format(w_op, r_op, sigma_op, SR))
# Step 6: 打印模块
if str(output_type).upper() == 'DATAFRAME':
return pd.DataFrame({'Weights':[w_op], 'Return':r_op, 'Sigma':sigma_op, 'SR':SR})
elif str(output_type).upper() == 'DICT':
return {'Weights':w_op, 'Return':r_op, 'Sigma':sigma_op, 'SR':SR}
elif str(output_type).upper() == 'LIST':
return [w_op, r_op, sigma_op, SR]
else:
return {'Weights':w_op, 'Return':r_op, 'Sigma':sigma_op, 'SR':SR}
########################
## Module 2 风险平价框架模块 ##
########################
def R_P(R, rf, Sigma, output_type = 'Dict', print_out = False):
'''
:param R: list格式,输入各收益序列
:param rf: num格式或list格式,输入无风险利率
:param Sigma: two-d list格式,输入各收益序列协方差矩阵
:param output_type: Bool类型,True为打印结果,Flase为不打印结果
:param : num格式或list格式
:return: dataframe
'''
# Step 1: 初始化收入变量格式
N = len(R) # 获取资产类别数
R = np.array(R).reshape(N) # 格式化收益率序列
rf = np.array(rf) # 格式化无风险利率
Sigma = np.array(Sigma).reshape(N, N) # 格式化协方差矩阵
# Step 2: 利用minimize函数进行模型求解:
eps = 1e-10 # 找到一个非常接近0的值作为误差限
w0 = np.ones(N) / N # 各类别资产权重参数初始化
fun = lambda w: np.dot(w - np.dot(np.dot(w, Sigma), np.transpose(w))/N / np.dot(w, Sigma),
np.transpose(w - np.dot(np.dot(w, Sigma), np.transpose(w))/N / np.dot(w, Sigma) ))
cons = ({'type': 'eq', 'fun': lambda w: np.sum(w) - 1}, # 限制条件一:全部投资
{'type': 'ineq', 'fun': lambda w: w - eps}, # 限制条件二:不可做空
)
res = minimize(fun, w0, method='SLSQP', constraints=cons)
# Step 4: 计算各指标
w_op = res.x # 获取权重数据
r_op = np.dot(res.x, R) # 计算收益率
sigma_op = np.dot(np.dot(w_op, Sigma), np.transpose(w_op)) # 计算组合波动率
SR = (r_op - rf) / sigma_op # 计算夏普比率
# Step 5: 是否打印测试
if print_out:
print('''
最优权重配比:{0:}
最优收益率:{1:.2f}
最优风险:{2:.2f}
最优夏普比率:{3:.2f}
'''.format(w_op, r_op, sigma_op, SR))
# Step 6: 打印模块
if str(output_type).upper() == 'DATAFRAME':
return pd.DataFrame({'Weights':[w_op], 'Return':r_op, 'Sigma':sigma_op, 'SR':SR})
elif str(output_type).upper() == 'DICT':
return {'Weights':w_op, 'Return':r_op, 'Sigma':sigma_op, 'SR':SR}
elif str(output_type).upper() == 'LIST':
return [w_op, r_op, sigma_op, SR]
else:
return {'Weights':w_op, 'Return':r_op, 'Sigma':sigma_op, 'SR':SR}
下面是主函数模块代码块。其中引用的“Risk_Factor_Calculation”模块来自于本人自己开发的量化策略回测框架中的一部分,请参考:【基于python的量化策略回测框架搭建】策略表现衡量指标模块。
# -*- coding: utf-8 -*-
# author: Mikey_Sun time: 5/26/2020
# all copyright belongs to the author. NO COPY ALLOWED.
import Risk_Factor_Calculation as RF
import Mean_Variance as MV
from EmQuantAPI import * #使用chioce数据库。详细使用说明请查阅百度
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
#################################
## Module 1: 下载数据,并存储到本地 ##
#################################
def download_data_from_choice():
loginresult = c.start()
codes = "510310.SH,511010.SH,518880.SH,131801.SZ" # 所需数据的代码。510310为沪深300ETF,511010为五年国债ETF
indicators = "open,high,low,close" # 需要下载的数据指标
startdate = "20130729" # 回测起始时间,可自行修改
enddate = "20200515" # 回测结束时间,可自行修改
data = c.csd(codes=codes, indicators=indicators, startdate=startdate, enddate=enddate,
options=("Ispandas,1")) # 导入数据,以pd.DataFrame类型格式存储
path = '你的数据储存地址' # 将数据保存到你的本地地址(需自行修改)
data.to_excel(path, index=True, header=True)
download_data_from_choice()
path = '你的数据储存地址' # 运行时请修改为你的数据保存地址
data = pd.read_excel(path) # 读取数据
data.set_index(data["DATES"], inplace=True) #设置数据索引为交易日
tickers = ["510310.SH","511010.SH","518880.SH",'131801.SZ']
HS300ETF = pd.DataFrame(data[data["CODES"] == '510310.SH']["CLOSE"].astype(float)) #沪深300ETF专属数据结构。仅保留收盘价格
GZ5yETF = pd.DataFrame(data[data["CODES"] == '511010.SH']["CLOSE"].astype(float)) #国债ETF专属数据结构。仅保留收盘价格
GoldETF = pd.DataFrame(data[data["CODES"] == '518880.SH']["CLOSE"].astype(float)) #黄金ETF专属数据结构。仅保留收盘价格
R_001 = pd.DataFrame(data[data["CODES"] == '131801.SZ']["CLOSE"].astype(float)) #黄金ETF专属数据结构。仅保留收盘价格
td_dates = HS300ETF.index #记录交易日信息,方便以后进行查找
n = len(td_dates)
#######################
## Module2: 计算收益率 ##
#######################
pd.set_option('mode.chained_assignment', None)
# 创建收益率序列
HS300ETF['RETURN'] = 0 #创建收益率序列
GZ5yETF['RETURN'] = 0 #创建收益率序列
GoldETF['RETURN'] = 0 #创建收益率序列
R_001['RETURN'] = 0
HS300ETF['RETURN'].astype(float)#设定格式为浮点
GZ5yETF['RETURN'].astype(float)#设定格式为浮点
GoldETF['RETURN'].astype(float)#设定格式为浮点
R_001['RETURN'].astype(float)#设定格式为浮点
# 计算收益率
HS300ETF["RETURN"][td_dates[1:]] = np.array(HS300ETF["CLOSE"][td_dates[1:]]) / np.array(HS300ETF["CLOSE"][td_dates[:-1]]) - 1 #计算HS300ETF日收益率
GZ5yETF["RETURN"][td_dates[1:]] = np.array(GZ5yETF["CLOSE"][td_dates[1:]]) / np.array(GZ5yETF["CLOSE"][td_dates[:-1]]) - 1 #计算GZ5yETF日收益率
GoldETF["RETURN"][td_dates[1:]] = np.array(GoldETF["CLOSE"][td_dates[1:]]) / np.array(GoldETF["CLOSE"][td_dates[:-1]]) - 1 #计算GZ5yETF日收益率
R_001["RETURN"][td_dates[1:]] = np.array(R_001["CLOSE"][td_dates[1:]])/100/365
# 将收益率序列单独存储至returns
returns = pd.DataFrame({'510310.SH':HS300ETF["RETURN"], '511010.SH':GZ5yETF["RETURN"], '518880.SH':GoldETF["RETURN"], '131801.SZ':R_001["RETURN"]},
index= td_dates)
#################################
## Module 3: 计算策略权重 ######
#################################
def weights_calculate_RP(T):
weights_strategy_RP = []
for t in range(T, n, T):
R = []
for i in range(3):
R.append(np.mean(returns[tickers[i]][td_dates[t - T:t]]) * T)
rf = np.mean(returns['131801.SZ'][td_dates[t - T:t]]) * T
Sigma = np.cov(returns[tickers[0:3]][t - T:t] * T, rowvar=False)
outcome = MV.R_P(R=R, rf=rf, Sigma=Sigma)
for i in range(T):
weights_strategy_RP.append(outcome['Weights'])
return weights_strategy_RP
def weights_calculate_MV(T):
weights_strategy_MV = []
for t in range(T, n, T):
R = []
for i in range(3):
R.append(np.mean(returns[tickers[i]][td_dates[t - T:t]]) * T)
rf = np.mean(returns['131801.SZ'][td_dates[t - T:t]]) * T
Sigma = np.cov(returns[tickers[0:3]][t - T:t] * T, rowvar=False)
outcome = MV.M_V(R=R, rf=rf, Sigma=Sigma)
for i in range(T):
weights_strategy_MV.append(outcome['Weights'])
return weights_strategy_MV
#################################
## Module 4: 各策略净值与收益率计算 ##
#################################
nv_HS300ETF = [1] #记录全仓股票策略净值
nv_GZ5yETF = [1] #记录全仓债券策略净值
nv_GoldETF = [1]
nv_Cash = [1]
r_HS300ETF = []
r_GZ5yETF = []
r_GoldETF = []
r_Cash = []
nv_strategy_MV = [1]
r_strategy_MV = []
nv_strategy_RP = [1]
r_strategy_RP = []
T = 30 # 设置调参周期长度
weights_strategy_RP = weights_calculate_RP(T) # 计算风险平价策略权重序列
weights_strategy_MV = weights_calculate_MV(T) # 计算马科维茨模型策略权重序列
for i in range(T, n):
nv_HS300ETF.append( nv_HS300ETF[i-T] *(1 + returns['510310.SH'][td_dates[i]]))
nv_GZ5yETF.append( nv_GZ5yETF[i-T] *(1 + returns['511010.SH'][td_dates[i]]))
nv_GoldETF.append( nv_GoldETF[i-T] *(1 + returns['518880.SH'][td_dates[i]]))
nv_Cash.append( nv_Cash[i-T] *(1 + returns['131801.SZ'][td_dates[i]]))
r_HS300ETF.append(returns['510310.SH'][td_dates[i]])
r_GZ5yETF.append(returns['511010.SH'][td_dates[i]])
r_GoldETF.append(returns['518880.SH'][td_dates[i]])
r_Cash.append(returns['131801.SZ'][td_dates[i]])
r_strategy_MV.append(weights_strategy_MV[i - T][0] * returns[tickers[0]][td_dates[i]] +
weights_strategy_MV[i - T][1] * returns[tickers[1]][td_dates[i]] +
weights_strategy_MV[i - T][2] * returns[tickers[2]][td_dates[i]] +
weights_strategy_MV[i - T][3] * returns[tickers[3]][td_dates[i]])
r_strategy_RP.append(weights_strategy_RP[i - T][0] * returns[tickers[0]][td_dates[i]] +
weights_strategy_RP[i - T][1] * returns[tickers[1]][td_dates[i]] +
weights_strategy_RP[i - T][2] * returns[tickers[2]][td_dates[i]])
nv_strategy_MV.append(nv_strategy_MV[i-T] * (1 + r_strategy_MV[i-T]))
nv_strategy_RP.append(nv_strategy_RP[i-T] * (1 + r_strategy_RP[i-T]))
#################################################
## Module 5: 汇总各策略净值信息,并计算其风险及收益情况 ##
#################################################
r_series = [r_HS300ETF, r_GZ5yETF, r_GoldETF, r_Cash, r_strategy_MV , r_strategy_RP]
nv_series = [nv_HS300ETF, nv_GZ5yETF, nv_GoldETF, nv_Cash, nv_strategy_MV ,nv_strategy_RP]
Period = 'D'
Indicators = 'ALL'
alpha = 0.05
output_type = 'pd.DataFrame'
strategy_names = ['沪深300', '债券ETF', '黄金ETF', '国债逆回购', '均值方差策略', '风险平价策略']
# 进入风险值计算回测系统框架(该框架为自主创建,需要读取该文件)
Risks = RF.Risk_Indicators(r_series, nv_series, Period = Period, Indicators= Indicators, alpha=alpha, output_type=output_type)
Risks = pd.DataFrame(Risks.values.tolist(), index = strategy_names, columns = Risks.columns)
#打印风险值情况
print(Risks[Risks.columns[:4]])
print(Risks[Risks.columns[4:]])
################################
## Module 6: 绘制各策略净值走势图 ##
################################
plt.plot(nv_HS300ETF, label = "nv_HS300ETF", color = "black")
plt.plot(nv_GZ5yETF, label = "nv_GZ5yETF", color = "red")
plt.plot(nv_GoldETF, label = "nv_GoldETF", color = "blue")
plt.plot(nv_Cash, label="nv_Cash", color="purple")
plt.plot(nv_strategy_MV, label="nv_strategy_MV", color="green")
plt.plot(nv_strategy_RP, label="nv_strategy_RP", color="grey")
plt.show()
注:为节约时间,本文代码直接在马科维茨代码的基础上打了补丁,代码内容冗长,有待进一步优化,在此仅做实例展示。
4.2 实证结果
当调仓周期为30个交易日时(设置 T = 30),策略的运行结果如下:
Return Sigma R/S MDD
沪深300 0.121751 0.245480 0.495972 1.046858
债券ETF 0.037404 0.028362 1.318794 0.056963
黄金ETF 0.058866 0.126968 0.463625 0.216147
国债逆回购 0.018584 0.000557 33.345747 0.000000
均值方差策略 0.061792 0.106153 0.582107 0.338244
风险平价策略 0.066728 0.060375 1.105237 0.102871
MDD_R MDD_P VaR CVaR
沪深300 0.456784 155.0 0.099732 0.099732
债券ETF 0.049023 39.0 0.016768 0.016768
黄金ETF 0.213716 461.0 0.046573 0.046573
国债逆回购 0.000000 0.0 0.000000 0.000000
均值方差策略 0.258140 17.0 0.065919 0.065919
风险平价策略 0.084008 47.0 0.051322 0.051322
Process finished with exit code 0

*数据来源:Choice数据库
图1 2013/09/09 - 2020/05/15 期间各策略净值走势图(以2013年9月8日为基日,定义净值为1。黑线为全股策略,红线为全债策略,蓝线为纯黄金策略,紫线为纯现金策略,绿线为马科维茨模型策略,灰线为风险平价策略)
当调仓周期调整为60个交易日时(设置 T = 60),策略的运行结果如下:
Return Sigma R/S MDD
沪深300 0.122891 0.246382 0.498783 1.041314
债券ETF 0.038749 0.028488 1.360169 0.057206
黄金ETF 0.064613 0.126399 0.511182 0.210307
国债逆回购 0.018449 0.000546 33.792535 0.000000
均值方差策略 0.051419 0.064649 0.795346 0.111822
风险平价策略 0.107824 0.064947 1.660180 0.090008
MDD_R MDD_P VaR CVaR
沪深300 0.456784 155.0 0.099732 0.099732
债券ETF 0.049023 39.0 0.016768 0.016768
黄金ETF 0.203894 339.0 0.046573 0.046573
国债逆回购 0.000000 0.0 0.000000 0.000000
均值方差策略 0.088425 12.0 0.034050 0.034050
风险平价策略 0.068778 53.0 0.023362 0.023362
Process finished with exit code 0

*数据来源:Choice数据库
图2 2013/10/30 - 2020/5/15 期间各策略净值走势图(以2013年10月29日为基日,定义净值为1。黑线为全股策略,红线为全债策略,蓝线为纯黄金策略,紫线为纯现金策略,绿线为马科维茨模型策略,灰线为风险平价策略)
5. 结论
5.1 策略表现
从策略表现上来看,调仓频率不论是每30个交易日一次还是每60个交易日一次,风险平价策略都相比于马科维茨策略拥有较低的风险水平和较高的夏普比率。从尾部风险角度来看,风险平价策略 在相同置信度下的在险价值较低,而最大回撤比率也显著低于马科维茨策略。不过值得注意的是,风险平价策略的最大回撤周期远大于马科维茨策略,这意味着该策略将空间上的部分损失转化为时间上的损失,这可能要求组合管理人对连续的小阴跌要有较高的承受度。
还有一点值得注意的是,当调仓周期从30个交易日变更为60个交易日的时候,后者的收益率和夏普比率均明显高于前者,这可能意味着策略的表现对调仓周期较为敏感。在本文进一步的实证过程中,发现策略的表现随调仓频率的增加而呈现出明显的倒U型,这说明选择合适的调仓周期也是影响风险平价策略表现的重要因素。
5.2 可能存在的问题及潜在处理方法
同时,我们也应该看到,调仓周期也是影响策略表现的一个重要因素。根据实证结果,调仓周期过短或过长,策略的表现均不理想。出现这一问题的原因可能有:
(1)估计量的有偏性。模型需要对各收益序列协方差矩阵进行估计,而样本长度会影响估计量的准确性。
(2)调仓信号的滞后性。当波动率在短期内发生重大变化时,调仓频率的滞后性会导致在短时间内仓位发生重大偏离。
对于第一种可能存在的问题,我们已经在马科维茨模型中给出了几种可能的解决方法,在此不做赘述。对于第二个问题,一种处理方法就是对调仓方式从周期性静态调仓变更为动态再平衡(Dynamic Rebalance)。例如,每日或每周监控各风险资产的风险贡献率,若某资产风险贡献率超越某一阈值(例如超越基准比率的10%)后则立即进行再平衡调仓。当然,也可以借鉴机器学习的办法,将调仓周期 T 作为策略的 调整参数(Tuning Parameter),通过设置目标函数(如夏普比率)以寻找最优的调仓周期。
此外,本文规定调仓周期与模型参数估计的样本窗口期长度相等(即以过去 T 个交易日的历史数据来计算未来 T 个交易日各资产所占的比例)。这一规定是经验性的,有兴趣的读者也可以在这一点上做进一步的研究。
免责声明
免责声明:本文内容及观点仅供参考,不构成任何投资建议,投资者据此操作,风险自担。一切有关本文涉及的数据均来自于第三方数据机构,使用时请查阅第三方数据平台的使用说明,本文不负有数据准确性的直接负责。本文所述策略表现均基于历史数据,不代表对未来的预期。投资有风险,入市需谨慎。
写在最后
若想查阅本系列全部文章,请参见目录页:系列文章目录索引。
欢迎感兴趣的小伙伴来跟作者一起挑刺儿~ 包括但不限于语言上的、排版上的和内容上的不足和疏漏~ 一起进步呀!
有任何问题,欢迎在本文下方留言,或者将问题发送至勘误邮箱: mikeysun_bugfix@163.com
谢谢大家!