Libraries and Data

library(fpp3)
library(ggtime)
library(kableExtra)
library(latex2exp)
library(readxl)
library(quantmod)
library(xts)

Read Data and Set Starting Value

FYI-In order to knit, you cannot rely on reading from external sources. I downloaded the symbols and saved them into a dataframe.

#getSymbols(c("MSFT", "CSCO", "BABA", "AMZN"), src = "yahoo", from = "2018-01-01", to = "2022-12-31", 
#           auto.assign = TRUE, periodicity = 'monthly')
#total=as.data.frame(cbind(SPY$SPY.Adjusted, QQQ$QQQ.Adjusted,GLD$GLD.Adjusted,EEM$EEM.Adjusted))
#write.csv(total,'hts_example.csv', row.names = FALSE)
total=read.csv('hts_example.csv')
str(total)
## 'data.frame':    60 obs. of  4 variables:
##  $ SPY.Adjusted: num  170 170 181 182 185 ...
##  $ QQQ.Adjusted: num  98.4 96.9 103.2 100.2 104.6 ...
##  $ GLD.Adjusted: num  107 119 118 124 116 ...
##  $ EEM.Adjusted: num  26.4 26.2 29.5 29.7 28.6 ...
initial=1000

Manipulate Data & Build Time Series

#Accumulation Rate
total[2:60,]=1+(total[2:60,]-total[1:59,])/total[1:59,] 

#Value by Month
for (i in 2:60) total[i,]=total[i-1, ]*total[i,]

#Date
total$Date=yearmonth(seq(as.Date("2018-01-01"), as.Date("2022/12/31"), by="months"))
colnames(total)=c("MSFT", "CSCO", "BABA", "AMZN", "Date")

#Train / Test
train=total[1:48,]
test=total[49:60,]

# Time Series
myts=total%>%as_tsibble(index=Date)
traints=train%>%as_tsibble(index=Date)
testts=test%>%as_tsibble(index=Date)

# Pivot Longer
temp=myts%>%pivot_longer(!c(Date), names_to='Stock', values_to='Value')
train=traints%>%pivot_longer(!c(Date), names_to='Stock', values_to='Value')
test=testts%>%pivot_longer(!c(Date), names_to='Stock', values_to='Value')

Assign Sector

temp$Sector=rep('Tech', nrow(temp))
train$Sector=rep('Tech', nrow(train))
test$Sector=rep('Tech', nrow(test))

temp$Sector[temp$Stock=="BABA"|temp$Stock=="AMZN"]="Store"
train$Sector[train$Stock=="BABA"|train$Stock=="AMZN"]="Store"
test$Sector[test$Stock=="BABA"|test$Stock=="AMZN"]="Store"

temp%>%autoplot()

# Aggregate & Plot

# Aggregate time series for plotting
myagg <- temp |>
  aggregate_key(
    Stock / Sector,
    Value = sum(Value)
  )

myagg |>
  filter(is_aggregated(Sector)) |>
  autoplot(Value) +
  labs(
    y = "Value",
    x = "Date",
    title = "Value of Investment"
  ) +
  facet_wrap(
    vars(Stock),
    scales = "free_y",
    ncol = 3
  ) +
  theme(
    legend.position = "none"
  )

Model and Plot

# ==========================================================
# BOTTOM-UP FORECAST
# ==========================================================

myagg2 <- train |>
  aggregate_key(
    Stock,
    Value = mean(Value)
  )

m1 <- myagg2 |>
  model(
    ets = ETS(Value)
  ) |>
  reconcile(
    bu = bottom_up(ets)
  )

aug1 <- augment(m1)

f1 <- m1 |>
  forecast(h = 12)

# level = NULL avoids the ggdist / ggplot2 4.x
# forecast-interval rendering bug
autoplot(
  f1,
  temp,
  level = NULL
) +
  labs(
    title = "Bottom-Up Forecast",
    x = "Date",
    y = "Value"
  )

acc1 <- f1 |>
  accuracy(
    test |>
      aggregate_key(
        Stock,
        Value = mean(Value)
      )
  )


# ==========================================================
# MIDDLE-OUT FORECAST
# ==========================================================

myagg3 <- train |>
  aggregate_key(
    Sector / Stock,
    Value = mean(Value)
  )

m2 <- myagg3 |>
  model(
    ets2 = ETS(Value)
  ) |>
  reconcile(
    md = middle_out(ets2)
  )

f2 <- m2 |>
  forecast(h = 12)

actual_hierarchy <- temp |>
  aggregate_key(
    Sector / Stock,
    Value = mean(Value)
  )

autoplot(
  f2,
  actual_hierarchy,
  level = NULL
) +
  facet_wrap(
    ~ Stock + Sector,
    scales = "free_y"
  ) +
  labs(
    title = "Middle-Out Forecast",
    x = "Date",
    y = "Value"
  )

acc2 <- f2 |>
  accuracy(
    test |>
      aggregate_key(
        Sector / Stock,
        Value = mean(Value)
      )
  )


# ==========================================================
# TOP-DOWN FORECAST
# ==========================================================

myagg4 <- train |>
  aggregate_key(
    Sector / Stock,
    Value = mean(Value)
  )

m3 <- myagg4 |>
  model(
    ets3 = ETS(Value)
  ) |>
  reconcile(
    td = top_down(ets3)
  )

f3 <- m3 |>
  forecast(h = 12)

autoplot(
  f3,
  actual_hierarchy,
  level = NULL
) +
  facet_wrap(
    ~ Stock + Sector,
    scales = "free_y"
  ) +
  labs(
    title = "Top-Down Forecast",
    x = "Date",
    y = "Value"
  )

acc3 <- f3 |>
  accuracy(
    test |>
      aggregate_key(
        Sector / Stock,
        Value = mean(Value)
      )
  )


# ==========================================================
# MINIMUM TRACE (MinT) FORECAST
# ==========================================================

myagg5 <- train |>
  aggregate_key(
    Sector / Stock,
    Value = mean(Value)
  )

m5 <- myagg5 |>
  model(
    ets5 = ETS(Value)
  ) |>
  reconcile(
    mint = min_trace(ets5)
  )

f5 <- m5 |>
  forecast(h = 12)

autoplot(
  f5,
  actual_hierarchy,
  level = NULL
) +
  facet_wrap(
    ~ Stock + Sector,
    scales = "free_y"
  ) +
  labs(
    title = "Minimum Trace (MinT) Forecast",
    x = "Date",
    y = "Value"
  )

acc5 <- f5 |>
  accuracy(
    test |>
      aggregate_key(
        Sector / Stock,
        Value = mean(Value)
      )
  )

Glances

glance(m1)
## # A tibble: 10 × 10
##    Stock        .model    sigma2 log_lik   AIC  AICc   BIC   MSE  AMSE    MAE
##    <chr*>       <chr>      <dbl>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl>
##  1 AMZN         ets     2.81       -117.  239.  240.  245.  2.69  4.71 1.29  
##  2 AMZN         bu      2.81       -117.  239.  240.  245.  2.69  4.71 1.29  
##  3 BABA         ets    21.9        -166.  338.  338.  343. 21.0  39.4  3.51  
##  4 BABA         bu     21.9        -166.  338.  338.  343. 21.0  39.4  3.51  
##  5 CSCO         ets     0.00160    -175.  360.  361.  369. 39.2  61.9  0.0305
##  6 CSCO         bu      0.00160    -175.  360.  361.  369. 39.2  61.9  0.0305
##  7 MSFT         ets     0.000995   -186.  383.  384.  392. 59.0  88.4  0.0220
##  8 MSFT         bu      0.000995   -186.  383.  384.  392. 59.0  88.4  0.0220
##  9 <aggregated> ets     0.000790   -155.  319.  320.  328. 14.6  22.6  0.0211
## 10 <aggregated> bu      0.000790   -155.  319.  320.  328. 14.6  22.6  0.0211
glance(m2)
## # A tibble: 14 × 11
##    Sector       Stock       .model  sigma2 log_lik   AIC  AICc   BIC   MSE  AMSE
##    <chr*>       <chr*>      <chr>    <dbl>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 Store        AMZN        ets2   2.81e+0   -117.  239.  240.  245.  2.69  4.71
##  2 Store        AMZN        md     2.81e+0   -117.  239.  240.  245.  2.69  4.71
##  3 Store        BABA        ets2   2.19e+1   -166.  338.  338.  343. 21.0  39.4 
##  4 Store        BABA        md     2.19e+1   -166.  338.  338.  343. 21.0  39.4 
##  5 Store        <aggregate… ets2   7.39e+0   -140.  286.  286.  291.  7.08 13.7 
##  6 Store        <aggregate… md     7.39e+0   -140.  286.  286.  291.  7.08 13.7 
##  7 Tech         CSCO        ets2   1.60e-3   -175.  360.  361.  369. 39.2  61.9 
##  8 Tech         CSCO        md     1.60e-3   -175.  360.  361.  369. 39.2  61.9 
##  9 Tech         MSFT        ets2   9.95e-4   -186.  383.  384.  392. 59.0  88.4 
## 10 Tech         MSFT        md     9.95e-4   -186.  383.  384.  392. 59.0  88.4 
## 11 Tech         <aggregate… ets2   1.15e-3   -180.  370.  371.  379. 46.8  72.0 
## 12 Tech         <aggregate… md     1.15e-3   -180.  370.  371.  379. 46.8  72.0 
## 13 <aggregated> <aggregate… ets2   7.90e-4   -155.  319.  320.  328. 14.6  22.6 
## 14 <aggregated> <aggregate… md     7.90e-4   -155.  319.  320.  328. 14.6  22.6 
## # ℹ 1 more variable: MAE <dbl>
glance(m3)
## # A tibble: 14 × 11
##    Sector       Stock       .model  sigma2 log_lik   AIC  AICc   BIC   MSE  AMSE
##    <chr*>       <chr*>      <chr>    <dbl>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 Store        AMZN        ets3   2.81e+0   -117.  239.  240.  245.  2.69  4.71
##  2 Store        AMZN        td     2.81e+0   -117.  239.  240.  245.  2.69  4.71
##  3 Store        BABA        ets3   2.19e+1   -166.  338.  338.  343. 21.0  39.4 
##  4 Store        BABA        td     2.19e+1   -166.  338.  338.  343. 21.0  39.4 
##  5 Store        <aggregate… ets3   7.39e+0   -140.  286.  286.  291.  7.08 13.7 
##  6 Store        <aggregate… td     7.39e+0   -140.  286.  286.  291.  7.08 13.7 
##  7 Tech         CSCO        ets3   1.60e-3   -175.  360.  361.  369. 39.2  61.9 
##  8 Tech         CSCO        td     1.60e-3   -175.  360.  361.  369. 39.2  61.9 
##  9 Tech         MSFT        ets3   9.95e-4   -186.  383.  384.  392. 59.0  88.4 
## 10 Tech         MSFT        td     9.95e-4   -186.  383.  384.  392. 59.0  88.4 
## 11 Tech         <aggregate… ets3   1.15e-3   -180.  370.  371.  379. 46.8  72.0 
## 12 Tech         <aggregate… td     1.15e-3   -180.  370.  371.  379. 46.8  72.0 
## 13 <aggregated> <aggregate… ets3   7.90e-4   -155.  319.  320.  328. 14.6  22.6 
## 14 <aggregated> <aggregate… td     7.90e-4   -155.  319.  320.  328. 14.6  22.6 
## # ℹ 1 more variable: MAE <dbl>
glance(m5)
## # A tibble: 14 × 11
##    Sector       Stock       .model  sigma2 log_lik   AIC  AICc   BIC   MSE  AMSE
##    <chr*>       <chr*>      <chr>    <dbl>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 Store        AMZN        ets5   2.81e+0   -117.  239.  240.  245.  2.69  4.71
##  2 Store        AMZN        mint   2.81e+0   -117.  239.  240.  245.  2.69  4.71
##  3 Store        BABA        ets5   2.19e+1   -166.  338.  338.  343. 21.0  39.4 
##  4 Store        BABA        mint   2.19e+1   -166.  338.  338.  343. 21.0  39.4 
##  5 Store        <aggregate… ets5   7.39e+0   -140.  286.  286.  291.  7.08 13.7 
##  6 Store        <aggregate… mint   7.39e+0   -140.  286.  286.  291.  7.08 13.7 
##  7 Tech         CSCO        ets5   1.60e-3   -175.  360.  361.  369. 39.2  61.9 
##  8 Tech         CSCO        mint   1.60e-3   -175.  360.  361.  369. 39.2  61.9 
##  9 Tech         MSFT        ets5   9.95e-4   -186.  383.  384.  392. 59.0  88.4 
## 10 Tech         MSFT        mint   9.95e-4   -186.  383.  384.  392. 59.0  88.4 
## 11 Tech         <aggregate… ets5   1.15e-3   -180.  370.  371.  379. 46.8  72.0 
## 12 Tech         <aggregate… mint   1.15e-3   -180.  370.  371.  379. 46.8  72.0 
## 13 <aggregated> <aggregate… ets5   7.90e-4   -155.  319.  320.  328. 14.6  22.6 
## 14 <aggregated> <aggregate… mint   7.90e-4   -155.  319.  320.  328. 14.6  22.6 
## # ℹ 1 more variable: MAE <dbl>