4  Portfolio Management

This is a book containing all class notes.

This is part of my Portfolio Management course.

4.1 Diversification

In this graph, we observe the benefits of diversification. The pink line computes the standard deviation of Ibov from 2010 to 2022, while the light green line computes the average of the standard deviation of the individual stocks that compose Ibov weighted by monthly volume. We can see that the portfolio has a lower standard deviation than the weighted average.

library(PerformanceAnalytics)
library(yfR)
library(ggplot2)
library(dplyr)
library(tidyr)
library(ggthemes)
library(tidyquant)
library(roll)
start <-'2010-01-01' 
end   <-'2022-01-01'
data <- yf_collection_get("IBOV", 
                         first_date = start,
                         last_date = end,
                         freq_data = "daily",)
data<-data[complete.cases(data),] 
data$month <- format(as.Date(data$ref_date, format="%y/%m/%d"),"%y/%m")
sd  <-    data %>%                          
            group_by(ticker,month) %>%
            summarise_at(vars(ret_adjusted_prices),list(sd = sd)) %>% 
            as.data.frame()
vol  <-    data %>%                          
            group_by(ticker,month) %>%
            summarise_at(vars(volume),list(vol_sum = sum)) %>% 
            as.data.frame()
vol2  <-    data %>%                          
            group_by(month) %>%
            summarise_at(vars(volume),
                         list(vol_tot = sum)) %>% 
            as.data.frame()
data2 <- merge(sd ,vol, by=c("ticker","month"))
data2 <- merge(data2 ,vol2, by=c("month"))
data2$w <- data2$vol_sum/data2$vol_tot
data2$sdw <- data2$sd * data2$w
data3  <-   data2 %>%                          
            group_by(month) %>%
            summarise_at(vars(sdw),list(total = sum)) %>% 
            as.data.frame()
data3<-data3[complete.cases(data3),] 
data3$id <-"Weighted"
# Index
ibov <- yf_get(tickers = '^BVSP', 
                         first_date = start,
                         last_date = end,
                         freq_data = "daily",)
ibov<-ibov[complete.cases(ibov),] 
ibov$month <- format(as.Date(ibov$ref_date, format="%y/%m/%d"),"%y/%m")
sd2  <-    ibov %>%                          
            group_by(month, ticker) %>%
            summarise_at(vars(ret_closing_prices) ,
                         list(total = var ) ) %>% 
            as.data.frame()
sd2$total <- sd2$total^0.5
sd2$id <-"ibov"
sd2$ticker <- NULL
data4 <- rbind(data3,sd2)
ggplot(data=data4, aes(x=month, y=total, color = id, group = id)) +
  geom_line() + 
        labs(x = "",
             y='Standard deviation',
             title ="Diversification benefits - Ibov vs. Weighted sd of individual stocks") +   theme_solarized()


4.1.1 Two assets price behavior

Note below that often when the price of a asset drops, the price of the other rises.


Let’s see the same graph using prices in day 1 = $100.


4.1.2 Correlation

Finally, let’s compute the correlation between the returns of these assets.

## [1] 0.5443201

The correlation is 0.544


4.1.3 Two-asset portfolio return

Now, let’s compute the average return of a portfolio with 40% invested in asset 1 and the remaining 60% invested in asset 2.

# Defining weights and calculating portfolio return (daily)
w <- c(0.40, 0.60)
# Creating a df with stocks and weights
w_tbl <- tibble(ticker = stocks,w = w)
# Including the weights in the df prices (which contains the prices)
prices  <- left_join(data ,w_tbl, by = 'ticker')
# calculating the product of return times the portfolio weights for all days (this is necessary to calculate average return)
prices$w_ret <- prices$ret_closing_prices * prices$w
# Creating a dataframe with portfolio returns 
port_ret <- prices %>%
  group_by(ref_date) %>%
  summarise(port_ret = sum(w_ret))
# Creating prices from the vector of returns
port_ret$price_close2 <- cumprod(1+port_ret$port_ret) * 100
# Graph with all returns
port_ret$ticker <- 'Portfolio'
ggplot(stock1, aes(ref_date , price_close2, color = ticker))+
        geom_line() +geom_line(data=stock2) +geom_line(data=port_ret) +
        labs(x = "",
             y='Closing prices', 
             title="Two assets and Portfolio returns, Initial price = 100", 
             subtitle = "Begin 01/01/2010") +   theme_solarized()

Obs: The return of the portfolio is the weighted average of the assets’ returns.

## [1] 0.03216604
## [1] 0.03330732

The average return of asset 1 and asset 2 are, respectively, 0.032 and 0.033.

## [1] 0.03285081

The average return of the portfolio is 0.033. ___

We can compute the average return of the portfolio “by hand”:

[1] 0.03285081

The average return of the portfolio, computed by hand, is 0.033.

Notice below that the difference between the two methods of calculation is zero, as it should be!

[1] 0

4.2 Portfolio risk

4.2.1 Two-asset portfolio risk

However, the standard deviation of the portfolio returns is not the weighted average of the assets’ standard deviation. Remember the equation below:

\[var = (w_1^2 \times \sigma_1^2) + (w_2^2 \times \sigma_2^2) + 2(w_1 \times w_2 \times \sigma_1 \times \sigma_2 \times \rho_{12})\]

Where \(\sigma^2\) is the variance of a asset, \(w\) are the weights, and \(\rho\) is the correlation between the assets.

## [1] 2.116466
## [1] 2.931682
## [1] 2.330659
## [1] 2.605595
## [1] -0.274936

These are the estimates from the previous example:

The standard deviation of asset 1 is 2.116.

The standard deviation of asset 2 is 2.932.

The standard deviation of the portfolio is 2.331.

The weighted average of the assets’ standard deviation is 2.606.

The decrease in the standard deviation due to diversification is -0.275.


4.2.2 Multi-assets portfolio risk

While the expected return of a three-asset portfolio is the weighted average of expected returns, the variance of the portfolio is measured using the equation below:

\[var = (w_1^2 \times \sigma_1^2) + (w_2^2 \times \sigma_2^2) + 2(w_1 \times w_2 \times \sigma_1 \times \sigma_2 \times \rho_{12}) + \\ (w_3^2 \times \sigma_3^2) + 2(w_1 \times w_3 \times \sigma_1 \times \sigma_3 \times \rho_{13}) + 2(w_2 \times w_3 \times \sigma_2 \times \sigma_3 \times \rho_{23})\]

When you have enough stocks (with equal weights), you can compute the variance as follows:

\[Var=\frac{1}{N} \times average\;variance + (1-\frac{1}{N}) \times average \; covariance\]

So, now, let’s compute the variance of multi-assets portfolios, equally weighted using real data from the Brazilian market.

start <-'2015-01-01' 
end   <-'2022-01-01'
# Finding the tickers
data_temp <- yf_collection_get("IBOV", 
                         first_date = start,
                         last_date = end,
                         freq_data = "monthly")
tickers<-unique(data_temp$ticker)
df <- data_temp %>%  select(ref_date)  %>% distinct()
# Now collecting data
for (i in 1:length(tickers)) {
data <- yf_get(tickers[[i]], 
               first_date = start,
               last_date = end,
               freq_data = "monthly")
data <- data[complete.cases(data),] 
data <- data %>%  select(ref_date, ret_closing_prices)
colnames(data) <- c("ref_date",  tickers[[i]]  )
df <- merge(df,data,by="ref_date")
}
# Setup to randomly combine "j" stocks and compute the standard deviation of this combination
final <- data.frame(matrix(NA,nrow = 1000, ncol = length(tickers)))
df$ref_date <- NULL
for (j in 1:length(tickers) ) {
for (i in 1:2000 ) {
df_r <- as.data.frame(df[, sample(  ncol(df) , j )])
cov <- cov(df_r)
# Var mean
var <- as.data.frame(diag(cov))
var_mean <- mean(var$`diag(cov)`)
  #Covariance mean
  if (j == 1) {
  covar_mean <- var_mean
  } else {
  covar <- cov
  diag(covar)=NA 
  covar[upper.tri(covar)] <- NA
  covar<-c(covar)
  covar_mean<- mean(covar,na.rm=TRUE)
  }
# Port Sd.
var_p <- (  (1/ j  * var_mean )   + (1- (1/j ) ) * covar_mean    ) ^ 0.5
final[i,j] <- var_p
}
}
# Computing the average of the "i" portfolios with "j" stocks 
sd   <- as.data.frame(colMeans(final))
sd$N  <- seq_along(sd[,1])
ggplot(sd, aes(y=`colMeans(final)` , x = N)) + geom_line() +
        labs(x = "Number of stocks (equal weight)",
             y='Standard deviation (monthly)', 
             title="Standard deviation a equally weighted portfolios", 
             subtitle = "Begin 01/01/2015 (Brazilian stocks)") +   theme_solarized()


You see, the previous graph contains the average of all possible combinations using N stocks. The inclusion of the next stock is defined by volume. If you don’t take the average of all possible combinations, you get the next graph. Clearly, the drop in risk is not so smooth.

4.3 Correlation extended

See what happens with the portfolio risk when we change the combination of weights of each asset.

The graph below contains the set of feasible portfolios (i.e., investment possibilities) containing a combination of two assets. For this example, I am fabricating the data as below. The graph below suggests there is one combination of weights that minimizes the standard deviation of the portfolio.

Notice that this graph expands the weights to below zero and above one. These weights’ values occur when you can sell one asset to buy the other. The portfolios in which you are selling either one of the assets are represented in both outer parts of the curve.

corr12 = -0.50

ret1 = 0.175
sd1 = 0.258
var1 = sd1^2
ret2 = 0.055
sd2 = 0.115
var2 = sd2^2
covar12 = corr12*sd1*sd2
w1 = seq(from=-0.4, to=1.4, by=0.05)
w2 = 1 - w1

retp = w1*ret1 + w2*ret2
varp = w1^2 * var1 + w2^2 * var2 + 2*w1*w2*covar12
sdp = sqrt(varp)
port.names = paste("portfolio", 1:length(w1), sep=" ")
df <- data.frame(port.names, w1, w2, retp, sdp)

df <- df %>%  mutate(Condition = case_when(w1<0 ~ "W1<0" , w1>1 ~ "W1>1"  , 0<w1 | w1<1 ~ "0<w1<1" ))

d1 <- df[df$w1 == 1, ]
d2 <- df[df$w2 == 1, ]

ggplot(df, aes(sdp, retp, color= Condition) ) + 
        geom_point(size=3)  +
        labs(x = "Standard deviation",y='Expected return') +
        xlim(0, max(sdp)) + ylim(0, max(retp) ) +
      geom_text(data = d1, aes( label = "Asset 1") , color = "black", hjust=1, vjust=-1, size=5) +
      geom_text(data = d2, aes( label = "Asset 2") , color = "black", hjust=1, vjust= 2, size=5) + 
    ggtitle(paste("Correlation = " , corr12)) 


4.3.1 Combinations of correlation

Let’s now change the correlation coefficient between these assets to see what happens.

That is, the shape of the investment possibilities is very sensitive to the correlation coefficient, especially when the correlation is negative.


4.3.2 Correlation matrix Brazil

Here are some correlations between selected Brazilian stocks using montly data since 2010. As you can see, correlations are all positive and only a few are close to zero.

stocks <-c("ITUB3.SA", "WEGE3.SA", "PETR3.SA", "VALE3.SA", "ITSA3.SA" , "BBAS3.SA", "ABEV3.SA", "GGBR4.SA", "LREN3.SA", "B3SA3.SA",  "UGPA3.SA","CSMG3.SA", "HYPE3.SA") 
start <-'2010-01-01' 
end   <-'2022-01-01'
df <- yf_get(tickers = stocks[[1]], 
                         first_date = start,
                         last_date = end,
                         freq_data = "monthly")
df <- df %>%  select(ref_date)

for (i in 1:length(stocks)) {
data <- yf_get(tickers = stocks[[i]], 
                         first_date = start,
                         last_date = end,
                         freq_data = "monthly")
data<-data[complete.cases(data),] 
data<- data %>%  select(ref_date, ret_adjusted_prices)
colnames(data) <- c("ref_date",  stocks[[i]]  )
df <- merge(df,data,by="ref_date")
}
df$ref_date <-NULL
cor<- cor(df, method = c("pearson"))
library(corrplot)
corrplot(cor,method = 'number',   type = 'lower' ,  tl.col = "black",  order = 'alphabet', tl.cex = 0.75,  col = COL1('Reds') , number.cex = 0.55)


Let’s see the correlations using circles to improve the visuals.

corrplot(cor,method = 'circle',   type = 'lower' ,  tl.col = "black",  order = 'alphabet', tl.cex = 0.75,  col = COL1('Reds') )

4.4 Efficient frontier

4.4.1 Minimum variance portfolio

Before we create a graph of the efficient frontier using real data, let’s go back to fabricated data to visualize the efficient frontier and the minimum variance portfolio.

Below, you can see clearly that the portfolio closest to the y-axis is the one with minimum variance.This portfolio is important because it is at the beginning of the efficient frontier. All portfolios below are not optimal (because there is one portfolio in the frontier with the same level of risk but higher return).


4.4.2 The historical returns and standard deviation of two assets

Let’s now move to an efficient frontier example using real data from Brazil. First, let’s download and compute the average return and the standard deviation of returns for two Brazilian stocks.

##     Ticker        ER        SD
## 1 BBDC3.SA 0.9905195  8.886146
## 2 PETR3.SA 1.2659083 13.074245

4.4.3 Creating a plot

This is the representation in a graph, when return is on the y-asis and the standard deviation is on the x-axis.


4.4.4 The frontier using two assets

It is not as beautiful as the graph using fabricated data because the real correlation between these assets are not low. But notice that the shape is the same as before. Also notice that we can see the minimum variance portfolio as well.

library(data.table)
cov_12 <- cov(asset1$ret_adjusted_prices, asset2$ret_adjusted_prices)
corr_12 <- cor(asset1$ret_adjusted_prices, asset2$ret_adjusted_prices)
weights <- seq(from = 0, to = 1, length.out = 1000)
two_assets <- data.table(w1 = weights ,w2 = 1 - weights)
# calculate the expected returns and standard deviations for the 1000 possible portfolios
two_assets[, ':=' (er_p = w1 * er_1 + w2 * er_2,
                   sd_p = sqrt(w1^2 * sd_1^2 +w2^2 * sd_2^2 +2 * w1 * w2 * cov_12))]
ggplot() +
geom_point(data = two_assets, aes(x = sd_p, y = er_p, color = w1)) +
geom_point(data = data.table(sd = c(sd_1, sd_2), mean = c(er_1, er_2)),
aes(x = sd, y = mean), color = "red", size = 3, shape = 18) +
theme_bw() + ggtitle("Possible Portfolios with Two Risky Assets") +
xlab("Volatility") + ylab("Expected Returns") +
      scale_y_continuous( limits = c(min(two_assets$er_p)*0.99, max(two_assets$er_p)*1.01)) +
      scale_x_continuous( limits = c(min(two_assets$sd_p)*0.9, max(two_assets$sd_p)*1.1)) +
scale_color_continuous(name = expression(omega[x]))+   theme_solarized()

The correlation between these assets is 0.595, which explains the shape of the curve. Keep in mind that it is hard to find stocks with low correlations. You might find low correlation in more international markets. Not so much in Brazil. (Code inspired in: https://github.com/DavZim/Efficient_Frontier).


4.4.5 The frontier using multiple assets

start <-'2010-01-01' 
end   <-'2022-01-01'
freq_data <- "yearly"
df <- yf_collection_get("IBOV", 
                         first_date = start,
                         last_date = end,
                         freq_data = freq_data)
stocks <- unique(df$ticker)
df <- df %>%  select(ref_date)
for (i in 1:length(stocks)) {
data <- yf_get(tickers = stocks[[i]], 
                         first_date = start,
                         last_date = end,
                         freq_data = freq_data)
data<-data[complete.cases(data),] 
data<- data %>%  select(ref_date, ret_closing_prices)
colnames(data) <- c("ref_date",  stocks[[i]]  )
df <- merge(df,data,by="ref_date")
}
df$ref_date <-NULL
cov<- cov(df)
ret<- as.vector(colMeans(df))
# Random numbers to create the frontier
set.seed(100)
int <- 20000
w<- data.frame((replicate(length(stocks),sample(int,rep=TRUE)) / int ))
w$sum <-  rowSums(w)
colnames(w) <- c(stocks, 'Sum')
for (i in 1:int) {
w[i, 1:length(stocks)] <- w[i, 1:length(stocks)] / w[i, ncol(w)]
}
w$Sum <- NULL
w$Sum <-  rowSums(w)
w$Sum <- NULL
# creating final dataframe
port <- data.frame(matrix(NA,nrow = int,ncol = 2))
colnames(port) <- c("Return", "Sd")
for (i in 1:int) {
port[i,1] <- sum(  w[i, ] * ret )
port[i,2] <- sqrt( as.matrix(w[i, ]) %*% as.matrix(cov) %*% as.matrix(t(w[i, ]) )) 
}
#ggplot
ggplot(port, aes(x=Sd, y=Return)) +
        geom_point(alpha=0.2) +
        theme_solarized() + 
        xlab("Standard deviation") + ylab("Expected Return") + 
        labs(title = paste(int , "random portfolios - All Ibov (2010-2022, Yearly returns)") )

4.5 The Risk-free asset

Let’s include a risk-free asset now to see if anything changes. If this economy is correctly priced, the risk free asset must have an expected return lower than both risky assets. Additionally, the risk-free asset is free of risk, so it will be placed at the y-axis. (Graphs inspired in compfinezbook ).


4.5.1 Combining Asset 1 and 2 vs. Asset 1 and risk-free rate

A strategy combining asset 1 and risk-free rate seems to lose comparing to a strategy combining asset 1 and 2.


4.5.2 The tangent portfolio

Nevertheless, see what happens if you combine asset 1 and asset 2 and find the tangent portfolio.

4.6 Sharpe ratio

Let’s see use the same data from the previous graph to create a list of Sharpe ratios. Remember what the Sharpe ratio is:

\[Sharpe\;ratio = \frac{E[R_p]-R_f}{Sd(R_p)}\]

So, for simplicity, I will assume a risk-free rate of 10%, which is low for Brazil these days (but fair in longer windows of time). Also, remember that I using the previous 12 years of data to compute the expected return below for all stocks.

port$rf <- 0.1
port$sharpe <- (port$Return - port$rf) / port$Sd
ggplot(port, aes(x=sharpe)) + 
        geom_histogram(alpha=0.2) + 
        theme_solarized() + 
        xlab("Sharpe Ratios") + ylab("Number of portfolios") + 
        labs(title = paste(int , "random portfolios - All Ibov (2010-2022, yearly returns)") )

What is a good Sharpe ratio? What is your interpretation about this graph?

4.7 Capital Market Line (CML)

The slope of the CML is the Sharpe ratio of the efficient portfolio. That is, the highest Sharpe ratio of all portfolios.

This portfolio is called Market portfolio. According to the CAPM, all investors should choose a portfolio on the CML, by holding some combination of the risk-free security and the market portfolio. The CML is represented in red below.


To see the Market portfolio more clearly, I adjusted the axis in the graph below.

Notice that the Market portfolio seems to be an outlier (remember, we are using real data, so outliers are expected to occur).


If we ignore the top 500 Sharpes and assume they are outlier, this is what we get.


4.7.1 CML with different risk free rates

If the risk free rate changes, all investors are expected to change their allocations.

Notice that the Market portfolio seems to be an outlier (remember, we are using real data, so outliers are expected to occur).


If we zoom in, here is what we find (again, you have to understand the effects of the outliers here and ignore them when making decisions).


So, here is the interpretation: if the risk free rate drops (let’s say from the orange to the blue line), the market portfolio switches to a safer one (“New Market Port.”).

If investors want the same level of return as before, they will need to adjust their holdings. They will buy the new market portfolio (“New Market Port.”) and will borrow at the risk free rate to get into a levered position (“After adjustment”).

The consequence of all is that, when the risk free rate drops, investor can have the same expected return as before while incurring in much less risk.

4.8 Beta

See a more detailed post here.

The graph below is a simple plot of the IBOV’s (market) and ABEV’s (a selected stock, see below) returns. We can observe that when the IBOV’s returns are positive, ABEV’s returns are likely positive too. The blue line shows this positive linear relationship. The Beta is the linear coefficient of this line (if you remember the type of function y = a + bx, the linear coefficient is b; this is what we are going to calculate below).

There are two things to understand here: 1) Beta is used in valuation models (such as the CAPM); thus it is essential to calculate it correctly; 2) when Beta is higher than 1, it means the stock is aggressive (meaning that when the market’s return is 1% the stock’s return is higher than 1%). The opposite occurs when the Beta is below 1 (meaning that when the market’s return is 1% the stock’s return is lower than 1%), and the stock is defensive.

freq.data   <- 'daily'
start <-'2000-01-01' 
end   <-Sys.Date()  
ibov <- yf_get(tickers = "^BVSP",
                        first_date = start,
                        last_date = end,
                        thresh_bad_data = 0.5,
                        freq_data = freq.data)
asset <- yf_get(tickers = "PETR4.SA",
                        first_date = start,
                        last_date = end,
                        thresh_bad_data = 0.5,
                        freq_data = freq.data )
ret_ibov <- ibov  %>% tq_transmute(select = price_adjusted,
                                   mutate_fun = periodReturn,
                                   period = 'daily',
                                   col_rename = 'return',
                                   type = 'log')
ret_asset <- asset   %>%tq_transmute(select = price_adjusted,
                                     mutate_fun = periodReturn,
                                     period = 'daily',
                                     col_rename = 'return',
                                     type = 'log')
ret <- left_join(ret_ibov, ret_asset, by = c("ref_date" = "ref_date"))
window <-252
ret$var <- roll_cov(ret$return.x, ret$return.x, width = window)
ret$cov <- roll_cov(ret$return.x, ret$return.y, width = window)
ret$beta <- ret$cov / ret$var
ret <- subset(ret, ret$beta != "NA" )
ggplot(ret, aes(x= return.x, y=return.y)) + 
  geom_point()+
  geom_smooth(method=lm, se=FALSE)+
  labs( y = "Daily returns PETR4", x="Daily returns IBOV",title = "Beta PETR4")+
  theme(plot.title = element_text(color="darkgreen", size=18, face="bold"),
        panel.background = element_rect(fill = "grey95", colour = "grey95"),
        axis.title=element_text(size=12,face="bold"),
        title=element_text(size=10,face="bold", color="darkgreen"),
        axis.text.y = element_text(face = "bold", color = "darkgreen", size = 12),
        axis.text.x = element_text(face = "bold", color = "darkgreen", size = 12))+
  xlim(-0.2, 0.2) + ylim(-0.2, 0.2) +   theme_solarized()


4.8.1 R-Squared

The r-squared of the linear regression above represents the proportion of the stock’s total risk that can be explained by the market risk.

## 
## Call:
## lm(formula = return.y ~ return.x, data = ret)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.208393 -0.009931 -0.000161  0.009957  0.151061 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 9.084e-05  2.584e-04   0.352    0.725    
## return.x    1.110e+00  1.468e-02  75.611   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.01908 on 5455 degrees of freedom
## Multiple R-squared:  0.5117, Adjusted R-squared:  0.5116 
## F-statistic:  5717 on 1 and 5455 DF,  p-value: < 2.2e-16

The stock’s beta is 1.11.

The standard error of the beta estimate is: 0.0147.

That is, we can use this error to infer the confidence interval where the “true” beta is.

Additionally, we can say that 51.173 percent of the stock’s risk is explained by market risk.


4.8.2 Beta hererogeneity

Notice that different stocks have different betas.

## 
## Call:
## lm(formula = return.y ~ return.x, data = ret)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.164865 -0.008987 -0.000832  0.007810  0.280403 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 0.0008855  0.0002655   3.336 0.000857 ***
## return.x    0.4465415  0.0150793  29.613  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.01961 on 5455 degrees of freedom
## Multiple R-squared:  0.1385, Adjusted R-squared:  0.1383 
## F-statistic: 876.9 on 1 and 5455 DF,  p-value: < 2.2e-16

Beta this asset is 0.447.

And around 13.849 percent of this stock’s risk is explained by market risk.


4.8.3 Beta through time

Below we observe that a stock’s beta varies over time (i.e., previous 252 trading days).


4.8.4 Betas in Brazil

Here is a graph of Betas from Brazilian companies.

start <-'2018-06-01' 
end   <-'2022-06-01'
freq_data <-  'monthly'
ibov <- yf_get(tickers = "^BVSP",
                        first_date = start,
                        last_date = end,
                        thresh_bad_data = 0.5,
                        freq_data = freq_data )
asset <- yf_collection_get("IBOV", 
                         first_date = start,
                         last_date = end,
                         thresh_bad_data = 0.5,
                         freq_data = freq_data )
ret_ibov <- ibov  %>%tq_transmute(select = price_adjusted,
                                  mutate_fun = periodReturn,
                                  period = 'monthly',
                                  col_rename = 'return')
ret_asset <- asset %>%    
            group_by(ticker) %>%
            tq_transmute(select = price_adjusted,
                                  mutate_fun = periodReturn,
                                  period = 'monthly',
                                  col_rename = 'return')
ret <- left_join(ret_ibov, ret_asset, by = c("ref_date" = "ref_date"))
var <- ret %>%
       group_by(ticker) %>%
       summarise(var = cov(return.x, return.x))
cov <- ret %>%
       group_by(ticker) %>%
       summarise(cov = cov(return.x, return.y))
beta <- merge(cov, var, by ="ticker" )
beta$beta <- beta$cov/beta$var
ggplot(beta, aes(x = reorder(ticker, beta ), y = beta, fill=beta) ) +
  geom_bar(aes(fill = -beta), position = "dodge", stat="identity")+
  theme(legend.position="none", 
        axis.text.y = element_blank() )+
  coord_flip()+
  labs(y = "Beta", x = "Stocks", title = "Betas Brazilian companies, monthly data (2018-2022)") +   theme_solarized()

Here is the list of top and bottom Betas (using monthly data from 2018 until 2022). That is, there is a lot of variation in the betas of Brazilian firms.

##      ticker     beta
## 77 VIIA3.SA 2.117961
## 26 CVCB3.SA 2.091105
## 42 GOLL4.SA 1.965591
## 20 COGN3.SA 1.847912
## 4  AZUL4.SA 1.752127
## 80 YDUQ3.SA 1.674490
## 27 CYRE3.SA 1.656599
## 61 PRIO3.SA 1.644783
## 49 JHSF3.SA 1.608768
## 59 PETR3.SA 1.502978
##       ticker       beta
## 78  VIVT3.SA 0.08580102
## 69  SUZB3.SA 0.26684614
## 48  JBSS3.SA 0.38555018
## 63  RADL3.SA 0.44335512
## 23  CRFB3.SA 0.44709683
## 50 KLBN11.SA 0.47248943
## 64  RAIL3.SA 0.56232149
## 34  ENBR3.SA 0.59252119
## 75  VALE3.SA 0.60719539
## 70 TAEE11.SA 0.60751436

4.9 Security Market Line (SML)

Under the CAPM assumptions, the security market line (SML) is the line along which all individual securities should lie when plotted according to their expected return and beta. To create the graph below, I need estimates to the Brazilian Risk free rate and the Equity Risk Premium. I am using 10% as risk free rate and 16% for the Equity Risk Premium. Equity Risk Premium was based on this website.

start <-'2018-06-01' 
end   <-'2022-06-01'
freq_data <-  'monthly'
ibov <- yf_get(tickers = "^BVSP",
                        first_date = start,
                        last_date = end,
                        thresh_bad_data = 0.5,
                        freq_data = freq_data )
asset <- yf_collection_get("IBOV", 
                         first_date = start,
                         last_date = end,
                         thresh_bad_data = 0.5,
                         freq_data = freq_data )
ret_ibov <- ibov  %>%tq_transmute(select = price_adjusted,
                                  mutate_fun = periodReturn,
                                  period = 'monthly',
                                  col_rename = 'return')
ret_asset <- asset %>%    
            group_by(ticker) %>%
            tq_transmute(select = price_adjusted,
                                  mutate_fun = periodReturn,
                                  period = 'monthly',
                                  col_rename = 'return')
ret <- left_join(ret_ibov, ret_asset, by = c("ref_date" = "ref_date"))
#To compute beta
var <- ret %>% group_by(ticker) %>% summarise(var = cov(return.x, return.x))
cov <- ret %>% group_by(ticker) %>% summarise(cov = cov(return.x, return.y))
beta <- merge(cov, var, by ="ticker" )
beta$beta <- beta$cov/beta$var
# Risk free rate
beta$rf  <- 0.1 
# Equity risk premium from here: https://ceqef.fgv.br/node/594
beta$erp <- 0.16 
# expected return
beta$ep <- beta$rf + beta$beta * beta$erp
#Security Market Line
ggplot(beta, aes(x = beta, y = ep  )  ) + geom_point() +
ggrepel::geom_text_repel(data = beta, aes(label = ticker) , size = 2.5 ,  max.overlaps = Inf)+
  theme(legend.position="none",  axis.text.y = element_blank() )+
  labs(y = "Expected return", 
       x = "Beta", 
       title = "SML Brazil, monthly data (2018-2022)" , 
       subtitle = "Rf = 10%, Risk premium = 16%" )+   theme_solarized()

4.10 Equity Risk Premium (ERP)

Let’s plot the Equity Risk Premium. I am extracting the data from this website.

library(ggplot2)
library(ggthemes)
library(downloader)
library(readxl)
url <- "https://ceqef.fgv.br/sites/ceqef.fgv.br/files/serie_de_equity_risk_premium_dezembro.xls"
download(url, dest="files/epr.xls", mode="wb") 
data <- read_excel("files/epr.xls")
data <- data[,1:2]
colnames(data) <- c("month", "erp")

ggplot(data, aes(x=month, y = erp)) + geom_line() + theme_solarized()+
  labs(y = "Equity Risk Premium (ERP)", 
       x = "", 
       title = "Equity Risk Premium (ERP) in Brazil" , 
       caption = "Source: https://ceqef.fgv.br/node/594" )