Advertisement
binkleym

Simple Stock Over/Undervaluation Analyzer

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