I am trying to perform a portfolio optimization that returns the weights which maximize my utility function. I can do this portion just fine including the constraint that we
Not totally sure I understand, but I think you can add the following as another constraint:
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
where x.range
is the interval (tuple
) repeated K[i]
times where K
is the number of times a particular level occurs and i
is the i
th level. column_name
in your case happens to be a date.
This says constrain the weights such that the sum of the weights in the i
th group is between the associated tuple
interval.
To map each of the level names to an interval do this:
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
After much time this seems to be the only solution that fits...
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])