Walt Disney (NYSE: DIS) recently announced significant labor cuts to ease shareholders’ pressure on reducing costs due to rising streaming investment. These cuts and some structural changes in the company have provided some boost to stock prices.
We will examine these price changes based on earnings per share (EPS) and their consensus forecasts. But these EPSs are announced two months after the interested quarter, so their effect reflects on the next quarter. Hence, we will use the lagged values of EPS in our model.
library(tidyverse) library(tidyquant) library(fable) library(feasts) library(tsibble) library(scales) library(sysfonts) library(showtext) library(ggtext) #Getting the daily Disney stock prices from yahoo finance df_dis <- tq_get("DIS") %>% #converting daily prices to a quarterly format tq_transmute(select = adjusted, mutate_fun = to.quarterly, col_rename = "price") %>% mutate(date = as.character(date) %>% yearquarter() ) %>% slice_head(n=-1) #The quarterly DIS EPS prices df_eps <- read_csv("https://raw.githubusercontent.com/mesdi/blog/main/eps.csv") %>% #converting string to quarterly tsibble date mutate(date = parse_date(date, "%b-%y") %>% yearquarter(), fc_eps = #remove the hexadecimal character set str_replace_all(fc_eps, regex("\uFFFD"), "") %>% as.numeric() ) #Merging the data frames df <- df_dis %>% left_join(df_eps) %>% #lagged values of the EPS prices mutate(lag_eps = lag(eps)) %>% na.omit()
We will model our data with dynamic regression ARIMA errors.
#Modeling fit <- df %>% as_tsibble() %>% model(ARIMA(price ~ lag_eps)) #Testing fit %>% gg_tsresiduals()
The above ACF chart of the model shows that all autocorrelation values are inside the 95% limits (dashed lines), which means the ARIMA estimated error distribution (the innovation residuals) that follows a white noise series. I would like to check this with the Ljung–Box test to be sure.
augment(fit) |> features(.innov, ljung_box) # A tibble: 1 x 3 # .model lb_stat lb_pvalue # <chr> <dbl> <dbl> #1 ARIMA(price ~ lag_eps) 0.530 0.466
Because the p-value is higher than 0.05, we would reject the alternative hypothesis, which means there is a correlation in residuals. Hence, our model is valid. As the residuals are similar to those normally distributed, we don’t have to obtain a bootstrapped version of the prediction intervals.
Now, we will forecast the prices for the next two quarters. To do that, we will use the last released EPS value for the 2022 Q4 and the consensus forecast value for the 2023 Q1 as predictors. Remember the lagged effects of the EPS values.
#Forecasting df_future <- df %>% as_tsibble() %>% new_data(2) %>% mutate(lag_eps = c(0.99, 1.19)) fc <- forecast(fit, new_data = df_future) #The upper values of the 80% and 95% prediction intervals fc_PI <- fc %>% hilo() %>% mutate( `80%` = `80%`$upper, `95%` = `95%`$upper ) %>% as_tibble() %>% pivot_longer(c(6,7), names_to = "PI", values_to = "upper") %>% mutate(PI = fct_reorder(PI, upper, .desc = TRUE)) #load fonts(google) font_add_google("Roboto Mono", "Mono") showtext_auto() #Comparison DIS stock prices where EPS values are below or above forecasts #with prediction values for the next two quarters df %>% ggplot(aes(date, price)) + geom_segment(aes(x = date, xend = date, y = 0, yend = price), color = "#9d9897") + geom_point(aes(color = ifelse(lag_eps >= fc_eps, "#ffff00", "#b80f0a")), size = 3) + geom_bar(data = fc_PI, stat = "identity", aes(x =date, y= upper, fill = PI)) + geom_text(data = fc_PI, mapping = aes(date, upper, label = number(round(upper,2), prefix = "$"), fill = PI), position = position_stack(vjust = 0.5), size = 7, family = "Mono") + scale_y_continuous(labels = label_dollar()) + scale_x_yearquarter(date_breaks = "1 year") + scale_color_identity() + scale_fill_manual(values = c("#5e9cd4", "#9ACD32")) + labs( x = "", y = "", title = "Comparison DIS stock prices where EPS values are <span style = 'color:#b80f0a'><b>below</b></span> or <span style = 'color:#ffff00'><b>above</b></span> forecasts", subtitle = "The upper values of the <span style = 'color:#9ACD32'><b>80%</b></span> and <span style = 'color:#5e9cd4'><b>95%</b></span> prediction intervals") + coord_flip() + theme_minimal() + theme( legend.position = "none", text = element_text(family = "Mono", size = 20), plot.title = element_markdown(hjust = 0.5), plot.subtitle = element_markdown(hjust = 0.5), panel.grid.minor.y = element_blank(), plot.background = element_rect(fill = "#f9cb9c", color = NA) )
The above graph shows the stock prices where EPS values are below or above the consensus EPS forecasts, and the upper values of 80% and 95% prediction intervals for 2023 Q1 and 2023 Q2.