具有分组边界的 SciPy 优化

Bra*_*gle 6 python optimization finance scipy pandas

我正在尝试执行投资组合优化,返回使我的效用函数最大化的权重。我可以很好地完成这一部分,包括权重总和为 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)
Run Code Online (Sandbox Code Playgroud)

现在我可以深入研究这个问题,我将权重存储在一个 MultIndex 熊猫系列中,这样每个资产都是与资产类别相对应的 ETF。当一个同等权重的投资组合被打印出来时,看起来像这样:

出[263]:
权益 CZA 0.045455
             IWM 0.045455
             间谍 0.045455
国际股权 EWA 0.045455
             EWO 0.045455
             IEV 0.045455
债券 IEF 0.045455
             害羞 0.045455
             TLT 0.045455
国际债券 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()
Run Code Online (Sandbox Code Playgroud) 出[264]:
净值 0.136364
国际股权 0.136364
债券 0.136364
国际债券 0.136364
商品 0.136364
pe 0.136364
高频 0.090909
现金 0.090909
数据类型:float64

在这些范围内

[(.08,.51), (.05,.21), (.05,.41), (.05,.41), (.2,.66), (0,.16), (0,.76), (0,.11)]
Run Code Online (Sandbox Code Playgroud)

[更新] 我想我会用一个我不太满意的笨拙的伪解决方案来展示我的进步。也就是说,因为它不使用整个数据集来解决权重,而是逐个资产类别来解决权重。另一个问题是它返回序列而不是权重,但我相信有人比我更合适,可以提供有关 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
Run Code Online (Sandbox Code Playgroud)

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

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()
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

[更新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])
Run Code Online (Sandbox Code Playgroud)

Bra*_*gle 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])
Run Code Online (Sandbox Code Playgroud)