avatarAlexzap

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

8609

Abstract

6401</span> NVDA <span class="hljs-number">0.398202</span> <span class="hljs-number">0.335418</span> <span class="hljs-number">0.426401</span> <span class="hljs-number">1.000000</span>

<span class="hljs-keyword">import</span> seaborn <span class="hljs-keyword">as</span> sns ax = sns.heatmap(corr_matrix, annot=<span class="hljs-literal">True</span>)</pre></div><figure id="9c90"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*nmOAgrT7SMwDBQZ6HtCo5Q.png"><figcaption>Correlation matrix of log daily changes</figcaption></figure><ul><li>Calculating the randomly weighted portfolio’s variance</li></ul><div id="b41c"><pre>w = {<span class="hljs-string">'AAPL'</span>: <span class="hljs-number">0.1</span>, <span class="hljs-string">'NVDA'</span>: <span class="hljs-number">0.4</span>, <span class="hljs-string">'META'</span>: <span class="hljs-number">0.3</span>, <span class="hljs-string">'AMZN'</span>: <span class="hljs-number">0.2</span>} port_var = cov_matrix.mul(w, axis=<span class="hljs-number">0</span>).mul(w, axis=<span class="hljs-number">1</span>).<span class="hljs-built_in">sum</span>().<span class="hljs-built_in">sum</span>() port_var <span class="hljs-number">0.0005679211473087001</span></pre></div><ul><li>Calculating the yearly returns for individual stocks</li></ul><div id="d41b"><pre>ind_er = df.resample(<span class="hljs-string">'Y'</span>).last().pct_change().mean() ind_er Ticker AAPL <span class="hljs-number">0.333813</span> AMZN <span class="hljs-number">0.688729</span> META <span class="hljs-number">0.414315</span> NVDA <span class="hljs-number">0.632646</span> dtype: float64</pre></div><ul><li>Calculating the portfolio return</li></ul><div id="e79e"><pre>w = [<span class="hljs-number">0.1</span>, <span class="hljs-number">0.4</span>, <span class="hljs-number">0.3</span>, <span class="hljs-number">0.2</span>] port_er = (wind_er).<span class="hljs-built_in">sum</span>() port_er <span class="hljs-number">0.5596969011447753</span></pre></div><ul><li>Calculating the annual std</li></ul><div id="f1e7"><pre>ann_sd = df.pct_change().apply(<span class="hljs-keyword">lambda</span> x: np.log(<span class="hljs-number">1</span>+x)).std().apply(<span class="hljs-keyword">lambda</span> x: xnp.sqrt(<span class="hljs-number">250</span>)) ann_sd Ticker AAPL <span class="hljs-number">0.448354</span> AMZN <span class="hljs-number">0.554643</span> META <span class="hljs-number">0.402217</span> NVDA <span class="hljs-number">0.596450</span> dtype: float64</pre></div><ul><li>Creating a table for visualizing returns and volatility of assets</li></ul><div id="c706"><pre>assets = pd.concat([ind_er, ann_sd], axis=<span class="hljs-number">1</span>) assets.columns = [<span class="hljs-string">'Returns'</span>, <span class="hljs-string">'Volatility'</span>] assets

Returns Volatility Ticker
AAPL <span class="hljs-number">0.333813</span> <span class="hljs-number">0.448354</span> AMZN <span class="hljs-number">0.688729</span> <span class="hljs-number">0.554643</span> META <span class="hljs-number">0.414315</span> <span class="hljs-number">0.402217</span> NVDA <span class="hljs-number">0.632646</span> <span class="hljs-number">0.596450</span></pre></div><ul><li>Plotting the MPT efficient frontier with 10k random portfolios</li></ul><div id="74be"><pre>p_ret = [] <span class="hljs-comment"># Define an empty array for portfolio returns</span> p_vol = [] <span class="hljs-comment"># Define an empty array for portfolio volatility</span> p_weights = [] <span class="hljs-comment"># Define an empty array for asset weights</span>

num_assets = <span class="hljs-built_in">len</span>(df.columns) num_portfolios = <span class="hljs-number">10000</span> <span class="hljs-keyword">for</span> portfolio <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(num_portfolios): weights = np.random.random(num_assets) weights = weights/np.<span class="hljs-built_in">sum</span>(weights) p_weights.append(weights) returns = np.dot(weights, ind_er) <span class="hljs-comment"># Returns are the product of individual expected returns of asset and its </span> <span class="hljs-comment"># weights </span> p_ret.append(returns) var = cov_matrix.mul(weights, axis=<span class="hljs-number">0</span>).mul(weights, axis=<span class="hljs-number">1</span>).<span class="hljs-built_in">sum</span>().<span class="hljs-built_in">sum</span>()<span class="hljs-comment"># Portfolio Variance</span> sd = np.sqrt(var) <span class="hljs-comment"># Daily standard deviation</span> ann_sd = sd*np.sqrt(<span class="hljs-number">250</span>) <span class="hljs-comment"># Annual standard deviation = volatility</span> p_vol.append(ann_sd)

data = {<span class="hljs-string">'Returns'</span>:p_ret, <span class="hljs-string">'Volatility'</span>:p_vol}

<span class="hljs-keyword">for</span> counter, symbol <span class="hljs-keyword">in</span> <span class="hljs-built_in">enumerate</span>(df.columns.tolist()): <span class="hljs-comment">#print(counter, symbol)</span> data[symbol+<span class="hljs-string">' weight'</span>] = [w[counter] <span class="hljs-keyword">for</span> w <span class="hljs-keyword">in</span> p_weights]

portfolios = pd.DataFrame(data) portfolios.head() <span class="hljs-comment"># Dataframe of the 10000 portfolios created</span>

Returns Volatility AAPL weight AMZN weight META weight NVDA weight <span class="hljs-number">0</span> <span class="hljs-number">0.496109</span> <span class="hljs-number">0.356620</span> <span class="hljs-number">0.377360</span> <span class="hljs-number">0.383954</span> <span class="hljs-number">0.207498</span> <span class="hljs-number">0.031189</span> <span class="hljs-number">1</span> <span class="hljs-number">0.484475</span> <span class="hljs-number">0.335763</span> <span class="hljs-number">0.326887</span> <span class="hljs-number">0.267905</span> <span class="hljs-number">0.300058</span> <span class="hljs-number">0.105150</span> <span class="hljs-number">2</span> <span class="hljs-number">0.517491</span> <span class="hljs-number">0.352926</span> <span class="hljs-number">0.279398</span> <span class="hljs-number">0.261404</span> <span class="hljs-number">0.212166</span> <span class="hljs-number">0.247032</span> <span class="hljs-number">3</span> <span class="hljs-number">0.481427</span> <span class="hljs-number">0.352069</span> <span class="hljs-number">0.220658</span> <span class="hljs-number">0.054611</span> <span class="hljs-number">0.404624</span> <span class="hljs-number">0.320107</span> <span class="hljs-number">4</span> <span class="hljs-number">0.508543</span> <span class="hljs-number">0.349808</span> <span class="hljs-number">0.323731</span> <span class="hljs-number">0.324500</span> <span class="hljs-number">0.208675</span> <span class="hljs-number">0.143094</span>

<span class="hljs-comment"># Plot efficient frontier</span> portfolios.plot.scatter(x=<span class="hljs-string">'Volatility'</span>, y=<span class="hljs-string">'Returns'</span>, marker=<span class="hljs-string">'o'</span>, s=<span class="hljs-number">10</span>, alpha=<span class="hljs-number">0.3</span>, grid=<span class="hljs-literal">True</span>, figsize=[<span class="hljs-number">10</span>,<span class="hljs-number">10</span>])</pre></div><figure id="427a"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*5K3M_r83UXb3luSnhUtqKw.png"><figcaption>MPT efficient frontier with 10k random portfolios</figcaption></figure><ul><li>The Min Volatility portfolio is as follows:</li></ul><div id="f048"><pre>min_vol_port = portfolios.iloc[portfolios[<span class="hljs-string">'Volatility'</span>].idxmin()] <span class="hljs-comment"># idxmin() gives us the minimum value in the column specified. </span> min_vol_port Returns <span class="hljs-number">0.445058</span> Volatility <span class="hljs-number">0.324898</span> AAPL weight <span class="hljs-number">0.327430</span> AMZN weight <span class="hljs-number">0.145461</span> META weight <span class="hljs-number">0.448399</span> NVDA weight <span class="hljs-number">0.078710</span> Name: <span class="hljs-number">7891</span>, dtype: float64</pre></div><ul><li>Plotting the Min Volatility portfolio</li></ul><div id="73aa"><pre>plt.subplots(figsize=[<span class="hljs-number">10</span>,<span class="hljs-number">10</span>]) plt.scatter(portfolios[<span class="hljs-string">'Volatility'</span>], portfolios[<span class="hljs-string">'Returns'</span>],marker=<span class="

Options

hljs-string">'o'</span>, s=<span class="hljs-number">10</span>, alpha=<span class="hljs-number">0.3</span>) plt.scatter(min_vol_port[<span class="hljs-number">1</span>], min_vol_port[<span class="hljs-number">0</span>], color=<span class="hljs-string">'r'</span>, marker=<span class="hljs-string">'*'</span>, s=<span class="hljs-number">500</span>) plt.grid() plt.xlabel(<span class="hljs-string">'Volatility'</span>) plt.ylabel(<span class="hljs-string">'Return'</span>)</pre></div><figure id="00cb"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*ZsLfySmELa345sN1sNkgVQ.png"><figcaption>Min Volatility portfolio vs MPT efficient frontier</figcaption></figure><ul><li>Plotting the max Sharpe ratio portfolio with risk factor of 0.02</li></ul><div id="02db"><pre><span class="hljs-comment"># Finding the optimal portfolio</span> rf = <span class="hljs-number">0.02</span> <span class="hljs-comment"># risk factor</span> optimal_risky_port = portfolios.iloc[((portfolios[<span class="hljs-string">'Returns'</span>]-rf)/portfolios[<span class="hljs-string">'Volatility'</span>]).idxmax()] optimal_risky_port

Returns <span class="hljs-number">0.569114</span> Volatility <span class="hljs-number">0.371583</span> AAPL weight <span class="hljs-number">0.050555</span> AMZN weight <span class="hljs-number">0.386680</span> META weight <span class="hljs-number">0.321123</span> NVDA weight <span class="hljs-number">0.241643</span> Name: <span class="hljs-number">4683</span>, dtype: float64

<span class="hljs-comment"># Plotting optimal portfolio</span> plt.subplots(figsize=(<span class="hljs-number">10</span>, <span class="hljs-number">10</span>)) plt.scatter(portfolios[<span class="hljs-string">'Volatility'</span>], portfolios[<span class="hljs-string">'Returns'</span>],marker=<span class="hljs-string">'o'</span>, s=<span class="hljs-number">10</span>, alpha=<span class="hljs-number">0.3</span>) plt.scatter(min_vol_port[<span class="hljs-number">1</span>], min_vol_port[<span class="hljs-number">0</span>], color=<span class="hljs-string">'r'</span>, marker=<span class="hljs-string">''</span>, s=<span class="hljs-number">500</span>) plt.scatter(optimal_risky_port[<span class="hljs-number">1</span>], optimal_risky_port[<span class="hljs-number">0</span>], color=<span class="hljs-string">'g'</span>, marker=<span class="hljs-string">''</span>, s=<span class="hljs-number">500</span>) plt.grid() plt.xlabel(<span class="hljs-string">'Volatility'</span>) plt.ylabel(<span class="hljs-string">'Return'</span>)</pre></div><figure id="6a6c"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*aMW9XNPcwqPx9ysxHv15TA.png"><figcaption>max Sharpe ratio portfolio with risk factor of 0.02, min Volatility portfolio and MPT efficient frontier.</figcaption></figure><ul><li>Plotting the max Sharpe ratio portfolio with risk factor of 0.2</li></ul><div id="1c82"><pre><span class="hljs-comment"># Same as above but with rf = 0.2 </span>

rf = <span class="hljs-number">0.2</span> <span class="hljs-comment"># risk factor</span> optimal_risky_port = portfolios.iloc[((portfolios[<span class="hljs-string">'Returns'</span>]-rf)/portfolios[<span class="hljs-string">'Volatility'</span>]).idxmax()] optimal_risky_port ...</pre></div><figure id="0040"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*by1XwpMpfi_SMnzAY10UmQ.png"><figcaption>max Sharpe ratio portfolio with risk factor of 0.2, min Volatility portfolio and MPT efficient frontier.</figcaption></figure><ul><li>Notice that while the difference in risk between min Volatility portfolio and max Sharpe portfolio is just ca. 7% (with risk factor of 0.2), the difference in returns is a whopping 15%.</li></ul><h2 id="8967">Conclusions</h2><ul><li>In this post, we have demonstrated how the Markowitz portfolio optimization works in practice.</li><li>We have attempted to balance risk and return of the 4 major tech stocks (tickers): ‘AAPL’, ‘NVDA’, ‘META’, ‘AMZN’.</li><li>The correlation coefficient of these stocks is within the acceptable range [0.32, 0.51].</li><li>The annual std (vol) shows that vol(META)<vol(aapl)<vol(amzn)<vol(nvda).< li=""></vol(aapl)<vol(amzn)<vol(nvda).<></li><li>We have found the following optimal risky portfolio with risk factor of 0.02</li></ul><div id="e5e0"><pre>Returns <span class="hljs-number">0.569114</span> Volatility <span class="hljs-number">0.371583</span> AAPL weight <span class="hljs-number">0.050555</span> AMZN weight <span class="hljs-number">0.386680</span> META weight <span class="hljs-number">0.321123</span> NVDA weight <span class="hljs-number">0.241643</span></pre></div><ul><li>Results confirm the key de-risking ability of <a href="https://www.studocu.com/en-us/document/seton-hall-university/corporate-finance/the-advantages-and-limitations-of-markowitz-portfolio-theory/46687042">Markowitz MPT</a> to incorporate the asset correlation matrix into the portfolio optimization process.</li><li>Our future work will focus on <a href="https://www.linkedin.com/pulse/practical-limitations-modern-portfolio-theory-samer-obeidat-mgm">the shortcomings of MPT</a>:</li><li>The model’s underlying assumptions are predicated on <a href="https://www.realized1031.com/blog/what-are-the-advantages-and-limitations-of-the-markowitz-model">normally functioning markets</a>. In highly volatile and unpredictable markets, the model may lose relevance.</li><li>MPT is <a href="https://www.realized1031.com/blog/what-are-the-advantages-and-limitations-of-the-markowitz-model">mean-variance theory</a>. Risk by variance works when returns are normally distributed. Assets that do not follow a normal distribution will not work with the Markowitz Model.</li><li>To mitigate these limitations, we‘ll consider using alternative portfolio optimization methods, such as Black-Litterman <a href="https://fastercapital.com/content/Capital-Scoring-Simulation--How-to-Test-and-Validate-Your-Capital-Scoring-Model-Using-Monte-Carlo-Methods.html">model or Monte Carlo</a> simulation, and incorporate qualitative factors such as market trends and geopolitical <a href="https://fastercapital.com/content/Investment-Risk-Assessment--How-to-Assess-and-Manage-the-Risks-of-Your-Investment-Decisions-and-Strategies.html">risks into our investment decisions</a>.</li></ul><h2 id="f29b">Explore More</h2><ul><li><a href="https://wp.me/pdMwZd-1YB">Stock Portfolio Risk/Return Optimization</a></li><li><a href="https://wp.me/pdMwZd-1Zl">Risk/Return QC via Portfolio Optimization — Current Positions of The Dividend Breeder</a></li><li><a href="https://wp.me/pdMwZd-21T">Portfolio Optimization Risk/Return QC — Positions of Humble Div vs Dividend Glenn</a></li><li><a href="https://wp.me/pdMwZd-26M">Risk/Return POA — Dr. Dividend’s Positions</a></li><li><a href="https://wp.me/pdMwZd-2f2">Bear vs. Bull Portfolio Risk/Return Optimization QC Analysis</a></li><li><a href="https://wp.me/pdMwZd-4Je">Portfolio Optimization of 20 Dividend Growth Stocks</a></li><li><a href="https://wp.me/pdMwZd-4TI">Applying a Risk-Aware Portfolio Rebalancing Strategy to ETF, Energy, Pharma, and Aerospace/Defense Stocks in 2023</a></li><li><a href="https://wp.me/pdMwZd-5E7">Risk-Aware Strategies for DCA Investors</a></li><li><a href="https://wp.me/pdMwZd-4IB">Towards Max(ROI/Risk) Trading</a></li></ul><h2 id="9156">References</h2><ul><li>Markowitz, Harry, 1952, Portfolio selection, Journal of Finance 7, 1, 77–91.</li><li><a href="https://ryanoconnellfinance.com/python-portfolio-optimization/">Portfolio Optimization using Python and Modern Portfolio Theory</a></li><li><a href="https://readmedium.com/portfolio-optimization-a-beginners-guide-24488cebf52c">Portfolio Optimization: A Beginner’s Guide.</a></li><li><a href="https://readmedium.com/portfolio-optimisation-using-monte-carlo-simulation-25d88003782e">Portfolio Optimization using Monte Carlo Simulation</a></li><li><a href="https://github.com/damianboh/portfolio_optimization/blob/main/Max%20Sharpe%20Ratio%20Portfolio%20Optimization%20for%20Stocks%20Using%20PyPortfolioOpt.ipynb">Optimize portfolio for max Sharpe ratio plot it out with efficient frontier curve</a></li></ul><h2 id="f5d8">Contacts</h2><ul><li><a href="https://newdigitals.org/">Website</a></li><li><a href="https://github.com/alva922">GitHub</a></li><li><a href="https://twitter.com/alzapress">X/Twitter</a></li><li><a href="https://nl.pinterest.com/alexzap922/">Pinterest</a></li><li><a href="https://mastodon.social/@alexzap">Mastodon</a></li><li><a href="https://alva922.tumblr.com/">Tumblr</a></li></ul></article></body>

Building an Optimal Risky Markowitz Portfolio with 4 Major Tech Stocks: min Volatility vs max Sharpe Ratio

Photo by John Moeses Bauan on Unsplash
  • The objective of this post is to build an optimal risky portfolio for 4 major tech companies: ‘AAPL’, ‘NVDA’, ‘META’, ‘AMZN’.
  • Method: plotting the Markowitz efficient frontier and comparing min volatility vs max Sharpe ratio portfolios under the umbrella of Modern Portfolio Theory (MPT).
  • According to the MPT, we must be compensated for a higher level of risk through higher expected returns. MPT employs the core idea of diversification — owning a portfolio of assets from different classes is less risky than holding a portfolio of similar assets.
  • Let’s get started by importing libraries and reading the input stock data from Yahoo
# Loading Packages
import numpy as np
import pandas as pd
from pandas_datareader import data
import matplotlib.pyplot as plt
import yfinance as yf
%matplotlib inline

# Importing data


tickers = ['AAPL', 'NVDA', 'META', 'AMZN']

oh = yf.download(tickers, period="max")
df = oh["Adj Close"]
df.tail()

Ticker     AAPL       AMZN       META       NVDA
Date    
2024-05-06 181.463882 188.699997 465.679993 921.400024
2024-05-07 182.152924 188.759995 468.239990 905.539978
2024-05-08 182.492477 188.000000 472.600006 904.119995
2024-05-09 184.320007 189.500000 475.420013 887.469971
2024-05-10 183.050003 187.479996 476.200012 898.780029
def plot_adj(df,title,stocks,y=0):
        ax = df[stocks].plot(title=title, figsize=(12,6), ax=None)
        ax.set_xlabel("Date")
        ax.set_ylabel("Stock Price")
        ax.axhline(y=y,color='black')
        ax.legend(stocks, loc='upper left')
        plt.grid()
        plt.show()

# View the plot of Adjusted close
plot_adj(df,"Adjusted Close Stock Prices",tickers)
Adjusted Close Stock Prices
  • Examining the descriptive statistics of stock prices
df.describe().T

      count mean       std        min      25%      50%      75%       max
Ticker        
AAPL 10944.0 20.593375 43.762683 0.037900 0.241563 0.426029 16.980130 197.589523
AMZN 6792.0 36.161682 52.250898 0.069792 2.065125 8.263750 49.150375 189.500000
META 3014.0 166.608252 103.596637 17.711208 82.095396 159.540726 213.525955 527.340027
NVDA 6367.0 52.385019 124.508386 0.312934 2.527168 4.224035 40.157242 950.020020
  • Calculating and plotting the covariance matrix of log daily changes
cov_matrix = df.pct_change().apply(lambda x: np.log(1+x)).cov()
cov_matrix
Ticker AAPL    AMZN     META     NVDA
Ticker    
AAPL 0.000804 0.000306 0.000194 0.000391
AMZN 0.000306 0.001231 0.000261 0.000413
META 0.000194 0.000261 0.000647 0.000301
NVDA 0.000391 0.000413 0.000301 0.001423

import seaborn as sns
ax = sns.heatmap(cov_matrix, annot=True)
Covariance matrix of log daily changes
  • Calculating and plotting the correlation matrix of log daily changes
corr_matrix = df.pct_change().apply(lambda x: np.log(1+x)).corr()
corr_matrix
Ticker AAPL    AMZN    META      NVDA
Ticker    
AAPL 1.000000 0.321586 0.428500 0.398202
AMZN 0.321586 1.000000 0.507065 0.335418
META 0.428500 0.507065 1.000000 0.426401
NVDA 0.398202 0.335418 0.426401 1.000000

import seaborn as sns
ax = sns.heatmap(corr_matrix, annot=True)
Correlation matrix of log daily changes
  • Calculating the randomly weighted portfolio’s variance
w = {'AAPL': 0.1, 'NVDA': 0.4, 'META': 0.3, 'AMZN': 0.2}
port_var = cov_matrix.mul(w, axis=0).mul(w, axis=1).sum().sum()
port_var
0.0005679211473087001
  • Calculating the yearly returns for individual stocks
ind_er = df.resample('Y').last().pct_change().mean()
ind_er
Ticker
AAPL    0.333813
AMZN    0.688729
META    0.414315
NVDA    0.632646
dtype: float64
  • Calculating the portfolio return
w = [0.1, 0.4, 0.3, 0.2]
port_er = (w*ind_er).sum()
port_er
0.5596969011447753
  • Calculating the annual std
ann_sd = df.pct_change().apply(lambda x: np.log(1+x)).std().apply(lambda x: x*np.sqrt(250))
ann_sd
Ticker
AAPL    0.448354
AMZN    0.554643
META    0.402217
NVDA    0.596450
dtype: float64
  • Creating a table for visualizing returns and volatility of assets
assets = pd.concat([ind_er, ann_sd], axis=1) 
assets.columns = ['Returns', 'Volatility']
assets

Returns Volatility
Ticker  
AAPL 0.333813 0.448354
AMZN 0.688729 0.554643
META 0.414315 0.402217
NVDA 0.632646 0.596450
  • Plotting the MPT efficient frontier with 10k random portfolios
p_ret = [] # Define an empty array for portfolio returns
p_vol = [] # Define an empty array for portfolio volatility
p_weights = [] # Define an empty array for asset weights

num_assets = len(df.columns)
num_portfolios = 10000
for portfolio in range(num_portfolios):
    weights = np.random.random(num_assets)
    weights = weights/np.sum(weights)
    p_weights.append(weights)
    returns = np.dot(weights, ind_er) # Returns are the product of individual expected returns of asset and its 
                                      # weights 
    p_ret.append(returns)
    var = cov_matrix.mul(weights, axis=0).mul(weights, axis=1).sum().sum()# Portfolio Variance
    sd = np.sqrt(var) # Daily standard deviation
    ann_sd = sd*np.sqrt(250) # Annual standard deviation = volatility
    p_vol.append(ann_sd)

data = {'Returns':p_ret, 'Volatility':p_vol}

for counter, symbol in enumerate(df.columns.tolist()):
    #print(counter, symbol)
    data[symbol+' weight'] = [w[counter] for w in p_weights]

portfolios  = pd.DataFrame(data)
portfolios.head() # Dataframe of the 10000 portfolios created

Returns Volatility AAPL weight AMZN weight META weight NVDA weight
0 0.496109 0.356620 0.377360 0.383954 0.207498 0.031189
1 0.484475 0.335763 0.326887 0.267905 0.300058 0.105150
2 0.517491 0.352926 0.279398 0.261404 0.212166 0.247032
3 0.481427 0.352069 0.220658 0.054611 0.404624 0.320107
4 0.508543 0.349808 0.323731 0.324500 0.208675 0.143094

# Plot efficient frontier
portfolios.plot.scatter(x='Volatility', y='Returns', marker='o', s=10, alpha=0.3, grid=True, figsize=[10,10])
MPT efficient frontier with 10k random portfolios
  • The Min Volatility portfolio is as follows:
min_vol_port = portfolios.iloc[portfolios['Volatility'].idxmin()]
# idxmin() gives us the minimum value in the column specified.                               
min_vol_port
Returns        0.445058
Volatility     0.324898
AAPL weight    0.327430
AMZN weight    0.145461
META weight    0.448399
NVDA weight    0.078710
Name: 7891, dtype: float64
  • Plotting the Min Volatility portfolio
plt.subplots(figsize=[10,10])
plt.scatter(portfolios['Volatility'], portfolios['Returns'],marker='o', s=10, alpha=0.3)
plt.scatter(min_vol_port[1], min_vol_port[0], color='r', marker='*', s=500)
plt.grid()
plt.xlabel('Volatility')
plt.ylabel('Return')
Min Volatility portfolio vs MPT efficient frontier
  • Plotting the max Sharpe ratio portfolio with risk factor of 0.02
# Finding the optimal portfolio
rf = 0.02 # risk factor
optimal_risky_port = portfolios.iloc[((portfolios['Returns']-rf)/portfolios['Volatility']).idxmax()]
optimal_risky_port

Returns        0.569114
Volatility     0.371583
AAPL weight    0.050555
AMZN weight    0.386680
META weight    0.321123
NVDA weight    0.241643
Name: 4683, dtype: float64

# Plotting optimal portfolio
plt.subplots(figsize=(10, 10))
plt.scatter(portfolios['Volatility'], portfolios['Returns'],marker='o', s=10, alpha=0.3)
plt.scatter(min_vol_port[1], min_vol_port[0], color='r', marker='*', s=500)
plt.scatter(optimal_risky_port[1], optimal_risky_port[0], color='g', marker='*', s=500)
plt.grid()
plt.xlabel('Volatility')
plt.ylabel('Return')
max Sharpe ratio portfolio with risk factor of 0.02, min Volatility portfolio and MPT efficient frontier.
  • Plotting the max Sharpe ratio portfolio with risk factor of 0.2
# Same as above but with rf = 0.2 

rf = 0.2 # risk factor
optimal_risky_port = portfolios.iloc[((portfolios['Returns']-rf)/portfolios['Volatility']).idxmax()]
optimal_risky_port
...
max Sharpe ratio portfolio with risk factor of 0.2, min Volatility portfolio and MPT efficient frontier.
  • Notice that while the difference in risk between min Volatility portfolio and max Sharpe portfolio is just ca. 7% (with risk factor of 0.2), the difference in returns is a whopping 15%.

Conclusions

  • In this post, we have demonstrated how the Markowitz portfolio optimization works in practice.
  • We have attempted to balance risk and return of the 4 major tech stocks (tickers): ‘AAPL’, ‘NVDA’, ‘META’, ‘AMZN’.
  • The correlation coefficient of these stocks is within the acceptable range [0.32, 0.51].
  • The annual std (vol) shows that vol(META)
  • We have found the following optimal risky portfolio with risk factor of 0.02
Returns        0.569114
Volatility     0.371583
AAPL weight    0.050555
AMZN weight    0.386680
META weight    0.321123
NVDA weight    0.241643
  • Results confirm the key de-risking ability of Markowitz MPT to incorporate the asset correlation matrix into the portfolio optimization process.
  • Our future work will focus on the shortcomings of MPT:
  • The model’s underlying assumptions are predicated on normally functioning markets. In highly volatile and unpredictable markets, the model may lose relevance.
  • MPT is mean-variance theory. Risk by variance works when returns are normally distributed. Assets that do not follow a normal distribution will not work with the Markowitz Model.
  • To mitigate these limitations, we‘ll consider using alternative portfolio optimization methods, such as Black-Litterman model or Monte Carlo simulation, and incorporate qualitative factors such as market trends and geopolitical risks into our investment decisions.

Explore More

References

Contacts

Python
Algorithmic Trading
Portfolio Management
Investment
Tech
Recommended from ReadMedium