Analyzing High Volume Stocks in the S&P 500: Showing Time Series Regression & Forecasting Factors

Introduction

In this statistical analysis, we delve into historical data to uncover the patterns and trends that have shaped the S&P 500 stocks and our goal is to provide insights into the historical trajectory and future prospects of stock movements within this index.

Problem Statement

We aim to provide insights that can enhance the ability to navigate the complexities of the stock market by (a) optimizing portfolio strategies, and (b) mitigating potential risks associated with investing in the S & P 500 index.

Objective

The analysis of the estimation for stock in S&P 500 with categories and time by the time series regression and forecasting factors from 2023 to 2030. 

Methods

This project will use time series forecasting.
The dependent variable is the stock estimate and the independent variables are time and the stock categories. The time series equation model in plot data visualization is the descriptive statistics and the visual analysis used by this project. The linear trend regression equation will be used. Lastly, the assumptions made for this analysis are 95% confidence interval, linear relationship between the dependent and independent varuables, and normally distributed visuals.

Install and load libraries

library("readxl")
library("ggpubr")
## Loading required package: ggplot2
library("tidyverse")
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ lubridate 1.9.3     ✔ tibble    3.2.1
## ✔ purrr     1.0.2     ✔ tidyr     1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library("gclus")
## Loading required package: cluster
library("ggplot2")
theme_set(theme_pubr())

Import data

SP500df <- read_excel("S&P500.xlsx")

Create a time series plot

plot(SP500df$Date, SP500df$Open, type = "l", col = 2, xlab = "Date", 
     ylab = "Price", main = "S&P500 Stocks")
lines(SP500df$Date, SP500df$High, type = "l", col = 3)
lines(SP500df$Date, SP500df$Low, type = "l", col = 4)
lines(SP500df$Date, SP500df$Close, type = "l", col = 5)
lines(SP500df$Date, SP500df$`Adj Close`, type = "l", col = 6)
legend("topleft", c("Open", "High", "Low", "Close", "Adj Close"), lty = 1, 
       col = 2:6)

Interpretation: The time series plot exhibits a positive linear trend pattern.

Plotting just the Highs and Lows for clarity

plot(SP500df$Date, SP500df$High, type = "l", col = 2, xlab = "Date", 
     ylab = "Price", main = "S&P500 Stocks")
lines(SP500df$Date, SP500df$Low, type = "l", col = 3)
legend("topleft", c("High", "Low"), lty = 1, 
       col = 2:3)

Creating time series equation models (linear trend regression)

Converting date to year

SP500df$Date <- as.numeric(format(SP500df$Date, "%Y"))
colnames(SP500df) <- c("Year", "Open", "High", "Low", "Close", "Adj Close", 
                       "Volume") # Renaming columns

Prerequiste- Removing scientific notation

options(scipen = 999)

Open

model_open <- lm(SP500df$Open ~ Year, data = SP500df)
summary(model_open)
## 
## Call:
## lm(formula = SP500df$Open ~ Year, data = SP500df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1044.13  -252.15   -34.22   214.83  1951.37 
## 
## Coefficients:
##                Estimate  Std. Error t value            Pr(>|t|)    
## (Intercept) -160967.195    4050.097  -39.74 <0.0000000000000002 ***
## Year             81.006       2.022   40.07 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 468.9 on 451 degrees of freedom
## Multiple R-squared:  0.7807, Adjusted R-squared:  0.7802 
## F-statistic:  1606 on 1 and 451 DF,  p-value: < 0.00000000000000022
Interpretation:
The linear trend equation is y = -160967.20 + 81.01*t
The R-squared shows the relationship between the independent and dependent variable is relatively strong, so the model is good.
The p-value is below the alpha of 0.05, meaning the model is significant.

High

model_high <- lm(SP500df$High ~ Year, data = SP500df)
summary(model_high)
## 
## Call:
## lm(formula = SP500df$High ~ Year, data = SP500df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1001.25  -274.51   -37.06   219.34  1969.39 
## 
## Coefficients:
##                Estimate  Std. Error t value            Pr(>|t|)    
## (Intercept) -166471.402    4201.145  -39.62 <0.0000000000000002 ***
## Year             83.776       2.097   39.95 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 486.4 on 451 degrees of freedom
## Multiple R-squared:  0.7797, Adjusted R-squared:  0.7792 
## F-statistic:  1596 on 1 and 451 DF,  p-value: < 0.00000000000000022
Interpretation:
The linear trend equation is y = -166471.40 + 83.78*t
The R-squared shows the relationship between the independent and dependent variable is relatively strong, so the model is good.
The p-value is below the alpha of 0.05, meaning the model is significant.

Low

model_low <- lm(SP500df$Low ~ Year, data = SP500df)
summary(model_low)
## 
## Call:
## lm(formula = SP500df$Low ~ Year, data = SP500df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1043.23  -230.34   -25.45   219.23  1914.20 
## 
## Coefficients:
##                Estimate  Std. Error t value            Pr(>|t|)    
## (Intercept) -154955.380    3884.299  -39.89 <0.0000000000000002 ***
## Year             77.982       1.939   40.22 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 449.7 on 451 degrees of freedom
## Multiple R-squared:  0.782,  Adjusted R-squared:  0.7815 
## F-statistic:  1618 on 1 and 451 DF,  p-value: < 0.00000000000000022
Interpretation:
The linear trend equation is y = -161554.88 + 81.30*t
The R-squared shows the relationship between the independent and dependent variable is relatively strong, so the model is good.
The p-value is below the alpha of 0.05, meaning the model is significant.

Close

model_close <- lm(SP500df$Close ~ Year, data = SP500df)
summary(model_close)
## 
## Call:
## lm(formula = SP500df$Close ~ Year, data = SP500df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1047.14  -257.67   -29.72   214.92  2008.31 
## 
## Coefficients:
##               Estimate Std. Error t value            Pr(>|t|)    
## (Intercept) -161554.88    4046.80  -39.92 <0.0000000000000002 ***
## Year             81.30       2.02   40.25 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 468.5 on 451 degrees of freedom
## Multiple R-squared:  0.7822, Adjusted R-squared:  0.7818 
## F-statistic:  1620 on 1 and 451 DF,  p-value: < 0.00000000000000022
Interpretation: 
The linear trend equation is y = -161554.88 + 81.30*t
The R-squared shows the relationship between the independent and dependent variable is relatively strong, so the model is good.
The p-value is below the alpha of 0.05, meaning the model is significant.

Adjusted Close

model_adj_close <- lm(SP500df$`Adj Close` ~ Year, data = SP500df)
summary(model_adj_close)
## 
## Call:
## lm(formula = SP500df$`Adj Close` ~ Year, data = SP500df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1047.14  -257.67   -29.72   214.92  2008.31 
## 
## Coefficients:
##               Estimate Std. Error t value            Pr(>|t|)    
## (Intercept) -161554.88    4046.80  -39.92 <0.0000000000000002 ***
## Year             81.30       2.02   40.25 <0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 468.5 on 451 degrees of freedom
## Multiple R-squared:  0.7822, Adjusted R-squared:  0.7818 
## F-statistic:  1620 on 1 and 451 DF,  p-value: < 0.00000000000000022
Interpretation:
The linear trend equation is y = -161554.88 + 81.30*t
The R-squared shows the relationship between the independent and dependent variable is relatively strong, so the model is good.
The p-value is below the alpha of 0.05, meaning the model is significant.

Calculating the mean absolute percentage error

Creating a dataframe for 1985-2022

new_data2 <- data.frame(Year = c(1985:2022))

Open

prediction_open <- data.frame(predict(model_open, newdata = new_data2))
colnames(prediction_open) <- "Open"
avgs_open <- aggregate(x = SP500df$Open, by = list(SP500df$Year), FUN = mean)
colnames(avgs_open) <- c("Year", "Open")
mean(abs((avgs_open$Open - prediction_open$Open)/avgs_open$Open)) * 100
## [1] 34.93284
Interpretation:
The MAPE for open is 34.93% this means it is relatively accurate.

High

prediction_high <- data.frame(predict(model_high, newdata = new_data2))
colnames(prediction_high) <- "High"
avgs_high <- aggregate(x = SP500df$High, by = list(SP500df$Year), FUN = mean)
colnames(avgs_high) <- c("Year", "High")
mean(abs((avgs_high$High - prediction_high$High)/avgs_high$High)) * 100
## [1] 35.25953
Interpretation: 
The MAPE for high is 35.26% this means it is relatively accurate.

Low

prediction_low <- data.frame(predict(model_low, newdata = new_data2))
colnames(prediction_low) <- "Low"
avgs_low <- aggregate(x = SP500df$Low, by = list(SP500df$Year), FUN = mean)
colnames(avgs_low) <- c("Year", "Low")
mean(abs((avgs_low$Low - prediction_low$Low)/avgs_low$Low)) * 100
## [1] 34.52231
Interpretation: 
The MAPE for low is 34.52% this means it is relatively accurate.

Close

prediction_close <- data.frame(predict(model_close, newdata = new_data2))
colnames(prediction_close) <- "Close"
avgs_close <- aggregate(x = SP500df$Close, by = list(SP500df$Year), FUN = mean)
colnames(avgs_close) <- c("Year", "Close")
mean(abs((avgs_close$Close - prediction_close$Close)/avgs_close$Close)) * 100
## [1] 34.78592
Interpretation: 
The MAPE for close is 34.79% this means it is relatively accurate.

Adjusted Close

prediction_adj_close <- data.frame(predict(model_adj_close, newdata = new_data2))
colnames(prediction_adj_close) <- "Adj Close"
avgs_adj_close <- aggregate(x = SP500df$`Adj Close`, by = list(SP500df$Year), FUN = mean)
colnames(avgs_adj_close) <- c("Year", "Adj Close")
mean(abs((avgs_adj_close$`Adj Close` - prediction_adj_close$`Adj Close`)/avgs_adj_close$`Adj Close`)) * 100
## [1] 34.78592
Interpretation:
The MAPE for adjusted close is 34.79% this means it is relatively accurate.

Predicting 2023-2030

Creating a dataframe for 2023-2030

new_data <- data.frame(Year = c(2023:2030))

Open

Open_prediction_2023_to_2030 <- predict(model_open, newdata = new_data)
Open_prediction_2023_to_2030
##        1        2        3        4        5        6        7        8 
## 2907.779 2988.785 3069.791 3150.797 3231.803 3312.809 3393.815 3474.821

High

High_prediction_2023_to_2030 <- predict(model_high, newdata = new_data)
High_prediction_2023_to_2030
##        1        2        3        4        5        6        7        8 
## 3007.095 3090.871 3174.646 3258.422 3342.198 3425.974 3509.750 3593.526

Low

Low_prediction_2023_to_2030 <- predict(model_low, newdata = new_data)
Low_prediction_2023_to_2030
##        1        2        3        4        5        6        7        8 
## 2801.766 2879.748 2957.729 3035.711 3113.693 3191.675 3269.656 3347.638

Close

Close_prediction_2023_to_2030 <- predict(model_close, newdata = new_data)
Close_prediction_2023_to_2030
##        1        2        3        4        5        6        7        8 
## 2920.472 3001.775 3083.078 3164.381 3245.683 3326.986 3408.289 3489.591

Adjusted close

AdjClose_prediction_2023_to_2030 <- predict(model_adj_close, newdata = new_data)
AdjClose_prediction_2023_to_2030
##        1        2        3        4        5        6        7        8 
## 2920.472 3001.775 3083.078 3164.381 3245.683 3326.986 3408.289 3489.591

Overall interpretation for all results

There is significant drop for the financial crisis in 2008, and a relative decrease in the stock market after eleven years can be found from the data. A relatively consistent growth across the years is available to be seen as a safe investment for the fund. 

Conclusion and Business Recommendations

We can see a major drop during the 2008 financial crisis and start of 2019 indicating that there has been a relative drop in the stock market

Despite having major highs and lows, this fund can be seen as a fairly safe investment because of relatively consistent growth across the years

The fund is subject to volatility, with periods of rapid growth and quick decline. This is generally caused by international events, economic status, etc. Thus long-term investments are ideal to ride out market cycles.

We can use the linear trend to make possible predictions for any natural disasters that may have an affect on the current stock trends.