【问题标题】:SciPy optimization with grouped bounds具有分组边界的 SciPy 优化
【发布时间】:2013-08-15 14:46:58
【问题描述】:

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

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。当打印出等重的投资组合时,如下所示:

Out[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()
Out[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])

【问题讨论】:

  • 不确定我是否理解问题所在。您想检查该值是否在提供的范围内并返回一个布尔 True/False 数组,还是想以这些范围表示的类别表示您的值?
  • 我正在尝试使用 scipy.opt 给我返回符合单独绑定到 (0,1) 的权重,然后也让分组的类权重受我显示的限制的约束以上。
  • 对于问题的第一部分:等待不是已经有界了吗?您将bounds 参数正确地提供给minimize 方法,所以它应该返回一个有界权重列表?
  • 是的,所有的权重都在 (0,1) 之间。现在我想添加第二个键
  • 出于说明的目的,假设我要使用伪代码中的约束来执行此操作: c_ = ({'type':'eq', 'fun': lambda W: sum(W ) - 1}, {'type':'eq', 'fun': lambda W: .08

标签: python optimization pandas scipy finance


【解决方案1】:

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

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 是重复K[i] 次的间隔(tuple),其中K 是特定级别出现的次数,iith 级别。 column_name 在你的情况下恰好是一个日期。

这表示限制权重,使ith 组中的权重总和在关联的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

【讨论】:

  • 我收到错误 AttributeError: 'SeriesGroupBy' object has no attribute 'filter' 我在 pandas 0.11
  • GroupBy 对象上的 filter 方法是由提交 2a2cfb83582ece7690c94457cc5a6f0835049f5c 引入的,要使用它,您需要升级到 pandas 0.12。我会看看我是否可以在不使用 filter 的情况下找到等效的解决方案
  • 但如果我正确理解了您的代码,它会测试分组级别是否在我的范围内,这正是我想要的。但是,我不熟悉“过滤器”,并且不确定要在哪里通过?因为你没有在这里传递它W.groupby(level=0, axis=0).filter(filterer).empty?
  • 将我在上面定义的字典c_ 添加到您在上面在solve_weights 中定义的dicts 中的tuple
  • 哦,我看到了问题,我误解了您的最后评论。我所做的是假设您有一个名为rangetuples 列,其中包含该特定组的间隔。或者,可能更好的是有两列,例如,lowerupper,它们分别是区间的下端点和上端点。
【解决方案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])

【讨论】:

    猜你喜欢
    • 2016-10-11
    • 2017-10-29
    • 1970-01-01
    • 2015-08-25
    • 1970-01-01
    • 2020-03-22
    • 2021-07-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多