Advertisement
binkleym

Simple Stock Over/Undervaluation Analyzer

Aug 11th, 2021 (edited)
2,824
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
R 7.27 KB | None | 0 0
  1. ################################################################################
  2. ### Fetch data on the selected stock and analyze for under/overvaluation.  This
  3. ### is a simple method which involves determining the long-term trend of the
  4. ### stock, and seeing if it is currently above/below that trend.   It should
  5. ### work decently on stocks that are "well behaved" (long track record of
  6. ### continuous gains, and a high r^2)
  7. ################################################################################
  8.  
  9. library(tidyverse)
  10.  
  11. ### Stock ticker for the stock of interest
  12. stock <- "BRK-A"  ; stock_txt <- "Berkshire Hathaway class A"
  13.  
  14. ### What are the start/end dates you are interested in
  15. date_start <- as.Date("1995-01-01")
  16. date_end   <- as.Date(Sys.Date())
  17.  
  18.  
  19. ################################################################################
  20. ### No changes should be necessary below here.
  21. ################################################################################
  22. ### Function to add recession bars to graphs
  23. geom_recession_bars <- function(date_start, date_end, fill = "darkseagreen4") {
  24.  
  25.   date_start <- as.Date(date_start, origin = "1970-01-01")
  26.   date_end   <- as.Date(date_end,   origin = "1970-01-01")
  27.  
  28.   recessions_tibble <-
  29.     tribble(
  30.       ~peak,     ~trough,
  31.       "1953-07-01", "1954-05-01",
  32.       "1957-08-01", "1958-04-01",
  33.       "1960-04-01", "1961-02-01",
  34.       "1969-12-01", "1970-11-01",
  35.       "1973-11-01", "1975-03-01",
  36.       "1980-01-01", "1980-07-01",
  37.       "1981-07-01", "1982-11-01",
  38.       "1990-07-01", "1991-03-01",
  39.       "2001-03-01", "2001-11-01",
  40.       "2007-12-01", "2009-06-01",
  41.       "2020-02-01", "2020-04-01"
  42.     ) %>%
  43.     mutate(peak   = as.Date(peak), trough = as.Date(trough))
  44.  
  45.   recessions_trim <- recessions_tibble %>%
  46.     filter(peak   >= min(date_start) & trough <= max(date_end))
  47.  
  48.   if (nrow(recessions_trim) > 0) {
  49.     recession_bars <- geom_rect(data = recessions_trim, inherit.aes = F,
  50.                                 fill = fill, alpha = 0.25,
  51.                                 aes(xmin = as.Date(peak,   origin = "1970-01-01"),
  52.                                     xmax = as.Date(trough, origin = "1970-01-01"),
  53.                                     ymin = -Inf, ymax = +Inf))
  54.   } else {
  55.     recession_bars <- geom_blank()
  56.   }
  57. }
  58.  
  59. ### Fetch stock prices from Yahoo Finance
  60. prices <-
  61.   quantmod::getSymbols(stock, env = NULL, src = "yahoo", from = date_start, to = date_end) %>%
  62.   zoo::fortify.zoo() %>%
  63.   select(Index, 2) %>%
  64.   tsibble::as_tsibble() %>%
  65.   rename(date = Index, value = 2) %>%
  66.   filter(value != 0) %>%
  67.   arrange(date) %>%
  68.   mutate(
  69.     log_value = log(value),
  70.     decimal_date = decimal_date(date)
  71.   )
  72.  
  73. ### Do a linear regression and pluck out the coefficients
  74. fit_prices <- lm(log_value ~ decimal_date, data = prices %>% filter(value != 0))
  75.  
  76. coeff1 <- fit_prices %>% broom::tidy() %>% filter(term == "(Intercept)") %>% pull("estimate")
  77. coeff2 <- fit_prices %>% broom::tidy() %>% filter(term == "decimal_date") %>% pull("estimate")
  78. adj_r2 <- fit_prices %>% broom::glance() %>% pull("adj.r.squared")
  79.  
  80. growth <- paste((coeff2 * 100) %>% round(digits = 1), "%/yr", sep = "")
  81. doubling_time <- paste((log(2) / log(1 + coeff2)) %>% round(digits = 1), "yrs")
  82.  
  83. ### Use the regression coefficients to compute the trend
  84. prices <-
  85.   prices %>%
  86.   mutate(fit_log_value = coeff1 + coeff2 * decimal_date) %>%
  87.   mutate(fit_value = exp(fit_log_value)) %>%
  88.   mutate(value_per = 1 - (fit_value / value))
  89.  
  90. valuation <- prices %>% arrange(date) %>% tail(n = 1) %>% pull(value_per)
  91.  
  92. # Determine number of years of trend growth represented by over/undervaluation
  93. years <- (abs(valuation) / coeff2) %>% round(digits = 1)
  94.  
  95. # Format string to display valuation in useful form
  96. valuation <- (100 * valuation) %>% round(digits = 1)
  97.  
  98. fair_price <- format(round((prices %>% tail(n = 1) %>% pull(value))*(1 - valuation / 100), digits = 2), big.mark = ",")
  99.  
  100. if (valuation > 0) {
  101.   valuation_txt <- paste("overvalued by ",  abs(valuation), "% (", years, " years growth), fair price = $", fair_price, sep = "")
  102. } else {
  103.   valuation_txt <- paste("undervalued by ", abs(valuation), "% (", years, " years growth), fair price = $", fair_price, sep = "")
  104. }
  105.  
  106.  
  107. ##########################################################################
  108. ### Graph 1:  Price of stock
  109. ##########################################################################
  110.  
  111. p_norm <-
  112.   ggplot(data = prices) +
  113.   theme_bw() +
  114.   theme(legend.title = element_blank()) +
  115.   geom_line(aes(x = date, y = value)) +
  116.   geom_line(aes(x = date, y = fit_value)) +
  117.   geom_recession_bars(min(prices$date), max(prices$date)) +
  118.   scale_x_date(breaks = scales::pretty_breaks(10)) +
  119.   scale_y_continuous(breaks = scales::pretty_breaks(10), labels = scales::label_comma()) +
  120.   labs(title = paste("Current stock price: $", format(round(prices %>% tail(n = 1) %>% pull(value), digits = 2), big.mark = ","), ", growth = ", growth, ", doubling time = ", doubling_time, sep = ""),
  121.        caption = "", x = "", y = "US $")
  122. print(p_norm)
  123.  
  124. ##########################################################################
  125. ### Graph 2:  log(Price of stock)
  126. ##########################################################################
  127.  
  128. caption  <- paste("Data retrieved from Yahoo Finance on", format(Sys.time(), "%B %d, %Y at %I:%M %p %Z"))
  129.  
  130. p_log <-
  131.   ggplot(data = prices) +
  132.   theme_bw() +
  133.   theme(legend.title = element_blank()) +
  134.   geom_line(aes(x = date, y = log_value)) +
  135.   geom_line(aes(x = date, y = fit_log_value)) +
  136.   geom_recession_bars(min(prices$date), max(prices$date)) +
  137.   scale_x_date(breaks = scales::pretty_breaks(10)) +
  138.   scale_y_continuous(breaks = scales::pretty_breaks(10)) +
  139.   labs(title = paste("log(Stock Price) adj R^2 = ", adj_r2 %>% round(digits = 3), sep = ""),
  140.        caption = "", x = "", y = "log(US $)")
  141. print(p_log)
  142.  
  143. ##########################################################################
  144. ### Graph 3:  Standard deviation above/below trend
  145. ##########################################################################
  146.  
  147. # Compute mean / standard deviation of % above/below trend
  148. mean_stock <- prices %>% filter(value != 0) %>% pull(value_per) %>% mean()
  149. sd_stock   <- prices %>% filter(value != 0) %>% pull(value_per) %>% sd()
  150.  
  151. p_trend <-
  152.   ggplot(data = prices) +
  153.   theme_bw() +
  154.   theme(legend.title = element_blank()) +
  155.   geom_line(aes(x = date, y = 1 - (fit_value / value))) +
  156.   geom_hline(yintercept = 0, linetype = "dotted") +
  157.   geom_recession_bars(min(prices$date), max(prices$date)) +
  158.   scale_x_date(breaks = scales::pretty_breaks(10)) +
  159.   scale_y_continuous(breaks = scales::pretty_breaks(10),
  160.                      labels = scales::percent_format(accuracy = 1),
  161.                      sec.axis = sec_axis(~ (. - mean_stock) / sd_stock,
  162.                                          name = "Z-Score",
  163.                                          breaks = scales::pretty_breaks(6))
  164.   ) +
  165.   labs(title = paste("Detrended stock price, ", valuation_txt, sep = ""),
  166.        caption = caption, x = "", y = "%")
  167. print(p_trend)
  168.  
  169. ### Print final graph
  170. title  <- cowplot::ggdraw() + cowplot::draw_label(paste(stock_txt, " (", stock, ")", sep = ""), fontface = "bold")
  171. print(cowplot::plot_grid(title, p_norm, p_log, p_trend, nrow = 4, ncol = 1, align = "v", rel_heights = c(0.1, 1, 1, 1)))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement