FAMA-FRENCH

(FAANG) CAPM and FAMA-FRENCH

This piece takes a look at FAANG stock returns relative to the three-factor Fama-French model. This model incorporates CAPM risk, in addition to two factors established by Fama-French in their model. These two extra factors incorporate firm’s size and the book to market valuation ratio.

The analysis looks into each stock’s relationship with the market through the Betas obtained from the regression between the stock’s returns and the market premium, and the factors abovementioned.

DATA WRANGLING

Here, we join the dataframes from the downloaded stocks and the Fama-French main risk factors SMB (size) and HML (book to market ratio), align them and set them to their proper decimal place.

#First, bring in the Fama-French 'FF' factors from the library
ff_url <- "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip"

#create a temp file
temp_file <- tempfile()

#download the file from the URL and place it in the temp file
download.file(ff_url, temp_file)

#unzip the temp file; "_o = original"
FF <- unzip(temp_file)

# Skipping the first 3 rows
FF <- read_csv(FF, skip = 3, col_types = cols(...1 = col_integer(),`Mkt-RF` = col_double(),
                                              SMB = col_double(),
                                              HML = col_double(),
                                              RF = col_double())) %>% rename(date = ...1) %>% subset(date>100000) %>% mutate(date = ymd(parse_date_time(date, "%Y%m")))  %>% na.omit(date)

#Create new dataframe as FF from the original, that adds a "MKT" column, which is the Market premium + the RF base

FF <- FF  %>% mutate(MKT = `Mkt-RF`+ RF)


#Download the stocks and put them into a dataframe
symbols <- c("AMZN","META","AAPL","GOOGL","NFLX")
prices <- 
  getSymbols(symbols, src = 'yahoo', 
             from = "2015-03-30", 
             auto.assign = TRUE, 
             warnings = FALSE) %>% 
  map(~Ad(get(.))) %>% 
  reduce(merge) %>%
  `colnames<-`(symbols)

#CONVERT DATA INTO MONTHLY RETURNS

returns <- 
  prices %>% 
  to.monthly(indexAt = "firstof", OHLC = FALSE) 
  
returns <- data.frame(Return.calculate(returns)) %>%
  na.omit

#create "Date" as a column and create returns
returns$date <-as.Date(row.names(returns))
row.names(returns) <-NULL

# Assuming you have a data frame named 'returns' with the date as a column and other columns containing the stock returns

# Identify the index of the 'Date' column
date_index <- which(names(returns) == "date")

# Move the 'Date' column to the first position
returns <- returns[, c(date_index, setdiff(1:ncol(returns), date_index))]

#merge dataframes

ff_assets <-
  returns %>% 
  left_join(FF, by = "date")%>%
  mutate(MKT_RF = MKT/100,
         SMB = SMB/100,
         HML = HML/100,
         RF = RF/100,
         AMZN = AMZN-RF,
         META = META-RF,
         GOOGL = GOOGL-RF,
         AAPL = AAPL-RF,
         NFLX = NFLX-RF
         ) %>%
  na.omit()

AMAZON

AMAZON CAPM

Amazon returned a β 1.286, meaning that that Amazon is sensitive to the market and therefore cyclical. This means that for every 1% change in the market (benchmark), Amazon’s returns will move in the same direction by 1.28 percent, slightly above the market.

capm_amazon <- lm(AMZN ~ MKT_RF, data=ff_assets)
summ_amzn <- summary(capm_amazon)
summ_amzn
## 
## Call:
## lm(formula = AMZN ~ MKT_RF, data = ff_assets)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.208650 -0.040946 -0.006518  0.046413  0.206087 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 0.009216   0.007449   1.237    0.219    
## MKT_RF      1.286390   0.154754   8.312 6.58e-13 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.07192 on 95 degrees of freedom
## Multiple R-squared:  0.4211, Adjusted R-squared:  0.415 
## F-statistic:  69.1 on 1 and 95 DF,  p-value: 6.581e-13
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_amzn_t <- glance(summ_amzn)
coeff_amzn <- tidy(summ_amzn)

# Combine the coefficients and summary data frames
amzn_capm <- bind_rows(summ_amzn_t, coeff_amzn)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(amzn_capm) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.4210762 0.4149822 0.0719157 69.097580 0.0000000 1 95 97 NA NA NA
NA NA NA 1.237217 0.2190561 NA NA NA (Intercept) 0.0092156 0.0074486
NA NA NA 8.312495 0.0000000 NA NA NA MKT_RF 1.2863902 0.1547538
#SML plot
inter_amzn <-coef(capm_amazon)[1]
slope_amzn <-coef(capm_amazon)[2]

#dataframe for SML
sml_amzn <-data.frame(MKT_RF = seq(min(ff_assets$MKT_RF),
                                   max(ff_assets$MKT_RF),
                                   length.out = 100))

# Calculate the SML values using LM coefficients
sml_amzn$SML<-inter_amzn + slope_amzn * sml_amzn$MKT_RF

#Plot SML
theme_set(theme_minimal(base_family = font))

plot_capm_amzn <-
ggplot() +
  geom_point(data=ff_assets, aes(x=MKT_RF, y=AMZN), color="#0072B2", alpha = 0.5, size = 2) +
  geom_line(data = sml_amzn, aes(x=MKT_RF, y=SML), color="#D55E00", size = 0.5)+
labs(title = "<b>Security Market Line (SML) AMAZON<b>", x = "<b>Market Return (MKT_RF)<b>", y = "<b>Stock Return (AMZN)<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12),
        panel.grid.major = element_line(color = "gray80", linetype = "dashed"),
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank(),
        panel.grid.major.y = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_x_continuous(labels = percent_format(), breaks = seq(-0.2, 0.2, 0.05)) +
  scale_y_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1))

ggplotly(plot_capm_amzn)

AMAZON Fama-French

After 2 more factors, size and book to market value, we found that: “Small Minus Big” (SML), or size of the company and the intercept are not statistically significant variables explaining Amazon’s returns. However, the book to market ratio, or “High Minus Low” (HML), and the market risk (CAPM) are important factors affecting Amazon’s returns. Here, the book to market value ratio has a negative β, while the market Rf β increased from the CAPM to Fama-French from 1.28 to 1.33. The model had a higher R-squared.

fama_amazon <- lm(AMZN ~ MKT_RF + SMB + HML,data=ff_assets)
summ_fama_amzn <- summary(fama_amazon)
summ_fama_amzn
## 
## Call:
## lm(formula = AMZN ~ MKT_RF + SMB + HML, data = ff_assets)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.136759 -0.042886 -0.004355  0.035452  0.156829 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.007175   0.006456   1.111    0.269    
## MKT_RF       1.339996   0.138657   9.664 1.05e-15 ***
## SMB         -0.299728   0.241978  -1.239    0.219    
## HML         -0.918025   0.161313  -5.691 1.46e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.062 on 93 degrees of freedom
## Multiple R-squared:  0.5788, Adjusted R-squared:  0.5652 
## F-statistic:  42.6 on 3 and 93 DF,  p-value: < 2.2e-16
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_fama_amzn_t <- glance(summ_fama_amzn)
coeff_fama_amzn <- tidy(summ_fama_amzn)

# Combine the coefficients and summary data frames
amzn_fama <- bind_rows(summ_fama_amzn_t, coeff_fama_amzn)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(amzn_fama) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.5788248 0.5652385 0.0619961 42.603566 0.0000000 3 93 97 NA NA NA
NA NA NA 1.111332 0.2692902 NA NA NA (Intercept) 0.0071749 0.0064561
NA NA NA 9.664079 0.0000000 NA NA NA MKT_RF 1.3399962 0.1386574
NA NA NA -1.238660 0.2185887 NA NA NA SMB -0.2997283 0.2419778
NA NA NA -5.690941 0.0000001 NA NA NA HML -0.9180252 0.1613134
amazon <- data.frame(ff_assets$AMZN, predict(fama_amazon), predict(capm_amazon))

plot_fama_amzn <- ggplot(amazon) +
  geom_point(aes(x = ff_assets$AMZN, y = predict(fama_amazon), color = "Fama-French"), alpha=0.5, size = 2) +
  geom_point(aes(x = ff_assets$AMZN, y = predict(capm_amazon), color = "CAPM Model"), alpha=0.5, size = 2) +
  geom_abline(intercept = 0, color = "#0072B2", size=0.7) +
  labs(title = "<b>Stock Returns vs Predicted Returns, AMAZON<b>", x = "<b>Stock Returns (AMZN)<b>", y = "<b>Predicted Returns<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 12, face = "bold"),
        axis.text = element_text(size = 10, face = "bold"),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20),
        legend.title = element_text(face = "bold", hjust = 0.5)) +
  scale_color_manual(values = c("Fama-French" = "magenta", "CAPM Model" = "purple"),
                     labels = c("Fama-French", "CAPM Model")) +
  guides(color = guide_legend(title = "<b>Models</b>"))

plot_fama_amzn <- ggplotly(plot_fama_amzn)

# Modify plotly layout for legend position
plot_fama_amzn <- plot_fama_amzn %>%
  layout(
    legend = list(
      x = 0.9, y = 0.1, 
      xanchor = "right", yanchor = "bottom",
      bgcolor = "white",
      bordercolor = "white",
      borderwidth = 1
    )
  )

plot_fama_amzn

GOOGLE

GOOGLE CAPM

Google returned a β 1.06, meaning that that Amazon is sensitive to the market and therefore cyclical, with essentially a β of 1. This means that for every 1% change in the market (benchmark), Google’s returns will move in the same direction by 1.06 percent, virtually on par with the market. Alpha, the intercept, was not statistically significant, and also virtually zero, so its impact is minimal. In this case, the adjusted R-squared was 0.4932, suggesting that about half of the returns movement can be explained by movements in the market.

capm_googl <- lm(GOOGL ~ MKT_RF, data=ff_assets)
summ_googl <- summary(capm_googl)
summ_googl
## 
## Call:
## lm(formula = GOOGL ~ MKT_RF, data = ff_assets)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.105634 -0.029380 -0.004115  0.031100  0.195526 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 0.005575   0.005275   1.057    0.293    
## MKT_RF      1.065021   0.109597   9.718 6.63e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.05093 on 95 degrees of freedom
## Multiple R-squared:  0.4985, Adjusted R-squared:  0.4932 
## F-statistic: 94.43 on 1 and 95 DF,  p-value: 6.631e-16
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_googl_t <- glance(summ_googl)
coeff_googl <- tidy(summ_googl)

# Combine the coefficients and summary data frames
googl_capm <- bind_rows(summ_googl_t, coeff_googl)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(googl_capm) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.4985004 0.4932214 0.0509309 94.431845 0.0000000 1 95 97 NA NA NA
NA NA NA 1.056830 0.2932694 NA NA NA (Intercept) 0.0055749 0.0052751
NA NA NA 9.717605 0.0000000 NA NA NA MKT_RF 1.0650209 0.1095971
#SML plot
inter_googl <-coef(capm_googl)[1]
slope_googl <-coef(capm_googl)[2]

#dataframe for SML
sml_googl <-data.frame(MKT_RF = seq(min(ff_assets$MKT_RF),
                                   max(ff_assets$MKT_RF),
                                   length.out = 100))

# Calculate the SML values using LM coefficients
sml_googl$SML<-inter_googl + slope_googl * sml_googl$MKT_RF

#Plot SML
theme_set(theme_minimal(base_family = font))

plot_capm_googl<-
ggplot() +
  geom_point(data=ff_assets, aes(x=MKT_RF, y=GOOGL), color="#0072B2", alpha = 0.5, size = 2) +
  geom_line(data = sml_googl, aes(x=MKT_RF, y=SML), color="#D55E00", size = 0.5)+
labs(title = "<b>Security Market Line (SML) GOOGLE<b>", x = "<b>Market Return (MKT_RF)<b>", y = "<b>Stock Return (GOOGL)<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12),
        panel.grid.major = element_line(color = "gray80", linetype = "dashed"),
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank(),
        panel.grid.major.y = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_x_continuous(labels = percent_format(), breaks = seq(-0.2, 0.2, 0.05)) +
  scale_y_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1))

ggplotly(plot_capm_googl)

GOOGLE Fama-French

After 2 more factors, both the size (HML) and book to market value (SML) are statistically significant but compared to CAPM (or market risk), their impact is smaller. In addition, SML and HML βetas were negative suggesting that the stock behaves like a “growth” security. The model had a slightly higher R-squared.

fama_googl <- lm(GOOGL ~ MKT_RF + SMB + HML,data=ff_assets)
summ_fama_googl <- summary(fama_googl)
summ_fama_googl
## 
## Call:
## lm(formula = GOOGL ~ MKT_RF + SMB + HML, data = ff_assets)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.109342 -0.030699 -0.006392  0.025726  0.162410 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.003818   0.005000   0.764  0.44702    
## MKT_RF       1.146116   0.107389  10.673  < 2e-16 ***
## SMB         -0.504400   0.187410  -2.691  0.00844 ** 
## HML         -0.302417   0.124936  -2.421  0.01744 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.04802 on 93 degrees of freedom
## Multiple R-squared:  0.5637, Adjusted R-squared:  0.5496 
## F-statistic: 40.04 on 3 and 93 DF,  p-value: < 2.2e-16
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_fama_googl_t <- glance(summ_fama_googl)
coeff_fama_googl <- tidy(summ_fama_googl)

# Combine the coefficients and summary data frames
googl_fama <- bind_rows(summ_fama_googl_t, coeff_fama_googl)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(googl_fama) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.5636541 0.5495785 0.0480155 40.0445601 0.0000000 3 93 97 NA NA NA
NA NA NA 0.7636371 0.4470165 NA NA NA (Intercept) 0.0038184 0.0050002
NA NA NA 10.6725534 0.0000000 NA NA NA MKT_RF 1.1461157 0.1073891
NA NA NA -2.6914238 0.0084368 NA NA NA SMB -0.5043995 0.1874099
NA NA NA -2.4205728 0.0174385 NA NA NA HML -0.3024166 0.1249360
googl <- data.frame(ff_assets$GOOGL, predict(fama_googl), predict(capm_googl))

plot_fama_googl<-
ggplot(googl) +
  geom_point(aes(x = ff_assets$GOOGL, y = predict(fama_googl), color = "Fama-French"), alpha=0.5, size = 2) +
  geom_point(aes(x = ff_assets$GOOGL, y = predict(capm_googl), color = "CAPM Model"), alpha=0.5, size = 2) +
  geom_abline(intercept = 0, color = "#0072B2", size=0.7) +
  labs(title = "<b>Stock Returns vs Predicted Returns GOOGLE<b>", x = "<b>Stock Returns (GOOGL)<b>", y = "<b>Predicted Returns<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 12, face = "bold"),
        axis.text = element_text(size = 10, face = "bold"),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_color_manual(values = c("Fama-French" = "magenta", "CAPM Model" = "purple"),
                     labels = c("Fama-French", "CAPM Model")) +
  guides(color = guide_legend(title = "<b>Models<b>"))


plot_fama_googl <- ggplotly(plot_fama_googl)

# Modify plotly layout for legend position
plot_fama_googl <- plot_fama_googl %>%
  layout(
    legend = list(
      x = 0.9, y = 0.1, 
      xanchor = "right", yanchor = "bottom",
      bgcolor = "white",
      bordercolor = "white",
      borderwidth = 1
    )
  )

plot_fama_googl

NETFLIX

NETFLIX CAPM

Netflix returned a β 1.28, meaning that that Netflix is even more sensitive to the market than the Google and Amazon, and is therefore a cyclical stock. This means that for every 1% change in the market (benchmark), Netflix’s returns will move in the same direction by 1.3 percent, essentially moving a fraction more (about a quarter) with the market. Alpha, the intercept, was not statistically significant, and also virtually zero, so its impact is minimal. In this case, the adjusted R-squared was 0.23, suggesting that less that a quarter of the return movements can be explained by the market itself. Therefore, it would be prudent to consider other variables to predict prices.

capm_nflx <- lm(NFLX ~ MKT_RF, data=ff_assets)
summ_nflx <- summary(capm_nflx)
summ_nflx
## 
## Call:
## lm(formula = NFLX ~ MKT_RF, data = ff_assets)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.38369 -0.06640 -0.00520  0.04838  0.32119 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.01283    0.01129   1.136    0.259    
## MKT_RF       1.28099    0.23462   5.460  3.8e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.109 on 95 degrees of freedom
## Multiple R-squared:  0.2388, Adjusted R-squared:  0.2308 
## F-statistic: 29.81 on 1 and 95 DF,  p-value: 3.797e-07
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_nflx_t <- glance(summ_nflx)
coeff_nflx <- tidy(summ_nflx)

# Combine the coefficients and summary data frames
nflx_capm <- bind_rows(summ_nflx_t, coeff_nflx)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(nflx_capm) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.2388492 0.2308371 0.1090284 29.811012 0.0000004 1 95 97 NA NA NA
NA NA NA 1.136143 0.2587545 NA NA NA (Intercept) 0.0128299 0.0112925
NA NA NA 5.459946 0.0000004 NA NA NA MKT_RF 1.2809896 0.2346158
#SML plot
inter_nflx <-coef(capm_nflx)[1]
slope_nflx <-coef(capm_nflx)[2]

#dataframe for SML
sml_nflx <-data.frame(MKT_RF = seq(min(ff_assets$MKT_RF),
                                   max(ff_assets$MKT_RF),
                                   length.out = 100))

# Calculate the SML values using LM coefficients
sml_nflx$SML<-inter_nflx + slope_nflx * sml_nflx$MKT_RF

#Plot SML
theme_set(theme_minimal(base_family = font))

plot_capm_nflx<-
ggplot() +
  geom_point(data=ff_assets, aes(x=MKT_RF, y=NFLX), color="#0072B2", alpha = 0.5, size = 2) +
  geom_line(data = sml_googl, aes(x=MKT_RF, y=SML), color="#D55E00", size = 0.5)+
labs(title = "<b>Security Market Line (SML) NFLX<b>", x = "<b>Market Return (MKT_RF)<b>", y = "<b>Stock Return (NFLX)<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12),
        panel.grid.major = element_line(color = "gray80", linetype = "dashed"),
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank(),
        panel.grid.major.y = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_x_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1)) +
  scale_y_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1))

ggplotly(plot_capm_nflx)

NETFLIX Fama-French

After 2 more factors, the size (HML) and book to market value (SML) under the Fama-French model, we noticed that while there was an improvement in the predictions, this was minimal compared to CAPM. Only book-to-market value (HML) and the market were statistically significant. The market β was virtually the same as the on from CAPM at 1.27, while the book-to-market β was -0.84, indicating a negative relationship with returns. Thus, although there is a positive relationship with the market, Netflix returns have shown counter-cyclical patterns relative to high book-to-market stocks. In other words, when high book-to-market stocks perform well, Netfix’s stock returns may tend to under perform, and vice versa. The model had a slightly higher R-squared at 0.29.

fama_nflx <- lm(NFLX ~ MKT_RF + SMB + HML,data=ff_assets)
summ_fama_nflx <- summary(fama_nflx)
summ_fama_nflx
## 
## Call:
## lm(formula = NFLX ~ MKT_RF + SMB + HML, data = ff_assets)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.33031 -0.06880 -0.00767  0.04978  0.33516 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.01202    0.01093   1.100  0.27402    
## MKT_RF       1.26772    0.23467   5.402 5.04e-07 ***
## SMB          0.12448    0.40953   0.304  0.76183    
## HML         -0.84423    0.27301  -3.092  0.00262 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.1049 on 93 degrees of freedom
## Multiple R-squared:  0.3099, Adjusted R-squared:  0.2877 
## F-statistic: 13.92 on 3 and 93 DF,  p-value: 1.425e-07
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_fama_nflx_t <- glance(summ_fama_nflx)
coeff_fama_nflx <- tidy(summ_fama_nflx)

# Combine the coefficients and summary data frames
nflx_fama <- bind_rows(summ_fama_nflx_t, coeff_fama_nflx)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(nflx_fama) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.3099268 0.2876663 0.1049233 13.9227678 0.0000001 3 93 97 NA NA NA
NA NA NA 1.1003404 0.2740238 NA NA NA (Intercept) 0.0120228 0.0109265
NA NA NA 5.4022156 0.0000005 NA NA NA MKT_RF 1.2677175 0.2346662
NA NA NA 0.3039721 0.7618282 NA NA NA SMB 0.1244849 0.4095275
NA NA NA -3.0923138 0.0026217 NA NA NA HML -0.8442315 0.2730096
nflx <- data.frame(ff_assets$NFLX, predict(fama_nflx), predict(capm_nflx))

plot_fama_nflx<-
ggplot(nflx) +
  geom_point(aes(x = ff_assets$NFLX, y = predict(fama_nflx), color = "Fama-French"), alpha=0.5, size = 2) +
  geom_point(aes(x = ff_assets$NFLX, y = predict(capm_nflx), color = "CAPM Model"), alpha=0.5, size = 2) +
  geom_abline(intercept = 0, color = "#0072B2", size=0.7) +
  labs(title = "<b>Stock Returns vs Predicted Returns NFLX<b>", x = "<b>Stock Returns (NFLX)<b>", y = "<b>Predicted Returns<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 12, face = "bold"),
        axis.text = element_text(size = 10, face = "bold"),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_x_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1)) +
  scale_y_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1))+
  scale_color_manual(values = c("Fama-French" = "magenta", "CAPM Model" = "purple"),
                     labels = c("Fama-French", "CAPM Model")) +
  guides(color = guide_legend(title = "<b>Models<b>"))


plot_fama_nflx <- ggplotly(plot_fama_nflx)

# Modify plotly layout for legend position
plot_fama_nflx <- plot_fama_nflx %>%
  layout(
    legend = list(
      x = 0.9, y = 0.1, 
      xanchor = "right", yanchor = "bottom",
      bgcolor = "white",
      bordercolor = "white",
      borderwidth = 1
    )
  )

plot_fama_nflx

FACEBOOK

FACEBOOK CAPM

Facebook returned a β 1.05, meaning that that Facebook is sensitive and essentially moves on par to the market, and, as such, is a cyclical stock. This means that for every 1% change in the market (benchmark), Facebook’s returns will move in the same direction by 1.05 percent, virtually, a β of 1. Alpha, the intercept, was not statistically significant, and also virtually zero, so its impact is minimal. The adjusted R-squared was 0.24, suggesting that about a quarter of the return movements can be explained by the market. Therefore, it would be prudent to consider other variables to predict prices.

capm_meta <- lm(META ~ MKT_RF, data=ff_assets)
summ_meta <- summary(capm_meta)
summ_meta
## 
## Call:
## lm(formula = META ~ MKT_RF, data = ff_assets)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.40647 -0.03758  0.00279  0.03978  0.20762 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 0.005365   0.009099   0.590    0.557    
## MKT_RF      1.059813   0.189032   5.607 2.02e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.08785 on 95 degrees of freedom
## Multiple R-squared:  0.2486, Adjusted R-squared:  0.2407 
## F-statistic: 31.43 on 1 and 95 DF,  p-value: 2.018e-07
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_meta_t <- glance(summ_meta)
coeff_meta <- tidy(summ_meta)

# Combine the coefficients and summary data frames
meta_capm <- bind_rows(summ_meta_t, coeff_meta)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(meta_capm) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.2486148 0.2407055 0.0878451 31.4331516 0.0000002 1 95 97 NA NA NA
NA NA NA 0.5896689 0.5568125 NA NA NA (Intercept) 0.0053651 0.0090985
NA NA NA 5.6065276 0.0000002 NA NA NA MKT_RF 1.0598134 0.1890321
#SML plot
inter_meta <-coef(capm_meta)[1]
slope_meta <-coef(capm_meta)[2]

#dataframe for SML
sml_meta <-data.frame(MKT_RF = seq(min(ff_assets$MKT_RF),
                                   max(ff_assets$MKT_RF),
                                   length.out = 100))

# Calculate the SML values using LM coefficients
sml_meta$SML<-inter_meta + slope_meta * sml_meta$MKT_RF

#Plot SML
theme_set(theme_minimal(base_family = font))

plot_capm_meta<-
ggplot() +
  geom_point(data=ff_assets, aes(x=MKT_RF, y=META), color="#0072B2", alpha = 0.5, size = 2) +
  geom_line(data = sml_googl, aes(x=MKT_RF, y=SML), color="#D55E00", size = 0.5)+
labs(title = "<b>Security Market Line (SML) META<b>", x = "<b>Market Return (MKT_RF)<b>", y = "<b>Stock Return (META)<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12),
        panel.grid.major = element_line(color = "gray80", linetype = "dashed"),
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank(),
        panel.grid.major.y = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_x_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1)) +
  scale_y_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1))

ggplotly(plot_capm_meta)

FACEBOOK Fama-French

After 2 more factors, the size (HML) and book to market value (SML) under the Fama-French model, we noticed that while there was an improvement in the predictions, this was minimal compared to CAPM. The market was again statistically significant, with a β increasing from 1.05 to 1.15. The book-to-market β was -0.54, indicating a negative relationship with returns, but its P value was high compared to the market; still, it was technically statistically significant. This suggests that like Netflix, Facebook’s returns have shown counter-cyclical behavior relative to high book-to-market (value) stocks. The size ratio was borderline statistically significant, with a p-value of 0.074. The model had a slightly higher R-squared at 0.30. Still, the model is not great. The model appears to over-predict, and the SE was about 0.084, suggesting a great dispersion in the results–illustrated by the chart.

fama_meta <- lm(META ~ MKT_RF + SMB + HML,data=ff_assets)
summ_fama_meta <- summary(fama_meta)
summ_fama_meta
## 
## Call:
## lm(formula = META ~ MKT_RF + SMB + HML, data = ff_assets)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.36817 -0.03688  0.00037  0.03497  0.19681 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.003047   0.008795   0.346   0.7298    
## MKT_RF       1.156899   0.188886   6.125 2.15e-08 ***
## SMB         -0.595626   0.329634  -1.807   0.0740 .  
## HML         -0.537559   0.219749  -2.446   0.0163 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.08445 on 93 degrees of freedom
## Multiple R-squared:  0.3201, Adjusted R-squared:  0.2982 
## F-statistic:  14.6 on 3 and 93 DF,  p-value: 7.239e-08
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_fama_meta_t <- glance(summ_fama_meta)
coeff_fama_meta <- tidy(summ_fama_meta)

# Combine the coefficients and summary data frames
meta_fama <- bind_rows(summ_fama_meta_t, coeff_fama_meta)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(meta_fama) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.320125 0.2981935 0.0844542 14.5966137 0.0000001 3 93 97 NA NA NA
NA NA NA 0.3464772 0.7297668 NA NA NA (Intercept) 0.0030472 0.0087949
NA NA NA 6.1248536 0.0000000 NA NA NA MKT_RF 1.1568991 0.1888860
NA NA NA -1.8069307 0.0740068 NA NA NA SMB -0.5956261 0.3296342
NA NA NA -2.4462410 0.0163158 NA NA NA HML -0.5375594 0.2197491
meta <- data.frame(ff_assets$META, predict(fama_meta), predict(capm_meta))

plot_fama_meta<-
ggplot(meta) +
  geom_point(aes(x = ff_assets$META, y = predict(fama_meta), color = "Fama-French"), alpha=0.5, size = 2) +
  geom_point(aes(x = ff_assets$META, y = predict(capm_meta), color = "CAPM Model"), alpha=0.5, size = 2) +
  geom_abline(intercept = 0, color = "#0072B2", size=0.7) +
  labs(title = "<b>Stock Returns vs Predicted Returns META<b>", x = "<b>Stock Returns (META)<b>", y = "<b>Predicted Returns<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 12, face = "bold"),
        axis.text = element_text(size = 10, face = "bold"),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_x_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1)) +
  scale_y_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1))+
  scale_color_manual(values = c("Fama-French" = "magenta", "CAPM Model" = "purple"),
                     labels = c("Fama-French", "CAPM Model")) +
  guides(color = guide_legend(title = "<b>Models<b>"))


plot_fama_meta <- ggplotly(plot_fama_meta)

# Modify plotly layout for legend position
plot_fama_meta <- plot_fama_meta %>%
  layout(
    legend = list(
      x = 0.9, y = 0.1, 
      xanchor = "right", yanchor = "bottom",
      bgcolor = "white",
      bordercolor = "white",
      borderwidth = 1
    )
  )

plot_fama_meta

APPLE

APPLE CAPM

Apple returned a β 1.22, meaning that that Apple is sensitive and essentially moves with the market, and, as such, is a cyclical stock. This means that for every 1% change in the market (benchmark), Apple’s returns will move in the same direction by 1.22 percent, slightly more than the market. Alpha, the intercept, was not statistically significant, and also virtually zero, so its impact is minimal. The adjusted R-squared was 0.47, suggesting that about half of the returns’ movements can be explained by the market, which is significant. Initially, if we incorporate Fama-French, we could obtain better results, but the market appears to be a large variable, suggesting that Apple’s returns are similarly linked to the market to those of Amazon.

capm_aapl <- lm(AAPL ~ MKT_RF, data=ff_assets)
summ_aapl <- summary(capm_aapl)
summ_aapl
## 
## Call:
## lm(formula = AAPL ~ MKT_RF, data = ff_assets)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.215570 -0.031263  0.000956  0.041769  0.145098 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)    
## (Intercept) 0.009708   0.006339   1.531    0.129    
## MKT_RF      1.222679   0.131705   9.283 5.62e-15 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.0612 on 95 degrees of freedom
## Multiple R-squared:  0.4757, Adjusted R-squared:  0.4701 
## F-statistic: 86.18 on 1 and 95 DF,  p-value: 5.621e-15
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_aapl_t <- glance(summ_aapl)
coeff_aapl <- tidy(summ_aapl)

# Combine the coefficients and summary data frames
aapl_capm <- bind_rows(summ_aapl_t, coeff_aapl)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(aapl_capm) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.4756676 0.4701484 0.0612046 86.182793 0.0000000 1 95 97 NA NA NA
NA NA NA 1.531357 0.1290044 NA NA NA (Intercept) 0.0097076 0.0063392
NA NA NA 9.283469 0.0000000 NA NA NA MKT_RF 1.2226787 0.1317049
#SML plot
inter_aapl <-coef(capm_aapl)[1]
slope_aapl <-coef(capm_aapl)[2]

#dataframe for SML
sml_aapl <-data.frame(MKT_RF = seq(min(ff_assets$MKT_RF),
                                   max(ff_assets$MKT_RF),
                                   length.out = 100))

# Calculate the SML values using LM coefficients
sml_aapl$SML<-inter_aapl + slope_aapl * sml_aapl$MKT_RF

#Plot SML
theme_set(theme_minimal(base_family = font))

plot_capm_aapl<-
ggplot() +
  geom_point(data=ff_assets, aes(x=MKT_RF, y=AAPL), color="#0072B2", alpha = 0.5, size = 2) +
  geom_line(data = sml_googl, aes(x=MKT_RF, y=SML), color="#D55E00", size = 0.5)+
labs(title = "<b>Security Market Line (SML) AAPL<b>", x = "<b>Market Return (MKT_RF)<b>", y = "<b>Stock Return (AAPL)<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 14),
        axis.text = element_text(size = 12),
        panel.grid.major = element_line(color = "gray80", linetype = "dashed"),
        panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank(),
        panel.grid.major.y = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_x_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1)) +
  scale_y_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1))

ggplotly(plot_capm_aapl)

APPLE Fama-French

After 2 more factors, the size (HML) and book to market value (SML) under the Fama-French model, we noticed that while there was an improvement in the predictions, this was minimal compared to CAPM, as expected and noted. The market was again statistically significant, with a β increasing from 1.22 to 1.26, so not a large change. The book-to-market ratio (HML) β was -0.54, indicating a negative relationship with returns and statistically significant. Similar to Amazon, Netflix and Facebook (and to a degree to Google too) the β for the book to market ratio indicates that Apple’s stock returns has shown counter-cyclical pattern relative to high(er) book-to-market stocks. The size factor was not statistically significant. The model had a slightly higher R-squared at 0.53 from 47 with only CAPM. Still, the model is not great, but better than Facebook and Netflix. Interestingly, the model appears to over-predict on the negative returns, and under predict when returns are positive, as illustrated by the scatter plot. Also, the SE at about 0.057, appears to be small; still, the model could be improved.

fama_aapl <- lm(AAPL ~ MKT_RF + SMB + HML,data=ff_assets)
summ_fama_aapl <- summary(fama_aapl)
summ_fama_aapl
## 
## Call:
## lm(formula = AAPL ~ MKT_RF + SMB + HML, data = ff_assets)
## 
## Residuals:
##       Min        1Q    Median        3Q       Max 
## -0.215404 -0.029019 -0.003701  0.038643  0.127052 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  0.008261   0.005996   1.378 0.171572    
## MKT_RF       1.269632   0.128777   9.859 4.06e-16 ***
## SMB         -0.275601   0.224734  -1.226 0.223166    
## HML         -0.525551   0.149818  -3.508 0.000698 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.05758 on 93 degrees of freedom
## Multiple R-squared:  0.5457, Adjusted R-squared:  0.5311 
## F-statistic: 37.24 on 3 and 93 DF,  p-value: 6.714e-16
# Assuming you have a linear regression model named 'model'

# Convert the summary to a tidy data frame using glance() and tidy() functions from broom package
summ_fama_aapl_t <- glance(summ_fama_aapl)
coeff_fama_aapl <- tidy(summ_fama_aapl)

# Combine the coefficients and summary data frames
aapl_fama <- bind_rows(summ_fama_aapl_t, coeff_fama_aapl)

# Print the results table using kable and kable_styling functions from kableExtra package
kable(aapl_fama) %>%
  kable_material(c("hover", full_width = T))
r.squared adj.r.squared sigma statistic p.value df df.residual nobs term estimate std.error
0.5457301 0.5310763 0.0575782 37.241372 0.0000000 3 93 97 NA NA NA
NA NA NA 1.377799 0.1715717 NA NA NA (Intercept) 0.0082614 0.0059961
NA NA NA 9.859187 0.0000000 NA NA NA MKT_RF 1.2696321 0.1287766
NA NA NA -1.226341 0.2231664 NA NA NA SMB -0.2756008 0.2247342
NA NA NA -3.507925 0.0006977 NA NA NA HML -0.5255506 0.1498181
aapl <- data.frame(ff_assets$AAPL, predict(fama_aapl), predict(capm_aapl))

plot_fama_aapl<-
ggplot(aapl) +
  geom_point(aes(x = ff_assets$AAPL, y = predict(fama_aapl), color = "Fama-French"), alpha=0.5, size = 2) +
  geom_point(aes(x = ff_assets$AAPL, y = predict(capm_aapl), color = "CAPM Model"), alpha=0.5, size = 2) +
  geom_abline(intercept = 0, color = "#0072B2", size=0.7) +
  labs(title = "<b>Stock Returns vs Predicted Returns AAPL<b>", x = "<b>Stock Returns (AAPL)<b>", y = "<b>Predicted Returns<b>") +
  theme(plot.title = element_text(size = 18, face = "bold"),
        axis.title = element_text(size = 12, face = "bold"),
        axis.text = element_text(size = 10, face = "bold"),
        panel.grid.major = element_blank(),
        panel.grid.minor = element_blank(),
        panel.background = element_blank(),
        axis.line = element_line(color = "gray60"),
        axis.ticks = element_line(color = "gray60"),
        plot.margin = margin(20, 20, 20, 20)) +
  scale_x_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1)) +
  scale_y_continuous(labels = percent_format(), breaks = seq(-0.3, 0.3, 0.1))+
  scale_color_manual(values = c("Fama-French" = "magenta", "CAPM Model" = "purple"),
                     labels = c("Fama-French", "CAPM Model")) +
  guides(color = guide_legend(title = "<b>Models<b>"))


plot_fama_aapl <- ggplotly(plot_fama_aapl)

# Modify plotly layout for legend position
plot_fama_aapl <- plot_fama_aapl %>%
  layout(
    legend = list(
      x = 0.9, y = 0.1, 
      xanchor = "right", yanchor = "bottom",
      bgcolor = "white",
      bordercolor = "white",
      borderwidth = 1
    )
  )

plot_fama_aapl

CONCLUSIONS

From an economic perspective, adding these variables can help identify risk factors, and as such consider other factors that could be relevant when analyzing the returns of a security. In other words, we obtain more information about the behavior of the returns of a stock. In the examples above we noticed how some stocks’ returns showed counter-cyclical behaviors relative to higher book-to-market stocks. These stocks are for large companies, so, the negative Beta makes sense as the model implies that these stocks tend to under perform relative to companies whose ratio is larger.

Furthermore, it helps to price the securities better by exposing the returns to other factors, naturally, that are statistically significant.

From an empirical perspective, as we saw in the examples above, the models improved once we added more variables, although not by much. It also helps identify the variable’s impact on the stock returns, and whether these are relevant or not over time.

Based on the alphas for these stocks, it is difficult to pick one stock over another given that all were non-statistically significant. Even when performing Fama-French, the alphas remained statistically insignificant, therefore, making it difficult to choose based on this indicator.