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