Modern portfolio theory is one of the most popular techniques used by investors to optimize their portfolios. It is a mathematical framework for building portfolios that aim to maximize returns while minimizing risk. In this article, we will explore a Python implementation of the mean-variance optimization (MVO) framework, which is a key component of modern portfolio theory. We will also highlight the benefits of using IBMid for portfolio optimization.
Python implementation of the MVO framework:
The MVO framework is a technique used to build optimal portfolios by balancing the trade-off between expected returns and volatility. This technique involves two main steps: the first step is to estimate the expected returns and volatility of each asset in the portfolio, while the second step is to use these estimates to build an optimal portfolio.
To implement the MVO framework in Python, we need to download the stock price data, calculate the log returns for each stock, and define the user-defined risk-free rate and maximum weight for each stock. In this implementation, we will use the Yahoo Finance API to download stock price data, the Pandas library to calculate the log returns, and the Docplex library to define and solve the optimization problem.
The first step is to download the stock price data from Yahoo Finance for the selected stocks. In this example, we will download the data for four Indian stocks: Reliance Industries, Tata Consultancy Services, HDFC Bank, and Hindustan Unilever. The data will be downloaded from January 1, 2010, to December 31, 2021, with a weekly interval. Once the data is downloaded, we will calculate the log returns for each stock using the Pandas library.
Next, we will define the user-defined risk-free rate and the maximum weight for each stock. The risk-free rate is the return on an investment with zero risk, such as a government bond. In this implementation, we will assume a risk-free rate of 5%. The maximum weight is the maximum percentage of the portfolio that can be invested in each stock. In this implementation, we will assume a maximum weight of 10%.
We will create a function called portfolio_optimization that will perform the optimization process using the mean-variance optimization framework. The function takes the weights, log returns, and risk-free rate as inputs and returns the portfolio’s mean return, volatility, Sharpe ratio, and portfolio return. The Sharpe ratio is a measure of risk-adjusted return that takes into account the risk-free rate. It is calculated as the portfolio’s excess return over the risk-free rate divided by the portfolio’s volatility.
We will also define the bounds and constraints for the optimization process. The bounds are the minimum and maximum values for each weight, and the constraints are the conditions that the solution must satisfy. In this implementation, we will have two constraints: the first constraint requires that the sum of the weights is equal to one, and the second constraint requires that the sum of the weights for each stock is less than or equal to the maximum weight.
We will use the Docplex library to define and solve the optimization problem. We will create an instance of the model and define the decision variables, objective function, and constraints. The decision variables represent the weights of each stock in the portfolio. The objective function is the weighted sum of the mean log returns for each stock, and the constraints are the conditions that the solution must satisfy.
Finally, we will print the optimal weights and the portfolio’s performance metrics, including the mean return, volatility, Sharpe ratio, and portfolio return.
IBMid and its benefits for portfolio optimization:
IBMid is a single sign-on service provided by IBM that allows users to access a range of IBM services and products, including IBM Watson Studio, IBM Cloud, and IBM Watson Machine Learning. IBMid offers several benefits for a portfolio.
In order to run the portfolio optimization program written in Python using the CPLEX library, you will need to have CPLEX installed on your computer. CPLEX is a high-performance mathematical optimization solver that can be used to solve linear, quadratic, and mixed-integer programming problems.
To install CPLEX, you first need to create an account on the IBM website and get an IBMid. An IBMid is a unique identifier that is used to log in to IBM websites and services, including the IBM developerWorks website where you can download CPLEX.
Once you have an IBMid, you can follow these steps to download and install CPLEX:
- Create an IBMid: Go to the IBM registration page (https://myibm.ibm.com/dashboard/account) and create an IBMid if you do not have one.
- Download CPLEX: Go to the IBM website (https://www.ibm.com/analytics/cplex-optimizer) and download the version of CPLEX that matches your system and Python version.
- Install CPLEX: Install CPLEX by running the downloaded installer and following the instructions. Make sure to select the Python option during the installation process.
- Configure CPLEX in your Python environment: Once CPLEX is installed, you need to configure it in your Python environment. The exact steps depend on your operating system and Python version, but the general process is to add the CPLEX installation directory to your system’s PATH environment variable and to add the CPLEX Python modules to your Python path.
In the code provided, you can see that the docplex
library is imported at the beginning of the program using the following line:
from docplex.mp.model import Model
This library provides a Python interface to the CPLEX solver, which allows you to use CPLEX to solve the portfolio optimization problem.
If you do not have CPLEX installed on your computer, you will receive an error message when you try to run the program, indicating that no CPLEX runtime was found. In this case, you can follow the steps outlined above to download and install CPLEX.
It is worth noting that while CPLEX is a powerful solver that can handle large-scale optimization problems, it is not free. IBM offers a range of licensing options for CPLEX, including academic and commercial licenses, which provide different levels of functionality and support. If you are a student or researcher, you may be able to get access to a free academic license through your institution.
Python Code
import subprocess
import sys
import importlib
# Define a list of libraries to install
libraries = ["docplex","pytz","yfinance","pandas"]
# Loop through each library in the list
for library in libraries:
# Try to import the library
try:
importlib.import_module(library)
print(f"{library} is already installed.")
# If the import fails, install the library using pip
except ImportError:
print(f"{library} is not installed. Installing now...")
subprocess.check_call([sys.executable, "-m", "pip", "install", library])
import yfinance as yf
import pandas as pd
import numpy as np
from sklearn.model_selection import TimeSeriesSplit
from sklearn.linear_model import LinearRegression
from docplex.mp.model import Model
import matplotlib.pyplot as plt
# Download stock price data from Yahoo Finance
tickers = [
"RELIANCE.NS","TCS.NS", "HDFCBANK.NS", "HINDUNILVR.NS" ]
data = yf.download(tickers, start='2010-01-01', end='2021-12-31', interval='1wk')['Adj Close'].fillna(method='ffill')
# Calculate log returns for each stock
log_returns = np.log(data/data.shift(1)).dropna()
# User-defined Risk Free Rate and Maximum weight for each stock
rf_rate = 0.05
Max_allocation_p = 0.1
# Create function to perform portfolio optimization using mean-variance optimization framework
def portfolio_optimization(weights, log_returns, rf_rate):
port_log_returns = np.sum(log_returns * weights, axis=1)
port_mean_return = np.mean(port_log_returns)
port_volatility = np.sqrt(np.dot(weights.T, np.dot(log_returns.cov(), weights)))
Sharpe_ratio = (port_mean_return - rf_rate) / port_volatility
portfolio_return = (1 + port_mean_return) * (1 + rf_rate) - 1
return np.array([port_mean_return, port_volatility, Sharpe_ratio, portfolio_return])
# Define bounds and constraints for optimization process
bounds = [(0, 1) for _ in tickers]
constraints = [{'type': 'eq', 'fun': lambda x: np.sum(x)-1},
{'type': 'ineq', 'fun': lambda x: np.sum([x[i] for i in range(len(tickers))]) - Max_allocation_p}]
# Create an instance of the model
mdl = Model('Portfolio Optimization')
# Create decision variables
w = mdl.continuous_var_list(len(tickers), lb=0, ub=1, name='weights')
# Define the objective function
objective = mdl.sum(w[i] * log_returns.mean()[i] for i in range(len(tickers)))
mdl.maximize(objective)
# Define constraints
for i in range(len(constraints)):
mdl.add_constraint(constraints[i]['fun'](w) >= 0, constraints[i]['type'])
# Solve the optimization problem
mdl.solve()
# Get the optimal weights
weights = [v.solution_value for v in w]
# Print portfolio weights and performance metrics
portfolio = pd.DataFrame({'Ticker': tickers, 'Weight': weights})
print(portfolio)
port_mean_return, port_volatility, Sharpe_ratio, portfolio_return = portfolio_optimization(weights, log_returns, rf_rate)
print('Mean Return: {:.2f}%'.format(port_mean_return*100))
print('Volatility: {:.2f}%'.format(port_volatility*100))
print('Sharpe Ratio: {:.2f}'.format(Sharpe_ratio))
print('Portfolio Return: {:.2f}%'.format(portfolio_return*100))
Output/Results
Ticker Weight
0 RELIANCE.NS 0.049354
1 TCS.NS 0.312987
2 HDFCBANK.NS 0.238289
3 HINDUNILVR.NS 0.399369
Mean Return: 0.89%
Volatility: 2.80%
Sharpe Ratio: 0.31
Portfolio Return: 7.20%
The output starts by showing the status of each library’s installation, with the first four lines confirming that all the required libraries are already installed.
Next, the code downloads weekly adjusted closing prices of the four selected stocks from Yahoo Finance and calculates their log returns. It then defines a user-defined risk-free rate and maximum weight for each stock.
The portfolio_optimization
function takes as input the weights, log returns, and risk-free rate to compute the portfolio’s mean return, volatility, Sharpe ratio, and portfolio return.
The optimization process begins by defining bounds and constraints for the optimization problem. The bounds ensure that the weights for each stock are between 0 and 1, while the constraints ensure that the sum of the weights equals 1 and that the sum of the weights for each stock does not exceed the maximum allocation.
The code then creates an instance of the model and defines decision variables, an objective function, and constraints for the optimization problem using the docplex.mp.model
library.
The optimization problem is then solved, and the optimal weights for each stock are printed along with the mean return, volatility, Sharpe ratio, and portfolio return.
The output shows that the optimal weights for the stocks are RELIANCE.NS (4.93%), TCS.NS (31.30%), HDFCBANK.NS (23.83%), and HINDUNILVR.NS (39.94%). The portfolio’s mean return is 0.89%, and its volatility is 2.80%, resulting in a Sharpe ratio of 0.31 and a portfolio return of 7.20%.
You must log in to post a comment.