Portfolio Analysis

Published:

We create a portfolio of stocks from American markets, analyze their performance and try to acess the risk in future.

Building the porfolio

We will build a tech dominant portfolio to analyze including companies like Apple, Microsoft, Google etc.

import yfinance as yf
import matplotlib.pyplot as plt
import numpy as np 
import pandas as pd 


# List of stock symbols
tech_stocks = ['AAPL', 'MSFT', 'AMZN', 'GOOGL', 'META']

# Set the date range for the historical data
start_date = '2020-08-01'
end_date = '2023-08-01'

# Download historical stock data for each stock

stocks = yf.download(tech_stocks, start_date, end_date)['Close']


[*********************100%%**********************]  5 of 5 completed

Portfolio construction

To assign the weight of each of the company stock we calculate the market capitalisation. To get the market value we need to know the last price of the security traded and the number of shares outstanding. yfinance gives the market cap directly.

For this, it is important to have a benchmark that can be used as a reference for the securities.

totalmp = 0
mcap=[]
for stock in tech_stocks:
    # Create a Ticker object
    # Create a Ticker object
    ticker = yf.Ticker(stock)

    # Fetch company information
    company_info = ticker.info

    # Get the market capitalization
    market_cap = company_info.get('marketCap', None)
    mcap.append(market_cap)
    totalmp += market_cap

weights = [mc/totalmp for mc in mcap]
print(weights)
[0.3174813752091255, 0.2629812984283075, 0.1530503049106324, 0.18437709035962263, 0.08210993109231199]

Portfolio standard deviation

\[\sigma_{port} = \sqrt{w_{t}\Sigma w}\]

\(\Sigma\) is covariance matrix and w’s are weights

# stock return
stocks_return  =  (stocks/stocks.shift(1))
# covariance of stocks
cov_matrix = stocks_return.cov()

# Annual portfolio covariance
cov_annual = cov_matrix * 252

# portfolio standard deviation

port_std = np.sqrt(np.dot(np.array(weights).T, np.dot(cov_annual, np.array(weights))))

print(f"portfolio standard deviation is  {port_std*100:0.2f}%")
portfolio standard deviation is  30.35%

Portfolio returns


# get daily returns for each stock
stocks_return  =  stocks.pct_change()
# weighted portfolio return

weighted_returns_portfolio  =  stocks_return.mul(weights,axis  =   1  )

# portfolio return
stocks_return[ 'Portfolio'  ]  =  weighted_returns_portfolio.sum(axis  =   1  )

stocks_return.head()
AAPLAMZNGOOGLMETAMSFTPortfolio
Date
2020-08-03NaNNaNNaNNaNNaN0.000000
2020-08-040.0066780.008657-0.006380-0.008454-0.0150090.000629
2020-08-050.0036250.0210910.003930-0.002842-0.0016410.006640
2020-08-060.0348890.0062310.0174840.0648680.0160140.028666
2020-08-07-0.024495-0.017842-0.0043720.011912-0.017888-0.012410
# cummulative returns 

cum_returns_porfolio  =  ((1 + stocks_return[ 'Portfolio'  ]).cumprod()-1)

fig,ax = plt.subplots(1,1,figsize  =  (19,8))
cum_returns_porfolio.plot(ax=ax)

ax.set_title('Cumulative Returns')
ax.set_ylabel('log Cumulative Returns of the Portfolio')
ax.set_xlabel('Date')
plt.show()

png

Capital asset Pricing model

  • Its major assumptions are that the offer of financial assets is equal to the demand of financial assets.

  • Only deals with systematic risk (market risk), that can’t be reduced.

Excess Return = Return − Risk Free Return

\[E(R_p) − RF = \beta_p(E(R_m) − RF)\]
  • \(E(R_p) − RF\) = The excess in the expected return of Portfolio P
  • \(E(R_m) − RF\) = The excess expected return of market portfolio
  • RF = Risk-Free Return
  • \(\beta_p\) = Beta of the portfolio

Currently US3Y yeild is 4.54% and inflation is 3.3%

# real risk free rate = RFR - infaltion
infaltion = 3.3
RFR  = 4.54
real_rf = RFR - infaltion

stocks_return[ 'RF Rate'  ]  =  real_rf/100.0

#  Excess return
stocks_return['excess']  =  stocks_return['Portfolio'] - stocks_return[ 'RF Rate'  ]

Beta

The measurement of the systematic risk is through the beta, which is a degree of sensitivity that includes the variation of an asset compared with an index that is used as a benchmark.

\[\beta_p = \frac{cov(R_{p},R_{m})}{\sigma_m}\]
# To calculate beta

stocks_return['Market']  =  yf.download( 'SPY',start_date,end_date)['Close']

stocks_return['Market']=stocks_return['Market'].pct_change()

stocks_return.head().dropna()

[*********************100%%**********************]  1 of 1 completed
AAPLAMZNGOOGLMETAMSFTPortfolioRF RateexcessMarket
Date
2020-08-040.0066780.008657-0.006380-0.008454-0.0150090.0006290.0124-0.0117710.003863
2020-08-050.0036250.0210910.003930-0.002842-0.0016410.0066400.0124-0.0057600.006211
2020-08-060.0348890.0062310.0174840.0648680.0160140.0286660.01240.0162660.006685
2020-08-07-0.024495-0.017842-0.0043720.011912-0.017888-0.0124100.0124-0.0248100.000718
# Exess return of the market

stocks_return['excess market']  =  stocks_return['Market'] - stocks_return['RF Rate']
stocks_return.head().dropna()
AAPLAMZNGOOGLMETAMSFTPortfolioRF RateexcessMarketexcess market
Date
2020-08-040.0066780.008657-0.006380-0.008454-0.0150090.0006290.0124-0.0117710.003863-0.008537
2020-08-050.0036250.0210910.003930-0.002842-0.0016410.0066400.0124-0.0057600.006211-0.006189
2020-08-060.0348890.0062310.0174840.0648680.0160140.0286660.01240.0162660.006685-0.005715
2020-08-07-0.024495-0.017842-0.0043720.011912-0.017888-0.0124100.0124-0.0248100.000718-0.011682
# covariance matrix 

covariance_matrix  =  stocks_return[[ 'excess', 'excess market'  ]].cov()
covariance_matrix
excessexcess market
excess0.0003650.000187
excess market0.0001870.000132
# covariane coeeficient 
covariance_coefficient  =  covariance_matrix.iloc[0, 1]

# variance of market
variance_coefficient  =  stocks_return['excess market'].var()

# beta of the portfolio
beta  =  covariance_coefficient / variance_coefficient

beta
1.416897406000382

The beta demonstrates that the portfolio is more volatile than the market. The portfolio is 41% more volatile than the S&P 500. For every 1% of movement in the market there will be a 1.41% of rise or fall in the portfolio.

Sharpe Ratio

Higher ratio is better it is considered since the denominator is standard deviation or risk. It is used when comparing peers, for example in an exchange-traded fund (ETF)

\[Sharpe \ Ratio = \frac{R_p-R_f}{ \sigma_p}\]
  • \(R_p\) = returns of the portfolio
  • \(R_f\) = risk-free rate
  • \(\sigma_p\) = standard deviation of the portfolio excess returns.
# plot returns of porfolio and market 

CumulativeReturns  =  ((1 + stocks_return[[ 'Portfolio','Market'  ]]).cumprod()- 1  )
CumulativeReturns.plot(figsize   =  ( 16,4  ))
_  =  plt.ylabel( 'Returns'  )
_  =  plt.title( 'Comparison - Portfolio vs. Benchmark'  )
_  =  plt.xlabel( 'Date'  )
plt.show()

png

# plot scatter plot for correlation
plt.scatter(stocks_return[ 'Portfolio'  ],stocks_return[ 'Market'],alpha  =   0.80  );
_  =  plt.ylabel( 'Returns'  )
_  =  plt.title( 'Correlation - Portfolio vs. Benchmark'  )
_  =  plt.xlabel( 'Date'  )

png

# get the two columns in a dataframe
portfolio_benchmark  =  stocks_return[[ 'Portfolio','Market']].dropna()

# get correlation


portfolio_benchmark.corr()
PortfolioMarket
Portfolio1.0000000.851744
Market0.8517441.000000
# get sharpe ratio

sharpe_ratio  =  ((stocks_return[ 'Portfolio'  ].mean()  -  stocks_return[ 'RF Rate'  ].mean()))/stocks_return['Portfolio'  ].std()

print("Sharpe ratio is ",sharpe_ratio)
print("Sharpe ratio annual ",sharpe_ratio*np.sqrt(252))

Sharpe ratio is  -0.6131452933469881
Sharpe ratio annual  -9.73337978247526

SR is negative, indicates that mean return of the portfolio is smaller than the risk-free rate. Therefore the portfolio is not effective

Traynor ratio

Higher TR is a result of the portfolio management. When analyzing the Traynor Ratio, if it is negative, the portfolio has underperformed the risk-free rate.

\[Traynor Ratio = \frac{R_p - R_f}{\beta_p}\]
  • \(R_p\) = returns of the portfolio
  • \(R_f\) = risk-free rate
  • \(\beta_p\) = Beta of the portfolio
# covariance
covariance  =  stocks_return.cov() * 252

# covariance between market and portfolio
covariance_market_portfolio  =  covariance.at['Market','Portfolio']

# variance of market
market_variance  =  stocks_return[ 'Market'].var() * 252

# beta of the portfolio
portfolio_beta  =  covariance_market / market_variance

traynor_ratio  =  ((stocks_return['Portfolio'].mean() - stocks_return['RF Rate'].mean()))/portfolio_beta

traynor_ratio

-0.008268904314486599

TR is negative, which means that the port- folio is not performing better than the risk-free rate. Main difference between the Sharpe and the Traynor ratio is that it compares with the beta and not the volatility

Jensen’s measure

Measuring the relationship between the return of the portfolio in comparison with another portfolio return with the same risk, same reference market and under the same parameters.

\[\alpha = R_p - (R_f + \beta_p(R_m-R_p))\]
# get covariance of between market and portfolio
annual_return = stocks_return
covariance = annual_return[[*tech_stocks,'Portfolio','Market']].cov()*252

covariance
AAPLMSFTAMZNGOOGLMETAPortfolioMarket
AAPL0.0931530.0643480.0747470.0640860.0810800.0792730.044044
MSFT0.0643480.0844400.0768800.0703690.0825890.0735780.042132
AMZN0.0747470.0768800.1427060.0810070.1100470.1002610.049349
GOOGL0.0640860.0703690.0810070.1024560.1002170.0815860.044204
META0.0810800.0825890.1100470.1002170.2327360.1197130.054172
Portfolio0.0792730.0735780.1002610.0815860.1197130.0920120.047174
Market0.0440440.0421320.0493490.0442040.0541720.0471740.033294
# covariance market

covariance_market = covariance.at['Market','Portfolio']

# variance of market
market_variance  =  stocks_return[ 'Market'  ].var() * 252

# beta of the portfolio
portfolio_beta  =  covariance_market/market_variance

# portfolio return
portfolio_return  =  stocks_return['Portfolio'  ].mean()

# RFR
risk_free_rate  =  stocks_return['RF Rate'].mean()

# alpha
alpha  =  portfolio_return  - (risk_free_rate + portfolio_beta*(portfolio_return - risk_free_rate))

alpha
0.004884448833019478