The web content outlines a comprehensive analysis of portfolio optimization strategies for four major tech stocks—Apple (AAPL), Meta Platforms (META), Amazon (AMZN), and NVIDIA (NVDA)—using key financial models such as Markowitz's Efficient Frontier, Sharpe Ratio, Value at Risk (VaR), and the Capital Asset Pricing Model (CAPM) to maximize risk-adjusted returns.
Abstract
The article delves into the application of stochastic optimization techniques to construct investment portfolios for four leading technology companies: Apple, Meta Platforms, Amazon, and NVIDIA. It emphasizes the importance of the Return/Risk Ratio (RRR) and employs several financial models to achieve the maximum possible RRR. The Markowitz Efficient Frontier, Monte Carlo simulations,
Portfolio Optimization of 4 Major Techs: Markowitz, Sharpe, VaR & CAPM
This article outlines a stochastic approach to investment portfolio optimization using several key KPIs and underlying models for maximizing the ratio RRR, where RRR=Return/Risk.
Notably, Apple has achieved a remarkable feat by surpassing a $3 trillion market cap, making it the sole company in recorded history to reach this milestone. Read more here.
If you’re considering entering the eCommerce market, it’s important to note that AMZN currently commands 37.8% of the US eCommerce market share, demonstrating its significant dominance in the industry.
Recent revenue growth reports suggest that META is still a major player in the digital advertising market, and that it is well-positioned for future growth.
In 2023, NVDA saw a remarkable surge in its share price by 228%, reaching a market capitalization of a staggering $1.19 trillion. This impressive financial performance is reflective of the company’s strategic shift from a gaming-focused business to designing chips for self-driving and driver-assisting vehicle technologies, cloud computing, and data centers. In fact, the data segment has now become the largest revenue generator for the company. Read full story.
import os
os.chdir('YOURPATH') # Set working directory
os. getcwd()
Basic Imports
import pandas as pd
import numpy as np
from functools import reduce
import yfinance as yfin
from pandas_datareader import data as pdr
import yfinance as yf
import datetime
import random
import scipy.optimize as sco
from scipy import stats
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
%matplotlib inline
mpl.style.use('ggplot')
figsize = (15, 8)
yf.pdr_override()
Input Stock Data Analysis
Downloading tech stock data and exporting as CSV
tickers = ("SPY", "AAPL", "META", "NVDA", "AMZN")
start = "2023-01-03"
end = '2024-04-05'
fin_data = yfin.download(tickers, start, end) #download yahoo finance data for specific dates
fin_data.to_csv('fin_data.csv') #convert data to csv#check the dimensions of the data
fin_data.shape
(315, 30)
#view the last 5 rows of the data
fin_data.tail()
Price Adj Close Close ... Open Volume
Ticker AAPL AMZN META NVDA SPY AAPL AMZN META NVDA SPY ... AAPL AMZN META NVDA SPY AAPL AMZN META NVDA SPY
Date
2024-03-28171.479996180.380005485.579987903.559998523.070007171.479996180.380005485.579987903.559998523.070007 ... 171.750000180.169998492.839996900.000000523.21002265672700380516001521280043521200962949002024-04-01 170.029999180.970001491.350006903.630005522.159973170.029999180.970001491.350006903.630005522.159973 ... 171.190002180.789993487.200012902.989990523.8300174624050029174500924700045244100624775002024-04-02 168.839996180.690002497.369995894.520020518.840027168.839996180.690002497.369995894.520020518.840027 ... 169.080002179.070007485.100006884.479980518.23999049329500326115001108100043306400742303002024-04-03 169.649994182.410004506.739990889.640015519.409973169.649994182.410004506.739990889.640015519.409973 ... 168.789993179.899994498.929993884.840027517.71997147602100309598001204710036845000590368002024-04-04 168.820007180.000000510.920013859.049988513.070007168.820007180.000000510.920013859.049988513.070007 ... 170.289993184.199997516.510010904.059998523.52002053289969414745452623481842761958962621415 rows × 30 columns
Check if there are missing values
fin_data.isnull().sum()
Descriptive statistics of Price = Adj Close
fin_data[['Adj Close']].describe().T
count mean std min25% 50% 75% max
Ticker
AAPL 315.0173.77133016.368597124.166641166.111610176.602585186.443153197.857529
AMZN 315.0130.95146025.71501083.120003105.065002130.220001147.029999182.410004
META 315.0299.96505898.044027124.607788226.894264299.352386336.288208512.190002
NVDA 315.0441.782295189.310871142.580032277.540955437.434967488.005554950.020020
SPY 315.0437.96826438.259185372.542725406.521133433.460419457.810654523.169983
Looking at the Maximum Closing Value for our stocks
defmax_close(stocks,df):
""" This calculates and returns the maximum closing value of a specific stock"""return df['Close'][stocks].max()
deftest_max():
""" This tests the max_close function"""for stocks in ["SPY", "AAPL", "META", "NVDA", "AMZN"]:
print("Maxiumum Closing Value for {} is {}".format(stocks, max_close(stocks,fin_data)))
test_max()
Maxiumum Closing Value for SPY is523.1699829101562
Maxiumum Closing Value for AAPL is198.11000061035156
Maxiumum Closing Value for META is512.1900024414062
Maxiumum Closing Value for NVDA is950.02001953125
Maxiumum Closing Value for AMZN is182.41000366210938
Examining the Mean Volume
defmean_vol(stocks,df):
""" This calculates and returns the minimum volume of a specific stock"""return df['Volume'][stocks].mean()
deftest_mean():
for stocks in ["SPY", "AAPL", "META", "NVDA", "AMZN"]:
print("Mean Volume for {} is {}".format(stocks, mean_vol(stocks,fin_data)))
test_mean()
Mean Volume for SPY is80445469.01904762
Mean Volume for AAPL is59626502.75873016
Mean Volume for META is22872390.215873014
Mean Volume for NVDA is48560584.62857143
Mean Volume for AMZN is55690328.07936508
Examining the Market vs Investment Monthly Returns
stock_a =['NVDA']
stock_m = ['^GSPC']
CAPM(stock_a,stock_m,start, end)
========================================
Beta from formula: 1.5791
Beta from Linear Regression: 1.5791
Alpha: 0.074
========================================
CAPM Line for NVDA vs ^GSPC Monthly Returns
stock_a =['AAPL']
stock_m = ['^GSPC']
CAPM(stock_a,stock_m,start, end)
========================================
Beta from formula: 0.9823
Beta from Linear Regression: 0.9823
Alpha: -0.004
========================================
CAPM Line for AAPL vs ^GSPC Monthly Returns
stock_a =['META']
stock_m = ['^GSPC']
CAPM(stock_a,stock_m,start, end)
========================================
Beta from formula: 0.8131
Beta from Linear Regression: 0.8131
Alpha: 0.07
========================================
CAPM Line for META vs ^GSPC Monthly Returns
stock_a =['AMZN']
stock_m = ['^GSPC']
CAPM(stock_a,stock_m,start, end)
========================================
Beta from formula: 1.1471
Beta from Linear Regression: 1.1471
Alpha: 0.019
========================================
CAPM Line for AMZN vs ^GSPC Monthly Returns
Creating a bar plot of the Estimated CAPM Alpha Tech Monthly Returns
# creating the dataset
data = {'NVDA':0.074, 'AAPL':-0.004, 'META':0.07,
'AMZN':0.019}
xvalues = list(data.keys())
yvalues = list(data.values())
fig = plt.figure(figsize = (10, 5))
# creating the bar plot
plt.bar(xvalues, yvalues, color ='red',
width = 0.4)
plt.xlabel("Stock")
plt.ylabel("Alpha")
plt.title("CAPM Alpha Tech Monthly Returns")
plt.show()
CAPM Alpha Tech Monthly Returns
Creating a bar plot of the Estimated CAPM Beta Tech Monthly Returns
# creating the dataset
data = {'NVDA':1.5791, 'AAPL':0.9823, 'META':0.8131,
'AMZN':1.1471}
xvalues = list(data.keys())
yvalues = list(data.values())
fig = plt.figure(figsize = (10, 5))
# creating the bar plot
plt.bar(xvalues, yvalues, color ='blue',
width = 0.4)
plt.xlabel("Stock")
plt.ylabel("Beta")
plt.title("CAPM Beta Tech Monthly Returns")
plt.show()
CAPM Beta Tech Monthly Returns
Max Sharpe Efficient Frontier
Let’s construct the Markowitz portfolio optimization model using the stochastic simulation algorithm. Our objective is to provide the highest Sharpe Ratio, which is a metric that compares the amount of return versus the amount of risk, based on historical data. Return is based on CAGR and risk is based on volatility. The portfolio is well suited for risk adverse investors with moderate growth expectations.
Creating max Sharpe and min Volatility Portfolios using simulated random portfolios
defcalc_portfolio_perf(weights, mean_returns, cov, rf):
portfolio_return = np.sum(mean_returns * weights) * 252
portfolio_std = np.sqrt(np.dot(weights.T, np.dot(cov, weights))) * np.sqrt(252)
sharpe_ratio = (portfolio_return - rf) / portfolio_std
return portfolio_return, portfolio_std, sharpe_ratio
defsimulate_random_portfolios(num_portfolios, mean_returns, cov, rf):
results_matrix = np.zeros((len(mean_returns)+3, num_portfolios))
for i inrange(num_portfolios):
weights = np.random.random(len(mean_returns))
weights /= np.sum(weights)
portfolio_return, portfolio_std, sharpe_ratio = calc_portfolio_perf(weights, mean_returns, cov, rf)
results_matrix[0,i] = portfolio_return
results_matrix[1,i] = portfolio_std
results_matrix[2,i] = sharpe_ratio
#iterate through the weight vector and add data to results arrayfor j inrange(len(weights)):
results_matrix[j+3,i] = weights[j]
results_df = pd.DataFrame(results_matrix.T,columns=['ret','stdev','sharpe'] + [ticker for ticker in tickers])
return results_df
mean_returns = df.pct_change().mean()
cov = df.pct_change().cov()
num_portfolios = 100000
rf = 0.0
results_frame = simulate_random_portfolios(num_portfolios, mean_returns, cov, rf)
#locate position of portfolio with highest Sharpe Ratio
max_sharpe_port = results_frame.iloc[results_frame['sharpe'].idxmax()]
#locate positon of portfolio with minimum standard deviation
min_vol_port = results_frame.iloc[results_frame['stdev'].idxmin()]
plt.rc('font', size=14)
#create scatter plot coloured by Sharpe Ratio
plt.subplots(figsize=(15,10))
plt.scatter(results_frame.stdev,results_frame.ret,c=results_frame.sharpe,cmap='RdYlBu')
plt.xlabel('Standard Deviation')
plt.ylabel('Returns')
plt.colorbar()
#plot red star to highlight position of portfolio with highest Sharpe Ratio
plt.scatter(max_sharpe_port[1],max_sharpe_port[0],marker=(5,1,0),color='r',s=500)
#plot green star to highlight position of minimum variance portfolio
plt.scatter(min_vol_port[1],min_vol_port[0],marker=(5,1,0),color='g',s=500)
plt.legend(["Sharpe Ratio Value","max Sharpe Portfolio" , "min Volatility Portfolio"],fontsize="18", loc ="upper left")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
plt.show()
Simulated random portfolios, max Sharpe & min Volatility Portfolios
Comparing the 2 stock weighting tables that made up those two portfolios, along with the annualized return (ret), STDEV and Sharpe ratio, viz.
max_sharpe_port.to_frame().T
ret stdev sharpe SPY AAPL META NVDA AMZN
258251.3381560.368153.6348070.0059070.0280120.4704470.4800560.015578
min_vol_port.to_frame().T
ret stdev sharpe SPY AAPL META NVDA AMZN
575820.311990.1407412.2167720.7107720.2040890.0129640.0099550.06222
Min VaR Efficient Frontier
Identifying the portfolio weights that minimize the Value at Risk (VaR)
defcalc_portfolio_perf_VaR(weights, mean_returns, cov, alpha, days):
portfolio_return = np.sum(mean_returns * weights) * days
portfolio_std = np.sqrt(np.dot(weights.T, np.dot(cov, weights))) * np.sqrt(days)
portfolio_var = abs(portfolio_return - (portfolio_std * stats.norm.ppf(1 - alpha)))
return portfolio_return, portfolio_std, portfolio_var
defsimulate_random_portfolios_VaR(num_portfolios, mean_returns, cov, alpha, days):
results_matrix = np.zeros((len(mean_returns)+3, num_portfolios))
for i inrange(num_portfolios):
weights = np.random.random(len(mean_returns))
weights /= np.sum(weights)
portfolio_return, portfolio_std, portfolio_VaR = calc_portfolio_perf_VaR(weights, mean_returns, cov, alpha, days)
results_matrix[0,i] = portfolio_return
results_matrix[1,i] = portfolio_std
results_matrix[2,i] = portfolio_VaR
#iterate through the weight vector and add data to results arrayfor j inrange(len(weights)):
results_matrix[j+3,i] = weights[j]
results_df = pd.DataFrame(results_matrix.T,columns=['ret','stdev','VaR'] + [ticker for ticker in tickers])
return results_df
mean_returns = df.pct_change().mean()
cov = df.pct_change().cov()
num_portfolios = 100000
rf = 0.0
days = 252
alpha = 0.05
results_frame = simulate_random_portfolios_VaR(num_portfolios, mean_returns, cov, alpha, days)
#locate positon of portfolio with minimum VaR
min_VaR_port = results_frame.iloc[results_frame['VaR'].idxmin()]
#create scatter plot coloured by VaR
plt.subplots(figsize=(15,10))
plt.scatter(results_frame.VaR,results_frame.ret,c=results_frame.VaR,cmap='RdYlBu')
plt.xlabel('Value at Risk')
plt.ylabel('Returns')
plt.colorbar()
#plot red star to highlight position of minimum VaR portfolio
plt.scatter(min_VaR_port[2],min_VaR_port[0],marker=(5,1,0),color='g',s=500)
plt.legend(["VaR Value","min VaR Portfolio"] ,fontsize="18", loc ="upper left")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
plt.show()
Simulated random portfolios & min VaR Portfolio
The stock weighting table that made up the above portfolio is given by
min_VaR_port.to_frame().T
ret stdev VaR SPY AAPL META NVDA AMZN
205370.3142270.1829320.013330.1596290.7791880.0172460.0168460.027091
Finally, converting the horizontal axis from VaR to STDEV (cf. here)
#locate positon of portfolio with minimum VaR
min_VaR_port = results_frame.iloc[results_frame['VaR'].idxmin()]
#create scatter plot coloured by VaR
plt.subplots(figsize=(15,10))
plt.scatter(results_frame.stdev,results_frame.ret,c=results_frame.VaR,cmap='RdYlBu')
plt.xlabel('Standard Deviation')
plt.ylabel('Returns')
plt.colorbar()
#plot red star to highlight position of minimum VaR portfolio
plt.scatter(min_VaR_port[1],min_VaR_port[0],marker=(5,1,0),color='g',s=500)
plt.legend(["STD Value","min VaR Portfolio"] ,fontsize="18", loc ="upper left")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
plt.show()
Simulated random portfolios & min VaR Portfolio in the Returns — STDEV plane
Conclusions
In this post, we have compared several popular stochastic portfolio optimization techniques that maximize returns while minimizing risk, excluding product costs and related fees.
It turns out that max Sharpe is the best tech portfolio in terms of the ret-to-stdev ratio
minVaR minVol maxSharpe
ret % 3131134
stdev % 181437
ratio 1.722.213.62
We have compared the CAPM Lines of Monthly Returns for our stocks against the benchmark (^GSPC).
According to the estimated CAPM Alpha (Beta), a positive value of 0.074 (0.81) is good for NVDA (META) as compared to other 3 tech stocks.
Results confirm that NVDA and META remain our tech growth stocks in focus today, indicating bullish sentiments.