6

我正在尝试执行投资组合优化,以返回最大化我的效用函数的权重。我可以很好地完成这部分,包括权重总和为 1 并且权重也给我一个目标风险的约束。我还包括了 [0 <= weights <= 1] 的界限。此代码如下所示:

def rebalance(PortValue, port_rets, risk_tgt):
    #convert continuously compounded returns to simple returns
    Rt = np.exp(port_rets) - 1 
    covar = Rt.cov()

    def fitness(W):
        port_Rt = np.dot(Rt, W)
        port_rt = np.log(1 + port_Rt)
        q95 = Series(port_rt).quantile(.05)
        cVaR = (port_rt[port_rt < q95] * sqrt(20)).mean() * PortValue
        mean_cVaR = (PortValue * (port_rt.mean() * 20)) / cVaR
        return -1 * mean_cVaR

    def solve_weights(W):
        import scipy.optimize as opt
        b_ = [(0.0, 1.0) for i in Rt.columns]
        c_ = ({'type':'eq', 'fun': lambda W: sum(W) - 1},
              {'type':'eq', 'fun': lambda W: sqrt(np.dot(W, np.dot(covar, W))\
                                                          * 252) - risk_tgt})
        optimized = opt.minimize(fitness, W, method='SLSQP', constraints=c_, bounds=b_)  

        if not optimized.success: 
           raise BaseException(optimized.message)
        return optimized.x  # Return optimized weights


    init_weights = Rt.ix[1].copy()
    init_weights.ix[:] = np.ones(len(Rt.columns)) / len(Rt.columns)

    return solve_weights(init_weights)

现在我可以深入研究这个问题,我将权重存储在 MultIndex 熊猫系列中,这样每个资产都是对应于资产类别的 ETF。当打印出相同权重的投资组合时,如下所示:

输出[263]:
股权 CZA 0.045455
             IWM 0.045455
             间谍 0.045455
intl_equity EWA 0.045455
             EWO 0.045455
             IEV 0.045455
债券 IEF 0.045455
             害羞 0.045455
             TLT 0.045455
intl_bond BWX 0.045455
             BWZ 0.045455
             IGOV 0.045455
商品 DBA 0.045455
             DBB 0.045455
             DBE 0.045455
pe ARCC 0.045455
             BX 0.045455
             PSP 0.045455
高频 DXJ 0.045455
             SRV 0.045455
现金 BIL 0.045455
             GSY 0.045455
名称:2009-05-15 00:00:00,数据类型:float64

如何包含额外的界限要求,以便当我将这些数据组合在一起时,权重总和落在我为该资产类别预先确定的分配范围之间?

所以具体来说,我想包括一个额外的边界,这样

init_weights.groupby(level=0, axis=0).sum()
输出[264]:
权益 0.136364
国际股权 0.136364
债券 0.136364
国际债券 0.136364
商品 0.136364
体育 0.136364
高频 0.090909
现金 0.090909
数据类型:float64

在这些范围内

[(.08,.51), (.05,.21), (.05,.41), (.05,.41), (.2,.66), (0,.16), (0,.76), (0,.11)]

[更新] 我想我会用一个我不太满意的笨拙的伪解决方案来展示我的进步。也就是说,因为它不是使用整个数据集来解决权重,而是使用资产类别来解决资产类别。另一个问题是它返回的是序列而不是权重,但我相信比我自己更合适的人可以提供一些关于 groupby 函数的见解。

因此,通过对我的初始代码进行轻微调整,我有:

PortValue = 100000
model = DataFrame(np.array([.08,.12,.05,.05,.65,0,0,.05]), index= port_idx, columns = ['strategic'])
model['tactical'] = [(.08,.51), (.05,.21),(.05,.41),(.05,.41), (.2,.66), (0,.16), (0,.76), (0,.11)]


def fitness(W, Rt):
    port_Rt = np.dot(Rt, W)
    port_rt = np.log(1 + port_Rt)
    q95 = Series(port_rt).quantile(.05)
    cVaR = (port_rt[port_rt < q95] * sqrt(20)).mean() * PortValue
    mean_cVaR = (PortValue * (port_rt.mean() * 20)) / cVaR
    return -1 * mean_cVaR  

def solve_weights(Rt, b_= None):
    import scipy.optimize as opt
    if b_ is None:
       b_ = [(0.0, 1.0) for i in Rt.columns]
    W = np.ones(len(Rt.columns))/len(Rt.columns)
    c_ = ({'type':'eq', 'fun': lambda W: sum(W) - 1})
    optimized = opt.minimize(fitness, W, args=[Rt], method='SLSQP', constraints=c_, bounds=b_)

    if not optimized.success: 
        raise ValueError(optimized.message)
    return optimized.x  # Return optimized weights

下面的单行将返回稍微优化的系列

port = np.dot(port_rets.groupby(level=0, axis=1).agg(lambda x: np.dot(x,solve_weights(x))),\ 
solve_weights(port_rets.groupby(level=0, axis=1).agg(lambda x: np.dot(x,solve_weights(x))), \
list(model['tactical'].values)))

Series(port, name='portfolio').cumsum().plot()

在此处输入图像描述

[更新 2]

以下更改将返回受约束的权重,尽管仍然不是最优的,因为它在组成资产类别上进行了分解和优化,因此当考虑目标风险的约束时,只有初始协方差矩阵的折叠版本可用

def solve_weights(Rt, b_ = None):

    W = np.ones(len(Rt.columns)) / len(Rt.columns)
    if b_ is None:
        b_ = [(0.01, 1.0) for i in Rt.columns]
        c_ = ({'type':'eq', 'fun': lambda W: sum(W) - 1})
    else:
        covar = Rt.cov()
        c_ = ({'type':'eq', 'fun': lambda W: sum(W) - 1},
              {'type':'eq', 'fun': lambda W: sqrt(np.dot(W, np.dot(covar, W)) * 252) - risk_tgt})

    optimized = opt.minimize(fitness, W, args = [Rt], method='SLSQP', constraints=c_, bounds=b_)  

    if not optimized.success: 
        raise ValueError(optimized.message)

    return optimized.x  # Return optimized weights

class_cont = Rt.ix[0].copy()
class_cont.ix[:] = np.around(np.hstack(Rt.groupby(axis=1, level=0).apply(solve_weights).values),3)
scalars = class_cont.groupby(level=0).sum()
scalars.ix[:] = np.around(solve_weights((class_cont * port_rets).groupby(level=0, axis=1).sum(), list(model['tactical'].values)),3)

return class_cont.groupby(level=0).transform(lambda x: x * scalars[x.name])
4

2 回答 2

3

不完全确定我理解,但我认为您可以添加以下内容作为另一个约束:

def w_opt(W):
    def filterer(x):
        v = x.range.values
        tp = v[0]
        lower, upper = tp
        return lower <= x[column_name].sum() <= upper
    return not W.groupby(level=0, axis=0).filter(filterer).empty

c_ = {'type': 'eq', 'fun': w_opt}  # add this to your other constraints

其中x.range是间隔 ( tuple) 重复K[i]次数 其中K是特定级别出现的次数,i是第ith 级别。column_name在你的情况下恰好是一个日期。

这表示约束权重,使i第 th 组中的权重总和在相关tuple区间之间。

要将每个级别名称映射到一个区间,请执行以下操作:

intervals = [(.08,.51), (.05,.21), (.05,.41), (.05,.41), (.2,.66), (0,.16), (0,.76), (0,.11)]
names = ['equity', 'intl_equity', 'bond', 'intl_bond', 'commodity', 'pe', 'hf', 'cash']

mapper = Series(zip(names, intervals))
fully_mapped = mapper[init_weights.get_level_values(0)]
original_dataset['range'] = fully_mapped.values
于 2013-08-13T21:22:32.703 回答
2

经过很长时间,这似乎是唯一适合的解决方案......

def solve_weights(Rt, b_ = None):

    W = np.ones(len(Rt.columns)) / len(Rt.columns)
    if  b_ is None:
        b_ = [(0.01, 1.0) for i in Rt.columns]
        c_ = ({'type':'eq', 'fun': lambda W: sum(W) - 1})
    else:
        covar = Rt.cov()
        c_ = ({'type':'eq', 'fun': lambda W: sum(W) - 1},
              {'type':'eq', 'fun': lambda W: sqrt(np.dot(W, np.dot(covar, W)) * 252) - risk_tgt})

    optimized = opt.minimize(fitness, W, args = [Rt], method='SLSQP', constraints=c_, bounds=b_)  

    if not optimized.success: 
        raise ValueError(optimized.message)

   return optimized.x  # Return optimized weights

class_cont = Rt.ix[0].copy()
class_cont.ix[:] = np.around(np.hstack(Rt.groupby(axis=1, level=0).apply(solve_weights).values),3)
scalars = class_cont.groupby(level=0).sum()
scalars.ix[:] = np.around(solve_weights((class_cont * port_rets).groupby(level=0, axis=1).sum(), list(model['tactical'].values)),3)

class_cont.groupby(level=0).transform(lambda x: x * scalars[x.name])
于 2013-08-19T22:20:36.473 回答