Portfolio optimization: from the highest Sharpe Ratio to minimum volatility

Portfolio optimization: from the highest Sharpe Ratio to minimum volatility

In this article, you will learn about the following:

  • How to calculate log returns and plot a correlation heatmap.
  • How to create a portfolio with equal weights and calculate statistics such as Expected Return, Volatility, and Sharpe ratio.
  • How to run portfolio optimisation to find a portfolio with the highest Sharpe ratio and minimum volatility.
  • How to calculate portfolio risk and performance for different parameters.



Read the full article at https://quantjourney.substack.com/


Let's assume that we want to create an optimal portfolio for the top growth companies that outperformed in 2023, also known as the "Magnificent 7". First, we will obtain the historical adjusted closing prices of these companies from Yahoo Finance.

start = '2021-01-01'
end = '2024-01-24'
tickers = ['META', 'TSLA','AAPL', 'MSFT', 'AMZN', 'GOOGL', 'NVDA']

data = yf.download(tickers, start=start, end=end)['Adj Close'][tickers]
        

Stock performance

To analyse the stock performance, we calculate daily log returns and plot them over time to examine our data.

daily_log_returns = np.log(data) - np.log(data.shift(1))
daily_log_returns.plot(figsize=(13, 8))
        

Correlation heatmap

Now, let's perform a correlation analysis of log returns and plot a heatmap using the seaborn library.

corr = daily_log_returns.corr()

plt.figure(figsize=(10, 10))
sns.heatmap(corr, annot=True, cmap="viridis")
plt.title('Log Returns Correlation Heatmap')
plt.show()
        

In the correlation heatmap, the colors represent the strength of the correlation between the returns of each pair of stocks. A value of 1 indicates a perfect positive correlation, while numbers approaching 0 indicate little to no correlation. The heatmap allows us to visualize how similarly the stocks move in relation to each other.

Visualise returns distribution of the Portfolio

Moving forward, we will assess the performance of our portfolio by analyzing key metrics over time. These metrics include expected return, volatility, skewness, and the Sharpe ratio. To achieve diversification, we have created an equally weighted portfolio, where each stock is allocated the same proportion of the total investment.

stocks = len(tickers)
weights = np.full((stocks,), 1 / stocks)
        
daily_portfolio_returns = daily_log_returns.dot(weights)
sns.displot(daily_portfolio_returns, bins=50)
        

Let's plot daily returns using the sns library.

Returns Distribution Statistics

expected_return = daily_portfolio_returns.mean() * 252
vol = daily_portfolio_returns.std() * np.sqrt(252)
skew = daily_portfolio_returns.skew()
sr = expected_return / vol

print(f"{expected_return=}")
print(f"{vol=}")
print(f"{skew=}")
print(f"{sr=}")
        

which gives the output:

expected_return=0.154953778394305

vol=0.3183505654118577

skew=-0.14207782986445805

sr=0.4867394477337824

Simulate 10.000 portfolios with Monte Carlo

In 1952, Harry Markowitz introduced this concept, which fundamentally changed how investors approach portfolio construction. It is a method for determining the optimal asset allocation in a portfolio to maximise returns for a given level of risk.

By combining assets with different expected returns and volatilities, investors can create a portfolio that maximises returns while minimizing risk. This involves solving a constrained optimisation problem, where the goal is to minimize the portfolio's variance. Mathematically, this is expressed as minimising wT*Σ, where w is the vector of portfolio weights, and Σ is the covariance matrix of the asset returns.

Efficient Frontier

When the target return (μ) is adjusted, the optimal weights (w) also undergo changes, leading to the formation of different portfolios. As a result, the Efficient Frontier is created, which is a curve on a risk-return graph. This curve represents the collection of portfolios that offer the lowest risk for a given level of expected return, and vice versa.

To further explore portfolio optimisation, we conduct a Monte Carlo simulation with 10,000 iterations and plot the results to visualise the Efficient Frontier approach. This method entails randomly generating portfolio weights and assessing their performance in terms of returns, volatility, and the Sharpe ratio. The objective of this exercise is to identify the most efficient allocation that offers the highest return per unit of risk.

returns = daily_log_returns
mean_returns = returns.mean() * 252
cov_matrix = returns.cov() * np.sqrt(252)

# Assuming 'tickers', 'mean_returns', and 'cov_matrix' are already defined
num_portfolios = 10000

# DataFrame to store the results
results = pd.DataFrame(columns=['returns', 'volatility', 'sharpe', 'weights'], index=range(num_portfolios))
# store all weights
weights_record = []

for i in range(num_portfolios):
		# Normalised randomly generated weights
    weights = np.random.random(len(tickers))
    weights /= np.sum(weights)

		# Calculate returns and volatility
    returns = np.dot(weights, mean_returns)
    volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))

		# Store results
    results.loc[i, 'returns'] = returns
    results.loc[i, 'volatility'] = volatility
    results.loc[i, 'sharpe'] = results.loc[i, 'returns'] / results.loc[i, 'volatility']
    results.loc[i, 'weights'] = ','.join(str(np.round(weight, 4)) for weight in weights)
		# record weights
    weights_record.append(weights)
    
print(results.sort_values('sharpe', ascending=False).head(10))
        

The results are as follows:

The portfolio with the highest Sharpe ratio of 3.42 is built with the following weights:

META (1.1%),

TSLA (1.0%),

AAPL (14%),

MSFT (36%),

AMZN (5.2%),

GOOGL (7.2%),

NVDA (34%).

Finding maximum Sharpe ratio and minimum volatility portfolio

From the efficient frontier, we can select multiple portfolios. As you saw, we can choose a portfolio that maximises the Sharpe ratio. Additionally, we can select the portfolio with the maximum return for a given target risk.

With the code below, we can obtain the maximum Sharpe portfolio and the minimum volatility portfolio. The maximum Sharpe portfolio is expected to offer the highest return per unit of risk, while the minimum volatility portfolio is, in fact, the most optimal portfolio with the lowest amount of risk. It can be considered a special case, as it serves as the "lowest risk benchmark" for all other portfolios on the efficient frontier. Over time, it has shown surprisingly good performance, leading some managers to choose it as their preferred portfolio.

# Efficient Frontier with max sharpe portfolio and min volatility portfolio
max_sharpe_portfolio = results.iloc[pd.to_numeric(results['sharpe']).idxmax()]
min_volatility_portfolio = results.iloc[pd.to_numeric(results['volatility']).idxmin()]        

Conclusion:

With just a few lines of Python code, you can create a diverse portfolio and evaluate its performance. By adjusting and analyzing strategies that have worked well in the past, you can make informed decisions for future investments.

In addition, we will discuss factor-based portfolio optimisation in future posts. This approach considers factors like company size, value, and market risk to potentially improve returns and manage risk effectively.

Read the full article at https://quantjourney.substack.com/


Vladan Pantelic

Co-Founder & CEO of Hoick

9 个月

Jakub, your explanations are super simple and practical. Love it! Sanjay Nadkarni, Christof Elsener

要查看或添加评论,请登录

Jakub Polec的更多文章

社区洞察

其他会员也浏览了