投资中最关心的两个问题是预期收益与风险,当对多个资产进行投资时,如何测定组合的风险与收益,如何根据这两项指标进行资产权重配置? 马科维茨理论给出了解决这一问题的框架,被认为是现代金融学的开端。本文首先给出马科维茨均值方差模型的理论说明,随后用股票指数数据绘制组合的有效前沿,最后给出一种应用方法,获取代码和数据请在后台回复“代码”。
01
均值方差模型
马科维茨理论的几个假设:
1、投资者在考虑每一次投资选择时,其依据是某一持仓时间内的证券收益的概率分布。
2、投资者是根据证券的期望收益率的方差或标准差估测组合的风险。
3、投资者的决定仅仅是依据证券的风险和收益。
4、在一定的风险水平上,投资者期望收益最大;在一定的收益水平上,投资者希望风险最小。
根据以上假设,马科维茨均值方差模型可以表述为:
最优组合为上述模型的解。在这一框架下,不同的期望收益对应不同的最优组合,从而得到一条最优组合曲线,对于给定的收益率,这条曲线上的投资组合有最小的风险,这条曲线称为有效前沿。
一种极端的情况是,投资者完全不考虑期望收益,只希望最小化风险,这样就可以忽略预期收益约束,得到最小化方差组合。
接下来通过python绘制资产组合的有效前沿。
02
两资产
首先考虑最简单的两资产情形
假设资产A,B收益率分别为5%,10%,波动率分别为15%,20%,对相关系数取值从-1到1,资产A权重从0到1,看不同投资组合的收益与风险情况。
图中每一条曲线的上半部分都是一个有效前沿(同样风险水平下,上半部分的收益显然高于下半部分),可以看到,当两资产完全正相关/负相关时,有效前沿退化成直线/双直线。并且随着两资产相关性的提高,同样的期望收益下,最优组合的风险越来越高,对应的,同样的风险水平下,期望收益越来越小。
代码
def portfolio1(ra,rb,sa,sb,rho,wa):
wb = 1 - wa
rp = wa*ra + wb*rb
sp = ((wa**2)*(sa**2) + (wb**2)*(sb**2) + 2*wa*wb*sa*sb*rho)**0.5
return rp,sp
ra = 0.05;rb = 0.1;sa = 0.15;sb = 0.2
rho = np.arange(-10,11,2)/10
wa = np.arange(101)/100
result = pd.DataFrame(columns = ['rho','wa','rp','sp'])
for rhos in rho:
for was in wa:
rp,sp = portfolio1(ra,rb,sa,sb,rhos,was)
x = pd.DataFrame([rhos,was,rp,sp]).T
x.columns = ['rho','wa','rp','sp']
result = result.append(x)
result = result.reset_index(drop = True)
plt.figure(figsize=(20,5))
for i in rho:
datas = result.loc[result.rho ==i]
plt.scatter(datas.sp,datas.rp,marker = 'o',label = 'rho = '+ str(i))
plt.legend()
plt.xlabel('sigma',fontsize = 20)
plt.ylabel('r',fontsize = 20)
plt.show()
03
多资产
多个资产情形下,由于股票会出现停牌等问题导致数据不连续。我们选用申万一级行业指数中的食品饮料(801120.SI)、银行(801780.SI)、通信(801770.SI)、国防军工(801740.SI)、医药生物(801150.SI)作为基础资产。
以2010年1月4日为起点将指数归一化到1000点后,各指数走势如下
用组合2017年的数据计算收益和方差,年化
我们首先用蒙特卡洛方法模拟出各种组合权重,计算各组合的收益风险水平并进行作图,根据组合的夏普比率进行着色,共模拟20000次。
代码
def weights(n):
w = np.random.random(n)
return w/w.sum()
def performance(price,w):
rf = 0.04
ret = price.pct_change(1).dropna()
r_mean = ret.mean()*252
p_mean = np.sum(r_mean*w)
r_cov = ret.cov()*252
p_var = np.dot(w.T,np.dot(r_cov,w))
p_std = np.sqrt(p_var)
p_sharpe = (p_mean-rf)/p_std
return p_mean,p_std,p_sharpe
np.random.seed(1)
p_mean,p_std,p_sharpe = np.column_stack([performance(datax,weights(5)) for i in range(20000)]) #产生随机组合
plt.figure(figsize = (10,5))# YlGnBu
plt.scatter(p_std, p_mean, c=p_sharpe, marker = 'o',cmap = 'YlGnBu',s = 15)
plt.xlabel('std')
plt.ylabel('mean')
plt.colorbar(label = 'Sharpe')
plt.title('Mean and Std of Returns')
plt.show()
接下来绘制组合的有效前沿,首先考虑之前提到最极端的情况:最小方差组合,使用scipy.minimize进行优化。
以最小方差下的收益为区间起点,收益的6倍为区间终点,期望收益等步长遍历区间,计算各收益下的最优组合,得到有效前沿。
代码
r_min = result[0]
r_mean_list = np.linspace(r_min,r_min*6,51)
v_list = []
for r in r_mean_list:
cons1 = ({'type':'eq','fun':lambda w:performance(datax,w)[0]-r},{'type':'eq','fun':lambda w:np.sum(w)-1})
opt_var_1 = minimize(min_variance,x0 = weights(5),args = (datax,),bounds=((0,1),(0,1),(0,1),(0,1),(0,1)),constraints = cons1)
#最小方差下的收益率
v_min = performance(datax,opt_var_1.x)[1]
v_list.append(v_min)
plt.figure(figsize = (10,5))
plt.scatter(p_std, p_mean, c=p_sharpe, marker = 'o',cmap = 'YlGnBu',s = 10)
plt.xlabel('std')
plt.ylabel('mean')
plt.colorbar(label = 'Sharpe')
plt.scatter(v_list,r_mean_list,marker = '*',color = 'orange',label = '有效前沿')
plt.scatter(result[1],result[0],marker = '*',color = 'red',s = 100,label = '最小方差组合')
plt.legend(prop=font)
plt.title('Mean and Std of Returns')
plt.show()
03
应用
以上文5种资产为例,2010年1月-2018年12月的数据,对比等权重配置和最小方差权重下的组合表现,对比从2011年开始,2010年数据用于优化,半年度调仓。
等权重配置
def EqualWeight(datas):
ret = datas.pct_change(1).fillna(0)
data_norm = datas/datas.iloc[0,]*1000
result = data_norm.copy()
result['ym'] = result.index
result['ym'] = result.ym.apply(lambda x:x.year*100 + x.month)
weights = pd.DataFrame(columns = datas.columns,index = datas.index).fillna(1/datas.shape[1])
result = result.loc[datetime.date(2011,1,4):]
result['nav'] = ((ret*weights).sum(axis = 1) + 1).cumprod()*1000
return weights,result
最小方差配置
def funs(weight,sigma):
weight = np.array([weight]).T
result = np.dot(np.dot(weight.T,np.mat(sigma)),weight)[0,0]
return(result)
def ConstraintGMOWeight(datas,period ='month',alpha = 1):
ret = datas.pct_change(1).fillna(0)
data_norm = datas/datas.iloc[0,]*1000
result = data_norm.copy()
result['m'] = result.index
result['m'] = result.m.apply(lambda x:x.month)
weights = pd.DataFrame(columns = datas.columns,index = datas.index).fillna(0)
# 约束
cons = ({'type': 'eq', 'fun': lambda x: 1 - sum(x)})
# 边界
bnds = ((0, alpha),(0, alpha),(0, alpha),(0, alpha),(0, alpha))
if period == 'month':
for i in range(result.shape[0]):
if i == 0:
pass
elif result.m[i] != result.m[i - 1]:
sigma = ret.iloc[:i].cov()
weight = [0 for i in range(datas.shape[1])]
res = minimize(funs,weight, method='SLSQP',args = (sigma,),
bounds=bnds,constraints=cons,tol=1e-8)
weights.iloc[i,:] = res.x
else:
weights.iloc[i,:] = weights.iloc[i-1,:]
elif period == '6month':
for i in range(result.shape[0]):
if i == 0:
pass
elif (result.m[i] != result.m[i - 1] and result.m[i]%6==0) :
sigma = ret.iloc[:i].cov()
weight = [0 for i in range(datas.shape[1])]
res = minimize(funs,weight, method='SLSQP',args = (sigma,),
bounds=bnds,constraints=cons,tol=1e-8)
weights.iloc[i,:] = res.x
else:
weights.iloc[i,:] = weights.iloc[i-1,:]
elif period == 'year':
for i in range(result.shape[0]):
if i == 0:
pass
elif (result.m[i] != result.m[i - 1] and result.m[i]%12==0) :
sigma = ret.iloc[:i].cov()
weight = [0 for i in range(datas.shape[1])]
res = minimize(funs,weight, method='SLSQP',args = (sigma,),
bounds=bnds,constraints=cons,tol=1e-8)
weights.iloc[i,:] = res.x
else:
weights.iloc[i,:] = weights.iloc[i-1,:]
else:
return '请输入调仓周期'
result = result.loc[datetime.date(2011,1,4):]
result['nav'] = ((ret*weights).sum(axis = 1) + 1).cumprod()*1000
return weights,result
结果如下
可以看出,最小方差组合的效果略优于等权重组合。