用python绘制有效前沿

投资中最关心的两个问题是预期收益与风险,当对多个资产进行投资时,如何测定组合的风险与收益,如何根据这两项指标进行资产权重配置? 马科维茨理论给出了解决这一问题的框架,被认为是现代金融学的开端。本文首先给出马科维茨均值方差模型的理论说明,随后用股票指数数据绘制组合的有效前沿,最后给出一种应用方法,获取代码和数据请在后台回复“代码”。

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

结果如下

可以看出,最小方差组合的效果略优于等权重组合。

原文发布于微信公众号 - 量化小白上分记(quanthzp)

原文发表时间:2019-02-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券