Stocks Submission

Author

Aidan Ho

# Clear environment
remove(list = ls())

# Load packages
library(tidyquant)
Registered S3 method overwritten by 'quantmod':
  method            from
  as.zoo.data.frame zoo 
── Attaching core tidyquant packages ─────────────────────── tidyquant 1.0.12 ──
✔ PerformanceAnalytics 2.1.0      ✔ TTR                  0.24.4
✔ quantmod             0.4.29     ✔ xts                  0.14.2
── Conflicts ────────────────────────────────────────── tidyquant_conflicts() ──
✖ zoo::as.Date()                 masks base::as.Date()
✖ zoo::as.Date.numeric()         masks base::as.Date.numeric()
✖ PerformanceAnalytics::legend() masks graphics::legend()
✖ quantmod::summary()            masks base::summary()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(fpp3)
── Attaching packages ──────────────────────────────────────────── fpp3 1.0.3 ──
✔ tibble      3.3.1     ✔ tsibble     1.2.0
✔ dplyr       1.2.1     ✔ tsibbledata 0.4.1
✔ tidyr       1.3.2     ✔ ggtime      0.2.0
✔ lubridate   1.9.5     ✔ feasts      0.5.0
✔ ggplot2     4.0.3     ✔ fable       0.5.0
── Conflicts ───────────────────────────────────────────────── fpp3_conflicts ──
✖ lubridate::date()    masks base::date()
✖ dplyr::filter()      masks stats::filter()
✖ dplyr::first()       masks xts::first()
✖ tsibble::index()     masks zoo::index()
✖ tsibble::intersect() masks base::intersect()
✖ tsibble::interval()  masks lubridate::interval()
✖ dplyr::lag()         masks stats::lag()
✖ dplyr::last()        masks xts::last()
✖ tsibble::setdiff()   masks base::setdiff()
✖ tsibble::union()     masks base::union()
✖ fable::VAR()         masks tidyquant::VAR()

Attaching package: 'fpp3'

The following object is masked from 'package:PerformanceAnalytics':

    prices
# Download daily stock prices for IBM (starts far before 1991)
df_daily <- tq_get(x = "IBM", 
                   get = "stock.prices", 
                   from = "1970-01-01")

# Aggregate to monthly data
stock_data_monthly <- df_daily %>%
  mutate(month = yearmonth(date)) %>%
  group_by(month) %>%
  summarise(adjusted = mean(adjusted)) %>%
  as_tsibble(index = month)

# Save to CSV
write.csv(x = stock_data_monthly, 
          file = "ibm_monthly_data.csv")

# Fit initial models
fits <- stock_data_monthly %>%
  model(
    arima = ARIMA(adjusted),
    ets   = ETS(adjusted),
    naive = NAIVE(adjusted)
  )
# Calculate split index (80% train, 20% test)
n_total <- nrow(stock_data_monthly)
n_train <- round(0.80 * n_total)

train <- stock_data_monthly[1:n_train, ]
test  <- stock_data_monthly[(n_train + 1):n_total, ]
# Fit forecasting models on training set
models_stock <- model(
  .data = train,
  Drift  = RW(adjusted ~ drift()),
  NAIVE  = NAIVE(adjusted),
  SNAIVE = SNAIVE(adjusted)
)

# Forecast across the test horizon length
h <- nrow(test)
fc_stock <- forecast(models_stock, h = h)

# Plot forecasts against training data
autoplot(object = fc_stock, data = train) + 
  labs(title = "IBM Stock Forecasts", 
       x = "Time", 
       y = "Adjusted Prices")

# Load data
data("EuStockMarkets")

# Extract variables
x <- EuStockMarkets[, "DAX"]
y <- EuStockMarkets[, "CAC"]

# Method A: Calculate slope using Covariance and Variance
slope_manual <- cov(x, y) / var(x)

# Method B: Run standard bivariate OLS regression
model_bivariate <- lm(CAC ~ DAX, data = EuStockMarkets)
slope_lm <- coef(model_bivariate)["DAX"]

# Compare results
cat("Manual slope [cov(x,y)/var(x)]:", slope_manual, "\n")
Manual slope [cov(x,y)/var(x)]: 0.5168872 
cat("Regression slope [lm()]         :", slope_lm, "\n")
Regression slope [lm()]         : 0.5168872 
# Run multivariate regression
model_multivariate <- lm(CAC ~ DAX + SMI, data = EuStockMarkets)

# Extract the multivariate coefficient for DAX
beta1_multivariate <- coef(model_multivariate)["DAX"]

cat("Simple Cov/Var slope         :", slope_manual, "\n")
Simple Cov/Var slope         : 0.5168872 
cat("Multivariate regression slope:", beta1_multivariate, "\n")
Multivariate regression slope: 0.8439822 

Multivariate Regression Explanation

The equation \(\frac{\text{Cov}(\text{DAX}, \text{CAC})}{\text{Var}(\text{DAX})}\) does not hold for a multivariate regression. Normally, it works because \(\text{DAX}\) is the only predictor variable in the model, so its slope absorbs all correlation with \(Y\). In multivariate regression, both \(X\) variables (like \(\text{DAX}\) and \(\text{SMI}\)) are usually correlated with each other, meaning that the simple covariance formula fails.

Stock Forecast Explanation & Buy/Hold/Sell Decision

We can see that the IBM stock will most likely follow the drift pattern and continue increasing over time. The Drift model follows the average historical trajectory from the start of the training period to the end. The positive slope best represents the long-term upward trend for IBM. However, in reality, actual market growth can vastly outperform or deviate from these basic baseline models.

I believe we should HOLD the stock because simple baseline models like Naive, SNaive, and Drift fail to reliably capture real-world market dynamics and macroeconomic factors. While the long-term trend shows positive growth, these basic models either assume a flat price, force rigid annual seasonality, or project a simple straight line with high uncertainty. Therefore, buying solely based on these basic forecasts carries unnecessary financial risk, making holding the position the wisest choice.