资产瞎配模型(一)

2019-01-22 15:43:32 浏览数 (1)

大类资产配置是量化中一个重要的领域,本文尝试实现若干资产配置模型。全文纯属瞎配,欢迎指正!

01

理论模型

资产配置是根据投资者的收益风险偏好及不同资产特性,将资金配置于多种资产类别的一种投资策略,目的在于分散风险,是对组合收益和组合风险的权衡。

首先给出一些符号定义

接下来说明所用到的配置模型,资产配置从技术角度来说,只需要考虑三个问题:选择资产、横截面分配、时间序列分配

选择资产相对主观,最重要的原则是风险不同源,从数据角度来说,相关性不能太高,相关性过高的资产,难以通过调整权重来规避风险。横截面分配与时间序列分配实质上就是确定各种资产的权重,各种模型也都是在选定资产后,在不同的假设下给出不同的权重表达式。接下来列出文中用到的所有资产配置模型。(公式较多,可能引起不适,可以直接看下一部分,不影响悦读

几种简单粗暴的配置方式:

除了这些相对简单的配置方式外,还有很多理论上很完美的模型,基本都能看见马科维茨的影子。

风险平价跟等波动率相对比,出发点都是使每类资产的面临的风险相同,但不同之处在于,等波动率考虑的是让各个资产对应的风险值相同,风险平价考虑的是让权重变化引起风险的的比例相同

当然除了这些,还有美林时钟、Black-Litterman等模型,也应用很广,美林时钟比较定性,BL模型是MVO的基础上引入了预期收益,客观 主观,但目前没搞懂实际应用时候观点矩阵该怎么定义,等搞懂了再尝试吧。

02

回测:资产选择

资产应选择相关性较低的资产,一般都是权益、债券、商品、黄金等资产中选择。本文选择资产类型如下

回测区间:2006年1月-2018年12月

数据来源:wind

将各资产起点标准化为1000点,各资产走势如下

不得不感叹,A股真的是十年一梦啊,各资产相关性如图,没有相关性很高的资产,所以做配置模型数据上没有什么问题。

03

回测:等权重

等权方式配置,起点时刻各资产买同样权重,一直持有。

代码语言:javascript复制
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['nav'] = ((ret*weights).sum(axis = 1)   1).cumprod()*1000
    return weights,result

净值曲线

权重变化

04

等资金

除了等权方式外,其他的方式配置的资产,权重都会随着时间发生变化,偏离初始的目标,需要进行“再平衡”,使资产配置重回平衡,这里分别尝试进行月度半年度年度的再平衡。

代码语言:javascript复制
def EqualCapitalWeight(datas,period ='month'):
    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)
    if period == 'month':
        for i in range(result.shape[0]):
            if i == 0:
                weights.iloc[i,:] = (1/datas.iloc[i,:])/((1/datas.iloc[i,:]).sum())          
            elif result.m[i] != result.m[i - 1]:
                weights.iloc[i,:] = (1/datas.iloc[i,:])/((1/datas.iloc[i,:]).sum())
            else:
                weights.iloc[i,:] = weights.iloc[i-1,:]
    elif period == '6month':
        for i in range(result.shape[0]):
            if i == 0:
                weights.iloc[i,:] = (1/datas.iloc[i,:])/((1/datas.iloc[i,:]).sum())              
            elif (result.m[i] != result.m[i - 1] and  result.m[i]%6==0) :
                weights.iloc[i,:] = (1/datas.iloc[i,:])/((1/datas.iloc[i,:]).sum())
            else:
                weights.iloc[i,:] = weights.iloc[i-1,:]

    elif period == 'year':
        for i in range(result.shape[0]):
            if i == 0 :
                weights.iloc[i,:] = (1/datas.iloc[i,:])/((1/datas.iloc[i,:]).sum())             
            elif (result.m[i] != result.m[i - 1] and  result.m[i]==0) :
                weights.iloc[i,:] = (1/datas.iloc[i,:])/((1/datas.iloc[i,:]).sum())
            else:
                weights.iloc[i,:] = weights.iloc[i-1,:]
    else: 
        return '请输入调仓周期'
    
    result['nav'] = ((ret*weights).sum(axis = 1)   1).cumprod()*1000
    return weights,result

只给出月度再平衡对应的权重变化,因为三个都差不多。

不过这个有问题,因为我完全没考虑到汇率

,最开始只用了国内的几个资产,后来加上了全球指数之后,忘改了,所以看看就好吧。

05

等波动率

等波动率以及后面需要用到协方差的模型都需要考虑一个问题,如何估计波动率/协方差?这里图方便我们都使用历史波动率估计量,不考虑高端方法。有两种方式计算,一种是滚动计算,每次只用过去一段时间的数据计算,另一种研报里称为递归计算,用过去所有数据计算,这里两种方法都进行尝试,对结果进行对比。

滚动计算的代码如下,递归计算代码类似。

代码语言:javascript复制
def EqualVolWeight(datas,period ='month'):
    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)
    position = 0
    if period == 'month':
        for i in range(result.shape[0]):
            if i == 0:
                pass
            elif result.m[i] != result.m[i - 1]:
                vol = ret.iloc[position:i].std()
                position = i
                weights.iloc[i,:] = (1/vol)/((1/vol).sum()) 

            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) :
                vol = ret.iloc[position:i].std()
                position = i
                weights.iloc[i,:] = (1/vol)/((1/vol).sum())    
            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]==0) :
                vol = ret.iloc[position:i].std()
                position = i
                weights.iloc[i,:] = (1/vol)/((1/vol).sum())    
            else:
                weights.iloc[i,:] = weights.iloc[i-1,:]

    else: 
        return '请输入调仓周期'
    
    result['nav'] = ((ret*weights).sum(axis = 1)   1).cumprod()*1000
    return weights,result

滚动结果

递归结果

从结果可以明显看出,滚动窗口敏感性更高,一方面能够更贴近最新的趋势,但另一方面也可能对于噪声过度反应。 相比之下, 递归窗口稳定性好得多,但不够灵敏。两种方法各有优劣。但整体来看,两种方法对应的权重是类似的,说明还是比较稳健的。

等波动率的情况下,货币的波动率太小了,导致高配货币,零配A股。很稳健,但这种结果跟直接买货币也差不多了,没什么意义。

06

GMV

首先尝试直接套用模型解析解的表达式计算权重。只给出权重计算公式,其他部分都和前面是一样的。

代码语言:javascript复制
sigma = ret.iloc[:i].cov()
weight = np.dot(np.mat(sigma).I,np.ones([sigma.shape[1],1])) 
weights.iloc[i,:] =  np.array(weight/(weight.sum())).T[0]

递归结果

权重有负值

滚动

滚动的结果明显可以看出权重有负值,A股、债券都不能做空,所以不符合常理,因此加上卖空限制后,重新求解。

07

GMO 卖空限制

有卖空限制后,模型没有解析解,只能通过最优化方法求数值解,我们使用python的scipy库中的minmum函数进行优化求解,funs为优化目标。

代码语言:javascript复制
def funs(weight,sigma):
    weight = np.array([weight]).T
    result = np.dot(np.dot(weight.T,np.mat(sigma)),weight)[0,0]
    return(result)

res =  minimize(funs,weight, method='SLSQP',args = (sigma,),
                bounds=bnds,constraints=cons,tol=1e-8)
weights.iloc[i,:] =  res.x

滚动结果

递归结果

两种方法结果基本是差不多的,债券和货币依然占绝大比例。

将单个资产占比限定在50%以内,重新优化:

滚动结果

基本上没有什么变化。

08

Risk Parity

优化函数funsRP

代码语言:javascript复制
def funsRP(weight,sigma):
    weight = np.array([weight]).T
    X = np.multiply(weight,np.dot(sigma.values,weight))
    result = np.square(np.dot(X,np.ones([1,X.shape[0]])) - X.T).sum()
    return(result)

滚动

递归

A股大概是怎么都不愿意配一点了,为了避免单个资产权重过高或者过低的问题,对资产权重加以限制

09

Risk Parity w<=40%

滚动

递归

10

Risk Parity w>=10%

滚动

递归

11

所有结果净值对比

递归-月度

递归-半年度

递归-年度

滚动-月度

滚动-半年度

滚动-年度

12

结果评价

从净值上来看,等权重是最优的,我们计算不同组合下的年化收益,波动率,夏普比,对结果进行评价。

代码语言:javascript复制
def performance(datas):
    nav = (datas.nav[datas.shape[0]-1]/1000)**(1/12) - 1
    vol = (datas.nav.pct_change(1)).std()*np.sqrt(250)
    Sharp = nav/vol
    return nav,vol,Sharp

剔除出现做空情况的GMO和有问题的等资金,所有方法结果对比如下

滚动

递归

两种参数估计方式下,结果基本是一致的:

  • 等波动率优于RP优于GMO优于等权重。如果看收益和波动率,等波动率下的年化收益是所有方法里最低的,但波动率也是最小的,小一个数量级。因为资产中有货币这一基本没有波动的资产,导致等波动率情况下货币占了80%以上的比例。除过等波动率的情况看,风险平价要更优一些。
  • 对于半年度和年度再平衡的策略,刚开始不满六个月/十二个月的时候,我所有的权重都设置的0,导致这段时间这些策略收益一直是0,其实至少可以全配货币或者按无风险利率累积。

总之,是一篇练手的作品,做的很粗糙,后面会尝试将这些模型用于行业配置和风格配置,欢迎指正!

13

参考文献

  1. Bodnar T, Schmid W. A test for the weights of the global minimum variance portfolio in an elliptical model[J]. Metrika, 2008, 67(2):127.
  2. 20160725-华泰证券-风险平价模型实证研究:风险平价模型在大类资产配置及行业配置中的应用
  3. 20180309-华宝证券-华宝证券金融工程专题报告:资产配置的流程、框架与运用
  4. 20180928-东北证券-东北证券大类资产配臵“全解析”专题研究之一::风险平价性质探究
  5. 20181114-爱建证券-爱建证券量化资产配置系列报告:从不同维度和角度探索风险平价资产配置方法的稳定性

0 人点赞