Skip to main content

Portfolio Optimization

A notebook was written to build a diversified portfolio for an individual, considering investment instruments such as mutual funds, ETFs, etc.

Given a certain risk or return threshold, Mean Variance Optimization was used to build an optimally diversified portfolio.

For example, if the individual was classified into Type 1 having maximum risk threshold of 1% according to answers in the behavioral finance questionnaire, Mean Variance Optimization would be used to build a portfolio to maximize returns with the constraint of risk being at most 1%.

Code

Profitable Mutual Funds were identified as potential investment assets, and loaded into a dataframe using pandas datareader and yahoo finance.

assets = {'HDFC Balanced Adv Dir Gr': '0P0001EI12.BO',
'HDFC Floating Rate Debt Gr': '0P0000CUYA.BO',
'Quant Small Cap Dir Gr': '0P0000XW4J.BO',
'Parag Parikh Long Term Equity': '0P0000YWL1.BO',
'ICICI Prudential Nifty 50 ETF': 'ICICINIFTY.NS',
'ICICI Pru Savings Dir Gr': '0P0000XWAO.BO',
'Quant Tax Plan Dir Gr': '0P0000XW51.BO',
'Union Tax saver (ELSS)': '0P0000UJ5X.BO',
'ICICI Prudential Balanced Advantage Fund': '0P00008TN4.BO',
'Aditya BSL ELSS Tax Relief 96': '0P0000XVYC.BO',
'Baroda BNP P ELSS': '0P0001B9QN.BO',
'Motilal Oswal Nasdaq 100 Fd of Fd Dir Gr': '0P0001F0CK.BO',
'Nippon India ETF Gold BeES': 'GOLDBEES.NS'}

data = pd.DataFrame()
for asset in assets.keys():
mutual_fund_data = pdr.get_data_yahoo(assets[asset], start=start_date, end=end_date)
data[asset] = mutual_fund_data['Adj Close']

The expected returns and covariance of the assets was calculated using 252 days since that's the number of days in a financial year.

daily_returns = data.pct_change().dropna()
expected_returns = daily_returns.mean() * 252
cov_matrix = daily_returns.cov() * 252

Next, cvxpy package was used to perform convex optimization on the portfolio returns/risk using these expected reurns and covariance matrix.

import cvxpy as cp

num_assets = len(assets)
weights = cp.Variable(num_assets)
expected_return = cp.sum(cp.multiply(weights, expected_returns))
portfolio_variance = cp.quad_form(weights, cov_matrix)

Two types of constrained optimization are possible - maximizing returns given a risk tolerance or minimizing risk given a return desire.

investors = {'Super Careful Treasure Guardian': [0.005, 0.09],
'Cautious Treasure Protector': [0.01, 0.10],
'Balanced Treasure Keeper': [0.03, 0.15],
'Clever Treasure Adventurer': [0.05, 0.20],
'Super Daring Treasure Explorer': [0.1, 0.30]}

type = 'Super Careful Treasure Guardian' # Personality type of the investor

risk_tolerance = investors[type][0] # Risk threshold. Lower the value, more conservative.
return_goal = investors[type][1] # Return threshold. Higher the value, more ambitious.

constraints = [cp.sum(weights) <= 1, weights >= 0]

# Uncomment these two lines if you want to maximize return while keeping risk at a threshold
# constraints.append(portfolio_variance <= risk_tolerance)
# objective = cp.Maximize(expected_return)

# Uncomment these two lines if you want to minimize risk while keeping return at a threshold
constraints.append(expected_return >= return_goal)
objective = cp.Minimize(portfolio_variance)

problem = cp.Problem(objective, constraints)
problem.solve()
optimized_weights = weights.value
optimized_expected_return = expected_return.value
optimized_portfolio_variance = portfolio_variance.value
final_weights = {str(list(assets.keys())[i]): round(optimized_weights[i], 4) for i in range(num_assets) if optimized_weights[i] >= 0.01}

For Super Careful Treasure Guardian and Cautious Treasure Protector, risk is minimized given a return threshold. For Super Daring Treasure Explorer and Clever Treasure Adventurer, return is maximized given a risk threshold.

For the above example, optimal portfolio is:

print(f"Portfolio of {type}")
print("Optimized Portfolio Weights:")
for asset in final_weights:
print(f"{asset}: {final_weights[asset] * 100}%")
print("Optimized Expected Return: " + str(round(optimized_expected_return * 100, 2)) + '%')
print("Optimized Portfolio Risk: " + str(round(optimized_portfolio_variance * 100, 2)) + '%')
print("Total Invested Budget: " + str(round(sum(optimized_weights) * 100, 2)) + '%')

# Portfolio of Super Careful Treasure Guardian
# Optimized Portfolio Weights:
# HDFC Floating Rate Debt Gr: 47.18%
# Quant Small Cap Dir Gr: 4.3999999999999995%
# Parag Parikh Long Term Equity: 2.29%
# ICICI Pru Savings Dir Gr: 44.45%
# Motilal Oswal Nasdaq 100 Fd of Fd Dir Gr: 1.67%
# Optimized Expected Return: 9.0%
# Optimized Portfolio Risk: 0.04%
# Total Invested Budget: 100.0%

The expected return of this portfolio is 9% at a minimal risk of 0.04%.