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.
##
## 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_amznGOOGLE 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.
##
## 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_googlNETFLIX
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.
##
## 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_nflxFACEBOOK 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.
##
## 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_metaAPPLE
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.
##
## 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_aaplCONCLUSIONS
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.