Trying to optimize a portfolio weight allocation here which maximize my return function by limit risk. I have no problem to find the optimized weight that yields to my return function by simple constraint that the sum of all weight equals to 1, and make the other constraint that my total risk is below target risk.
My problem is, how can I add industry weight bounds for each group?
My code is below:
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import scipy.optimize as sco
dates = pd.date_range('1/1/2000', periods=8)
industry = ['industry', 'industry', 'utility', 'utility', 'consumer']
symbols = ['A', 'B', 'C', 'D', 'E']
zipped = list(zip(industry, symbols))
index = pd.MultiIndex.from_tuples(zipped)
noa = len(symbols)
data = np.array([[10, 9, 10, 11, 12, 13, 14, 13],
[11, 11, 10, 11, 11, 12, 11, 10],
[10, 11, 10, 11, 12, 13, 14, 13],
[11, 11, 10, 11, 11, 12, 11, 11],
[10, 11, 10, 11, 12, 13, 14, 13]])
market_to_market_price = pd.DataFrame(data.T, index=dates, columns=index)
rets = market_to_market_price / market_to_market_price.shift(1) - 1.0
rets = rets.dropna(axis=0, how='all')
expo_factor = np.ones((5,5))
factor_covariance = market_to_market_price.cov()
delta = np.diagflat([0.088024, 0.082614, 0.084237, 0.074648,
0.084237])
cov_matrix = np.dot(np.dot(expo_factor, factor_covariance),
expo_factor.T) + delta
def calculate_total_risk(weights, cov_matrix):
port_var = np.dot(np.dot(weights.T, cov_matrix), weights)
return port_var
def max_func_return(weights):
return -np.sum(rets.mean() * weights)
# optimized return with given risk
tolerance_risk = 27
noa = market_to_market_price.shape[1]
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
{'type': 'eq', 'fun': lambda x: calculate_total_risk(x, cov_matrix) - tolerance_risk})
bnds = tuple((0, 1) for x in range(noa))
init_guess = noa * [1. / noa,]
opts_mean = sco.minimize(max_func_return, init_guess, method='SLSQP',
bounds=bnds, constraints=cons)
In [88]: rets
Out[88]:
industry utility consumer
A B C D E
2000-01-02 -0.100000 0.000000 0.100000 0.000000 0.100000
2000-01-03 0.111111 -0.090909 -0.090909 -0.090909 -0.090909
2000-01-04 0.100000 0.100000 0.100000 0.100000 0.100000
2000-01-05 0.090909 0.000000 0.090909 0.000000 0.090909
2000-01-06 0.083333 0.090909 0.083333 0.090909 0.083333
2000-01-07 0.076923 -0.083333 0.076923 -0.083333 0.076923
2000-01-08 -0.071429 -0.090909 -0.071429 0.000000 -0.071429
In[89]: opts_mean['x'].round(3)
Out[89]: array([ 0.233, 0.117, 0.243, 0.165, 0.243])
how can I add such group bound such that sum of 5 assets falling into to below bound?
model = pd.DataFrame(np.array([.08,.12,.05]), index= set(industry), columns = ['strategic'])
model['tactical'] = [(.05,.41), (.2,.66), (0,.16)]
In [85]: model
Out[85]:
strategic tactical
industry 0.08 (0.05, 0.41)
consumer 0.12 (0.2, 0.66)
utility 0.05 (0, 0.16)
I have read this similar post SciPy optimization with grouped bounds but still can't get any clues, can any body help? Thank you.
Firstly, consider using
cvxopt
, a module designed specifically for convex optimization. I'm not too familiar but an example for an efficient frontier is here.Now getting to your question, here's a workaround that applies specifically to the question you posted and uses
minimize
. (It could be generalized to create more flexibility in input types and user-friendliness, and a class-based implementation would be useful here too.)Regarding your question, "how can I add group bounds?", the short answer is that you actually need to do this through the
constraints
rather thanbounds
parameter becauseThis specification doesn't match with what you're trying to do. What the below example does, instead, is to add an inequality constraint separately for the upper and lower bound of each group. The function
mapto_constraints
returns a list of dicts that is added to your current constraints.To begin, here's some example data:
You can see that the annualized figures "make sense":
Now for some functions that will be used in the optimization. These are closely modeled after examples from Yves Hilpisch's Python for Finance, chapter 11.
Here is the expected annualized standard deviation of an equal-weight portfolio. I'm just giving this here to use as an anchor in the optimization (the
risk_tol
parameter).The next function takes a DataFrame that looks like your
model
DataFrame and builds constraints for each group. Note that this is pretty inflexible in that you will need to follow the specific format for the returns andmodel
DataFrames you're using now.A note on how the constraints are built above:
Lastly, the optimization itself: