Libraries and Data

require(feasts)
## Loading required package: feasts
## Warning: package 'feasts' was built under R version 4.3.3
## Loading required package: fabletools
## Warning: package 'fabletools' was built under R version 4.3.3
require(fpp3)
## Loading required package: fpp3
## Warning: package 'fpp3' was built under R version 4.3.2
## ── Attaching packages ────────────────────────────────────────────── fpp3 0.5 ──
## ✔ tibble      3.2.1     ✔ ggplot2     3.5.1
## ✔ dplyr       1.1.4     ✔ tsibble     1.1.4
## ✔ tidyr       1.3.0     ✔ tsibbledata 0.4.1
## ✔ lubridate   1.9.3     ✔ fable       0.3.3
## Warning: package 'tibble' was built under R version 4.3.2
## Warning: package 'dplyr' was built under R version 4.3.2
## Warning: package 'tidyr' was built under R version 4.3.2
## Warning: package 'lubridate' was built under R version 4.3.2
## Warning: package 'ggplot2' was built under R version 4.3.3
## Warning: package 'tsibble' was built under R version 4.3.3
## Warning: package 'tsibbledata' was built under R version 4.3.2
## Warning: package 'fable' was built under R version 4.3.2
## ── Conflicts ───────────────────────────────────────────────── fpp3_conflicts ──
## ✖ lubridate::date()    masks base::date()
## ✖ dplyr::filter()      masks stats::filter()
## ✖ tsibble::intersect() masks base::intersect()
## ✖ tsibble::interval()  masks lubridate::interval()
## ✖ dplyr::lag()         masks stats::lag()
## ✖ tsibble::setdiff()   masks base::setdiff()
## ✖ tsibble::union()     masks base::union()
require(ggplot2)
require(kableExtra)
## Loading required package: kableExtra
## Warning: package 'kableExtra' was built under R version 4.3.3
## 
## Attaching package: 'kableExtra'
## The following object is masked from 'package:dplyr':
## 
##     group_rows
require(latex2exp)
## Loading required package: latex2exp
## Warning: package 'latex2exp' was built under R version 4.3.2
require(readxl)
## Loading required package: readxl
## Warning: package 'readxl' was built under R version 4.3.2
require(tidyverse)
## Loading required package: tidyverse
## Warning: package 'tidyverse' was built under R version 4.3.2
## Warning: package 'readr' was built under R version 4.3.2
## Warning: package 'purrr' was built under R version 4.3.2
## Warning: package 'stringr' was built under R version 4.3.2
## Warning: package 'forcats' was built under R version 4.3.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats 1.0.0     ✔ readr   2.1.4
## ✔ purrr   1.0.2     ✔ stringr 1.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter()          masks stats::filter()
## ✖ kableExtra::group_rows() masks dplyr::group_rows()
## ✖ tsibble::interval()      masks lubridate::interval()
## ✖ dplyr::lag()             masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
require(magrittr)
## Loading required package: magrittr
## Warning: package 'magrittr' was built under R version 4.3.2
## 
## Attaching package: 'magrittr'
## 
## The following object is masked from 'package:purrr':
## 
##     set_names
## 
## The following object is masked from 'package:tidyr':
## 
##     extract
require(quantmod)
## Loading required package: quantmod
## Warning: package 'quantmod' was built under R version 4.3.3
## Loading required package: xts
## Warning: package 'xts' was built under R version 4.3.3
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 4.3.2
## 
## Attaching package: 'zoo'
## 
## The following object is masked from 'package:tsibble':
## 
##     index
## 
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## 
## 
## ######################### Warning from 'xts' package ##########################
## #                                                                             #
## # The dplyr lag() function breaks how base R's lag() function is supposed to  #
## # work, which breaks lag(my_xts). Calls to lag(my_xts) that you type or       #
## # source() into this session won't work correctly.                            #
## #                                                                             #
## # Use stats::lag() to make sure you're not using dplyr::lag(), or you can add #
## # conflictRules('dplyr', exclude = 'lag') to your .Rprofile to stop           #
## # dplyr from breaking base R's lag() function.                                #
## #                                                                             #
## # Code in packages is not affected. It's protected by R's namespace mechanism #
## # Set `options(xts.warn_dplyr_breaks_lag = FALSE)` to suppress this warning.  #
## #                                                                             #
## ###############################################################################
## 
## Attaching package: 'xts'
## 
## The following objects are masked from 'package:dplyr':
## 
##     first, last
## 
## Loading required package: TTR
## Warning: package 'TTR' was built under R version 4.3.2
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
require(tidyverse)
require(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()
## Plot variable not specified, automatically selected `.vars = Value`

# Aggregate & Plot

#Aggregate TS For Plotting
myagg <- temp |>
  aggregate_key(Stock/Sector,Value=sum(Value))
myagg
## # A tsibble: 540 x 4 [1M]
## # Key:       Stock, Sector [9]
##        Date Stock        Sector       Value
##       <mth> <chr*>       <chr*>       <dbl>
##  1 2018 Jan <aggregated> <aggregated>  402.
##  2 2018 Feb <aggregated> <aggregated>  412.
##  3 2018 Mar <aggregated> <aggregated>  431.
##  4 2018 Apr <aggregated> <aggregated>  436.
##  5 2018 May <aggregated> <aggregated>  435.
##  6 2018 Jun <aggregated> <aggregated>  443.
##  7 2018 Jul <aggregated> <aggregated>  463.
##  8 2018 Aug <aggregated> <aggregated>  460.
##  9 2018 Sep <aggregated> <aggregated>  463.
## 10 2018 Oct <aggregated> <aggregated>  455.
## # ℹ 530 more rows
#Plot
myagg%>%filter(is_aggregated(Sector))%>%
  autoplot(Value) +
  labs(y = "Value",
       title = "Value of Investment") +
  facet_wrap(vars(Stock), scales = "free_y", ncol = 3) +
  theme(legend.position = "none")

Model and Plot

#Bottoms Up Forecast
myagg2 <- train |>aggregate_key(Stock,Value=mean(Value))
m1=myagg2|> model(ets = ETS(Value))|>reconcile(bu = bottom_up(ets))
aug1=m1%>%augment()
f1=m1%>%forecast(h=12)
f1%>%autoplot(temp)

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)
f2%>%autoplot(temp%>%aggregate_key(Sector/Stock,Value=mean(Value)))+facet_wrap(~Stock+Sector)

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

#Top Down
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)
f3%>%autoplot(temp%>%aggregate_key(Sector/Stock,Value=mean(Value)))+facet_wrap(~Stock+Sector)

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

#MinT
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)
f5%>%autoplot(temp%>%aggregate_key(Sector/Stock,Value=mean(Value)))+facet_wrap(~Stock+Sector)

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>