Portfolio Optimization using Python and Modern Portfolio Theory
Portfolio Optimization is an essential aspect of modern investing, and Modern Portfolio Theory (MPT) is a well-known approach to achieve an optimal portfolio. In this article, we will walk through a Python script that demonstrates how to optimize a portfolio of stocks using MPT. We will use Yahoo Finance data and the Scipy library to find the optimal weights of assets in the portfolio that maximize the Sharpe ratio.
Section 1: Define Tickers and Time Range
First, we define a list of tickers for the assets we want to include in the portfolio. In this example, we use five ETFs representing various asset classes: SPY, BND, GLD, QQQ, and VTI. We then set the start and end dates for our analysis. We use a five-year historical time range for our calculations.
tickers = ['SPY','BND','GLD','QQQ','VTI']
end_date = datetime.today()
start_date = end_date - timedelta(days = 5*365)
Section 2: Download Adjusted Close Prices
Next, we create an empty DataFrame to store the adjusted close prices of each asset. We use the yfinance library to download the data from Yahoo Finance.
adj_close_df = pd.DataFrame()
for ticker in tickers:
data = yf.download(ticker, start = start_date,end = end_date)
adj_close_df[ticker] = data['Adj Close']
Section 3: Calculate Lognormal Returns
We calculate the lognormal returns for each asset and drop any missing values.
console.log( 'Code is Poetry' );
Section 4: Calculate Covariance Matrix
We compute the covariance matrix using the annualized log returns.
cov_matrix = log_returns.cov() * 252
Section 5: Define Portfolio Performance Metrics
We define functions to calculate the portfolio standard deviation, expected return, and Sharpe ratio.
def standard_deviation(weights, cov_matrix):
variance = weights.T @ cov_matrix @ weights
return np.sqrt(variance)
def expected_return(weights, log_returns):
return np.sum(log_returns.mean()*weights)*252
def sharpe_ratio(weights, log_returns, cov_matrix, risk_free_rate):
return (expected_return(weights, log_returns) - risk_free_rate) / standard_deviation(weights, cov_matrix)
Section 6: Portfolio Optimization
We set the risk-free rate, define a function to minimize the negative Sharpe ratio, and set constraints and bounds for the optimization process.
risk_free_rate = .02
def neg_sharpe_ratio(weights, log_returns, cov_matrix, risk_free_rate):
return -sharpe_ratio(weights, log_returns, cov_matrix, risk_free_rate)
constraints = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1}
bounds = [(0, 0.4) for _ in range(len(tickers))]
initial_weights = np.array([1/len(tickers)]*len(tickers))
optimized_results = minimize(neg_sharpe_ratio, initial_weights, args=(log_returns, cov_matrix, risk_free_rate), method='SLSQP', constraints=constraints, bounds=bounds)
Section 7: Analyze the Optimal Portfolio
We obtain the optimal weights and calculate the expected annual return, expected volatility, and Sharpe ratio for the optimal portfolio.
optimal_weights = optimized_results.x
print("Optimal Weights:")
for ticker, weight in zip(tickers, optimal_weights):
print(f"{ticker}: {weight:.4f}")
optimal_portfolio_return = expected_return(optimal_weights, log_returns)
optimal_portfolio_volatility = standard_deviation(optimal_weights, cov_matrix)
optimal_sharpe_ratio = sharpe_ratio(optimal_weights, log_returns, cov_matrix, risk_free_rate)
print(f"Expected Annual Return: {optimal_portfolio_return:.4f}")
print(f"Expected Volatility: {optimal_portfolio_volatility:.4f}")
print(f"Sharpe Ratio: {optimal_sharpe_ratio:.4f}")
Display the Final Portfolio in a Plot
We create a bar chart to visualize the optimal weights of the assets in the portfolio.
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.bar(tickers, optimal_weights)
plt.xlabel('Assets')
plt.ylabel('Optimal Weights')
plt.title('Optimal Portfolio Weights')
plt.show()
Conclusion
This Python script demonstrates how to use Modern Portfolio Theory to optimize a portfolio of stocks. By calculating the optimal weights for each asset, we aim to maximize the Sharpe ratio, providing the highest possible risk-adjusted return. This approach can help investors create a well-diversified portfolio and make informed decisions when allocating their investments.