# =========================
# LIBRARY
# =========================
invisible(lapply(c("readxl","dplyr","ggplot2","readr","moments","MASS","forecast","tseries","rugarch","FinTS","scales"), library, character.only = TRUE))
## Warning: package 'readxl' was built under R version 4.4.3
## Warning: package 'dplyr' was built under R version 4.4.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
## Warning: package 'ggplot2' was built under R version 4.4.3
## Warning: package 'readr' was built under R version 4.4.3
## Warning: package 'MASS' was built under R version 4.4.2
## 
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
## 
##     select
## Warning: package 'forecast' was built under R version 4.4.3
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
## Warning: package 'tseries' was built under R version 4.4.3
## Warning: package 'rugarch' was built under R version 4.4.3
## Loading required package: parallel
## Warning: package 'FinTS' was built under R version 4.4.3
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 4.4.3
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## 
## Attaching package: 'FinTS'
## The following object is masked from 'package:forecast':
## 
##     Acf
## Warning: package 'scales' was built under R version 4.4.3
## 
## Attaching package: 'scales'
## The following object is masked from 'package:readr':
## 
##     col_factor
# =========================
# 1. INPUT DATA, RETURN, DAN SINKRONISASI
# =========================
setwd("C:/Users/HP/Downloads/SKEMA B baru")
baca_data <- function(file) read_excel(file) %>% transmute(Date = as.Date(Tanggal), Close = as.numeric(Terakhir)) %>% filter(!is.na(Date), !is.na(Close)) %>% arrange(Date) %>% distinct(Date, .keep_all = TRUE)
ihsg <- baca_data("IHSG.xlsx"); sp500 <- baca_data("SP500.xlsx")
cat("Jumlah data IHSG  :", nrow(ihsg), "\nJumlah data SP500 :", nrow(sp500), "\n")
## Jumlah data IHSG  : 3763 
## Jumlah data SP500 : 3890
ihsg_ret <- ihsg %>% mutate(Return_IHSG = log(Close / lag(Close))) %>% filter(!is.na(Return_IHSG)) %>% rename(Close_IHSG = Close)
sp500_ret <- sp500 %>% mutate(Return_SP500 = log(Close / lag(Close))) %>% filter(!is.na(Return_SP500)) %>% rename(Close_SP500 = Close)
idx_sp <- findInterval(as.numeric(ihsg_ret$Date) - 1e-9, as.numeric(sp500_ret$Date)); idx_sp[idx_sp == 0] <- NA_integer_
data_model <- ihsg_ret %>% mutate(Date_SP500_Exog = sp500_ret$Date[idx_sp], Close_SP500_Exog = sp500_ret$Close_SP500[idx_sp], Return_SP500_Exog = sp500_ret$Return_SP500[idx_sp], Lag_Days_SP500 = as.integer(Date - Date_SP500_Exog)) %>% filter(!is.na(Return_IHSG), !is.na(Return_SP500_Exog)) %>% dplyr::select(Date, Close_IHSG, Return_IHSG, Date_SP500_Exog, Close_SP500_Exog, Return_SP500_Exog, Lag_Days_SP500)
str(data_model); head(data_model); tail(data_model); print(table(data_model$Lag_Days_SP500))
## tibble [3,761 × 7] (S3: tbl_df/tbl/data.frame)
##  $ Date             : Date[1:3761], format: "2011-01-05" "2011-01-06" ...
##  $ Close_IHSG       : num [1:3761] 3784 3736 3631 3479 3455 ...
##  $ Return_IHSG      : num [1:3761] 0.00627 -0.01262 -0.02845 -0.04302 -0.00676 ...
##  $ Date_SP500_Exog  : Date[1:3761], format: "2011-01-04" "2011-01-05" ...
##  $ Close_SP500_Exog : num [1:3761] 1270 1277 1274 1272 1270 ...
##  $ Return_SP500_Exog: num [1:3761] -0.00134 0.00503 -0.0022 -0.00181 -0.00134 ...
##  $ Lag_Days_SP500   : int [1:3761] 1 1 1 3 1 1 1 1 3 4 ...
## # A tibble: 6 × 7
##   Date       Close_IHSG Return_IHSG Date_SP500_Exog Close_SP500_Exog
##   <date>          <dbl>       <dbl> <date>                     <dbl>
## 1 2011-01-05      3784.     0.00627 2011-01-04                 1270.
## 2 2011-01-06      3736.    -0.0126  2011-01-05                 1277.
## 3 2011-01-07      3631.    -0.0285  2011-01-06                 1274.
## 4 2011-01-10      3479.    -0.0430  2011-01-07                 1272.
## 5 2011-01-11      3455.    -0.00676 2011-01-10                 1270.
## 6 2011-01-12      3555.     0.0284  2011-01-11                 1274.
## # ℹ 2 more variables: Return_SP500_Exog <dbl>, Lag_Days_SP500 <int>
## # A tibble: 6 × 7
##   Date       Close_IHSG Return_IHSG Date_SP500_Exog Close_SP500_Exog
##   <date>          <dbl>       <dbl> <date>                     <dbl>
## 1 2026-06-17      6221.   -0.00549  2026-06-16                 7511.
## 2 2026-06-18      6172.   -0.00781  2026-06-17                 7420.
## 3 2026-06-19      6177.    0.000777 2026-06-18                 7501.
## 4 2026-06-22      6117.   -0.00983  2026-06-18                 7501.
## 5 2026-06-23      6101.   -0.00251  2026-06-22                 7473.
## 6 2026-06-24      5888.   -0.0356   2026-06-23                 7365.
## # ℹ 2 more variables: Return_SP500_Exog <dbl>, Lag_Days_SP500 <int>
## 
##    1    2    3    4    5 
## 2890   42  728  100    1
# =========================
# 2. FUNGSI TEMA DAN PLOT DASAR
# =========================
tema_ts <- function() theme_minimal(base_family = "serif") + theme(axis.text.x = element_text(angle = 45, hjust = 1, color = "black", size = 12), axis.text.y = element_text(color = "black", size = 12), axis.title = element_text(color = "black", size = 16), legend.title = element_text(color = "black", size = 12), legend.text = element_text(color = "black", size = 11), axis.line = element_line(color = "black", linewidth = 0.5), panel.grid.major = element_line(color = "grey60", linewidth = 0.4), panel.grid.minor = element_line(color = "grey75", linewidth = 0.4))
buat_periode <- function(b) {b <- as.Date(b); if(length(b) == 0) return(data.frame(xmin = as.Date(character()), xmax = as.Date(character()))); if(length(b) %% 2 != 0) stop("Jumlah tanggal batas_koreksi harus genap."); data.frame(xmin = b[c(TRUE,FALSE)], xmax = b[c(FALSE,TRUE)])}
plot_ts <- function(data, y_col, y_lab, batas_koreksi = as.Date(character()), warna = "blue", is_return = FALSE) {periode <- buat_periode(batas_koreksi); p <- ggplot(data, aes(Date, .data[[y_col]])); if(nrow(periode) > 0) p <- p + geom_rect(data = periode, aes(xmin = xmin, xmax = xmax, ymin = -Inf, ymax = Inf), inherit.aes = FALSE, fill = "red", alpha = 0.30); if(is_return) p <- p + geom_hline(yintercept = 0, color = "black", linewidth = 0.5); p <- p + geom_line(color = warna, linewidth = 0.8); if(length(batas_koreksi) > 0) p <- p + geom_vline(xintercept = batas_koreksi, linetype = "dashed", color = "red", linewidth = 0.8, alpha = 0.7); p + scale_x_date(date_breaks = "1 year", date_labels = "%Y", expand = expansion(mult = c(0.01,0.01))) + scale_y_continuous(labels = if(is_return) percent_format(accuracy = 1) else label_number(big.mark = ".", decimal.mark = ",")) + labs(x = "Date", y = y_lab) + tema_ts()}

batas_koreksi_ihsg <- as.Date(character()); batas_koreksi_sp500 <- as.Date(character())
plot.1 <- plot_ts(ihsg, "Close", "Closing Price IHSG", batas_koreksi_ihsg, "blue")
plot.2 <- plot_ts(sp500, "Close", "Closing Price S&P 500", batas_koreksi_sp500, "blue")
plot.3 <- plot_ts(data_model, "Return_IHSG", "Return IHSG", batas_koreksi_ihsg, "red", TRUE)
plot.4 <- plot_ts(data_model, "Return_SP500_Exog", "Synchronized Return S&P 500", batas_koreksi_sp500, "red", TRUE)
plot.1; plot.2; plot.3; plot.4

# =========================
# 3. DESKRIPTIF, SPLIT DATA, DAN ADF
# =========================
stat_deskriptif <- function(x) {x <- na.omit(x); data.frame(Observations = length(x), Mean = mean(x), Std_Dev = sd(x), Skewness = skewness(x), Kurtosis = kurtosis(x))}
deskriptif_return <- bind_rows(stat_deskriptif(data_model$Return_IHSG) %>% mutate(Variable = "Return IHSG"), stat_deskriptif(data_model$Return_SP500_Exog) %>% mutate(Variable = "Return S&P 500")) %>% dplyr::select(Variable, everything())
deskriptif_return
##         Variable Observations         Mean    Std_Dev   Skewness Kurtosis
## 1    Return IHSG         3761 0.0001192317 0.01077818 -0.6297416 11.45148
## 2 Return S&P 500         3761 0.0005127508 0.01067056 -0.7731835 17.31353
n <- nrow(data_model); n_train <- floor(0.90 * n); data_train <- data_model[1:n_train, ]; data_test <- data_model[(n_train + 1):n, ]
cat("Jumlah data latih =", nrow(data_train), "\nJumlah data uji   =", nrow(data_test), "\n"); print(range(data_train$Date)); print(range(data_test$Date))
## Jumlah data latih = 3384 
## Jumlah data uji   = 377
## [1] "2011-01-05" "2024-11-08"
## [1] "2024-11-11" "2026-06-24"
tanggal_awal_uji <- min(data_test$Date)
plot.train.test <- ggplot(ihsg %>% mutate(Jenis_Data = ifelse(Date < tanggal_awal_uji, "Data Latih", "Data Uji")), aes(Date, Close)) + geom_line(aes(color = Jenis_Data), linewidth = 0.8) + geom_vline(xintercept = tanggal_awal_uji, linetype = "dashed", color = "black", linewidth = 0.8) + scale_color_manual(values = c("Data Latih" = "blue", "Data Uji" = "red")) + scale_x_date(date_breaks = "1 year", date_labels = "%Y") + scale_y_continuous(labels = label_number(big.mark = ".", decimal.mark = ",")) + labs(x = "Tahun", y = "Harga Penutupan IHSG", color = "Keterangan") + tema_ts()
plot.train.test

adf_rapi <- function(x, nama, lag_order = 15) {h <- suppressWarnings(adf.test(na.omit(x), alternative = "stationary", k = lag_order)); data.frame(Variable = nama, Test = "ADF", Test_Statistic = as.numeric(h$statistic), Lag_Order = lag_order, P_Value = h$p.value, Decision = ifelse(h$p.value < 0.05, "Stationary", "Not Stationary"))}
lag_adf <- 15
adf_train <- bind_rows(adf_rapi(data_train$Return_IHSG, "IHSG Return", lag_adf), adf_rapi(data_train$Return_SP500_Exog, "S&P 500 Return", lag_adf))
adf_train
##         Variable Test Test_Statistic Lag_Order P_Value   Decision
## 1    IHSG Return  ADF      -15.28783        15    0.01 Stationary
## 2 S&P 500 Return  ADF      -14.47395        15    0.01 Stationary
# =========================
# 4. PREWHITENING DAN CCF
# =========================
acf(data_train$Return_SP500_Exog, lag.max = 20, xlim = c(1,20), ylim = c(-0.15, 0.15))

pacf(data_train$Return_SP500_Exog, lag.max = 20)

spec_sp <- list(model_1 = list(order = c(4,0,2), fixed = c(NA,NA,0,NA,NA,NA,NA)), model_2 = list(order = c(4,0,0), fixed = c(NA,NA,0,NA,NA)), model_3 = list(order = c(0,0,2), fixed = c(NA,NA,NA)), model_4 = list(order = c(2,0,2), fixed = c(NA,NA,NA,NA,NA)), model_5 = list(order = c(1,0,2), fixed = c(NA,NA,NA,NA)), model_6 = list(order = c(2,0,1), fixed = c(NA,NA,NA,NA)), model_7 = list(order = c(1,0,1), fixed = c(NA,NA,NA)))
model_sp <- lapply(spec_sp, function(s) stats::arima(data_train$Return_SP500_Exog, order = s$order, fixed = s$fixed, method = "ML", include.mean = TRUE))
## Warning in stats::arima(data_train$Return_SP500_Exog, order = s$order, fixed =
## s$fixed, : some AR parameters were fixed: setting transform.pars = FALSE
## Warning in stats::arima(data_train$Return_SP500_Exog, order = s$order, fixed =
## s$fixed, : some AR parameters were fixed: setting transform.pars = FALSE
rekap_sp500 <- data.frame(Model = names(model_sp), AIC = sapply(model_sp, AIC), BIC = sapply(model_sp, BIC), Ljung_Box_pvalue = sapply(model_sp, function(m) Box.test(residuals(m), lag = 10, type = "Ljung-Box")$p.value))
rekap_sp500
##           Model       AIC       BIC Ljung_Box_pvalue
## model_1 model_1 -21114.89 -21072.00       0.24014224
## model_2 model_2 -21118.45 -21087.81       0.21283411
## model_3 model_3 -21114.64 -21090.14       0.04416628
## model_4 model_4 -21113.77 -21077.01       0.11370100
## model_5 model_5 -21113.96 -21083.32       0.07863368
## model_6 model_6 -21113.04 -21082.41       0.06284383
## model_7 model_7 -21111.35 -21086.84       0.02316958
model_sp500_terbaik <- forecast::Arima(data_train$Return_SP500_Exog, order = c(4,0,0), fixed = c(NA,NA,0,NA,NA), include.mean = TRUE, method = "ML")
model_sp500_terbaik
## Series: data_train$Return_SP500_Exog 
## ARIMA(4,0,0) with non-zero mean 
## 
## Coefficients:
##           ar1     ar2  ar3      ar4   mean
##       -0.0995  0.0662    0  -0.0400  5e-04
## s.e.   0.0171  0.0172    0   0.0171  2e-04
## 
## sigma^2 = 0.0001139:  log likelihood = 10564.22
## AIC=-21118.45   AICc=-21118.43   BIC=-21087.81
res_sp500 <- residuals(model_sp500_terbaik); Box.test(res_sp500, lag = 10, type = "Ljung-Box")
## 
##  Box-Ljung test
## 
## data:  res_sp500
## X-squared = 13.198, df = 10, p-value = 0.2128
sp500_pw <- residuals(model_sp500_terbaik)
ihsg_pw <- residuals(forecast::Arima(data_train$Return_IHSG, model = model_sp500_terbaik))
prewhitened_data <- data.frame(Date = data_train$Date, SP500_Prewhitened = as.numeric(sp500_pw), IHSG_Prewhitened = as.numeric(ihsg_pw)) %>% filter(!is.na(SP500_Prewhitened), !is.na(IHSG_Prewhitened))
ccf_pw <- ccf(prewhitened_data$SP500_Prewhitened, prewhitened_data$IHSG_Prewhitened, lag.max = 20, main = "CCF Prewhitening: S&P 500 terhadap IHSG", ylab = "Cross Correlation")

ccf_table <- data.frame(Lag = as.numeric(ccf_pw$lag), CCF = as.numeric(ccf_pw$acf)) %>% mutate(Batas_Atas = 1.96 / sqrt(nrow(prewhitened_data)), Batas_Bawah = -1.96 / sqrt(nrow(prewhitened_data)), Signifikan = ifelse(abs(CCF) > Batas_Atas, "Signifikan", "Tidak Signifikan")) %>% arrange(Lag)
ccf_table
##    Lag           CCF Batas_Atas Batas_Bawah       Signifikan
## 1  -20  8.188814e-03 0.03369309 -0.03369309 Tidak Signifikan
## 2  -19  4.799343e-03 0.03369309 -0.03369309 Tidak Signifikan
## 3  -18 -7.300100e-03 0.03369309 -0.03369309 Tidak Signifikan
## 4  -17  1.277615e-02 0.03369309 -0.03369309 Tidak Signifikan
## 5  -16 -9.298898e-03 0.03369309 -0.03369309 Tidak Signifikan
## 6  -15  1.331959e-02 0.03369309 -0.03369309 Tidak Signifikan
## 7  -14  1.706262e-02 0.03369309 -0.03369309 Tidak Signifikan
## 8  -13 -1.713465e-02 0.03369309 -0.03369309 Tidak Signifikan
## 9  -12 -4.867493e-03 0.03369309 -0.03369309 Tidak Signifikan
## 10 -11  1.807978e-02 0.03369309 -0.03369309 Tidak Signifikan
## 11 -10  4.375356e-03 0.03369309 -0.03369309 Tidak Signifikan
## 12  -9 -3.662920e-02 0.03369309 -0.03369309       Signifikan
## 13  -8  3.889919e-03 0.03369309 -0.03369309 Tidak Signifikan
## 14  -7  4.655443e-02 0.03369309 -0.03369309       Signifikan
## 15  -6 -7.939770e-03 0.03369309 -0.03369309 Tidak Signifikan
## 16  -5 -3.429580e-02 0.03369309 -0.03369309       Signifikan
## 17  -4  1.748013e-02 0.03369309 -0.03369309 Tidak Signifikan
## 18  -3  1.071124e-03 0.03369309 -0.03369309 Tidak Signifikan
## 19  -2  3.201359e-02 0.03369309 -0.03369309 Tidak Signifikan
## 20  -1 -2.242445e-02 0.03369309 -0.03369309 Tidak Signifikan
## 21   0  3.041923e-01 0.03369309 -0.03369309       Signifikan
## 22   1  2.174147e-01 0.03369309 -0.03369309       Signifikan
## 23   2 -1.419924e-02 0.03369309 -0.03369309 Tidak Signifikan
## 24   3 -3.631938e-03 0.03369309 -0.03369309 Tidak Signifikan
## 25   4  1.691475e-02 0.03369309 -0.03369309 Tidak Signifikan
## 26   5 -1.818233e-02 0.03369309 -0.03369309 Tidak Signifikan
## 27   6  3.087976e-02 0.03369309 -0.03369309 Tidak Signifikan
## 28   7  3.409496e-02 0.03369309 -0.03369309       Signifikan
## 29   8  3.938027e-02 0.03369309 -0.03369309       Signifikan
## 30   9 -5.862090e-03 0.03369309 -0.03369309 Tidak Signifikan
## 31  10 -5.537485e-03 0.03369309 -0.03369309 Tidak Signifikan
## 32  11  2.340867e-02 0.03369309 -0.03369309 Tidak Signifikan
## 33  12 -3.759728e-02 0.03369309 -0.03369309       Signifikan
## 34  13 -6.017329e-02 0.03369309 -0.03369309       Signifikan
## 35  14 -8.920971e-03 0.03369309 -0.03369309 Tidak Signifikan
## 36  15 -3.171234e-02 0.03369309 -0.03369309 Tidak Signifikan
## 37  16  3.208040e-02 0.03369309 -0.03369309 Tidak Signifikan
## 38  17 -1.543306e-03 0.03369309 -0.03369309 Tidak Signifikan
## 39  18 -9.242605e-03 0.03369309 -0.03369309 Tidak Signifikan
## 40  19 -7.901571e-05 0.03369309 -0.03369309 Tidak Signifikan
## 41  20 -2.141856e-02 0.03369309 -0.03369309 Tidak Signifikan
# =========================
# 5. DATA ARIMAX DAN IDENTIFIKASI ORDE
# =========================
data_train_arimax <- data_train %>% mutate(Xt = Return_SP500_Exog, Xt_1 = lag(Return_SP500_Exog, 1)) %>% filter(!is.na(Xt_1))
data_test_arimax <- data_test %>% mutate(Xt = Return_SP500_Exog, Xt_1 = lag(Return_SP500_Exog, 1)) %>% filter(!is.na(Xt_1))
y_train <- data_train_arimax$Return_IHSG; y_test <- data_test_arimax$Return_IHSG
xmat <- function(data, cols) {x <- as.matrix(data[, cols, drop = FALSE]); colnames(x) <- cols; x}
x_train_xt <- xmat(data_train_arimax, "Xt"); x_test_xt <- xmat(data_test_arimax, "Xt")
x_train_xt1 <- xmat(data_train_arimax, "Xt_1"); x_test_xt1 <- xmat(data_test_arimax, "Xt_1")
x_train_xt_xt1 <- xmat(data_train_arimax, c("Xt","Xt_1")); x_test_xt_xt1 <- xmat(data_test_arimax, c("Xt","Xt_1"))

acf(y_train, lag.max = 20, main = "ACF Return IHSG", xlab = "Lag", ylab = "ACF", xlim = c(1,20), ylim = c(-0.06,0.06))

pacf(y_train, lag.max = 20, main = "PACF Return IHSG", xlab = "Lag", ylab = "PACF")

# =========================
# 6. ESTIMASI KANDIDAT ARIMAX
# =========================
p_kandidat <- 0:5; q_kandidat <- 0:5; lag_ljung <- 15
skenario_xreg <- list(Xt = x_train_xt, Xt_1 = x_train_xt1, Xt_dan_Xt_1 = x_train_xt_xt1)
uji_signifikansi <- function(model) {est <- coef(model); vc <- model$var.coef; if(is.null(vc)) return(NULL); d <- diag(vc); if(any(is.na(d)) || any(d <= 0)) return(NULL); se <- sqrt(d); z <- est / se; p <- 2 * pnorm(abs(z), lower.tail = FALSE); data.frame(Parameter = names(est), Estimate = as.numeric(est), Std_Error = as.numeric(se), Z_value = as.numeric(z), p_value = as.numeric(p), row.names = NULL)}
ambil_pvalue <- function(obj) if(is.null(obj)) NA_real_ else obj$p.value

estimasi_kandidat_arimax <- function(y, xreg, nama_skenario, p_set = p_kandidat, q_set = q_kandidat, lag_lb = lag_ljung) {
  kandidat <- expand.grid(p = p_set, q = q_set); hasil_model <- list(); rekap_model <- list(); nama_xreg <- colnames(xreg)
  for(i in 1:nrow(kandidat)) {
    p <- kandidat$p[i]; q <- kandidat$q[i]
    fit <- tryCatch(forecast::Arima(y, order = c(p,0,q), xreg = xreg, include.mean = TRUE, method = "ML"), error = function(e) NULL)
    if(!is.null(fit)) {
      sig <- tryCatch(uji_signifikansi(fit), error = function(e) NULL)
      lb15 <- tryCatch(Box.test(residuals(fit), lag = lag_lb, type = "Ljung-Box", fitdf = p + q), error = function(e) NULL)
      param_arma <- if(!is.null(sig)) sig[grepl("^(ar|ma)", sig$Parameter, ignore.case = TRUE), , drop = FALSE] else NULL
      arma_signif <- if(!is.null(param_arma) && nrow(param_arma) > 0) all(param_arma$p_value < 0.05) else TRUE
      xreg_signif <- if(!is.null(sig)) all(sapply(nama_xreg, function(v) if(v %in% sig$Parameter) sig$p_value[sig$Parameter == v][1] < 0.05 else FALSE)) else FALSE
      nama_model <- paste0("ARIMAX(", p, ",0,", q, ")"); hasil_model[[nama_model]] <- fit
      rekap_model[[length(rekap_model) + 1]] <- data.frame(Skenario = nama_skenario, Model = nama_model, p = p, q = q, AIC = AIC(fit), BIC = BIC(fit), Ljung_Box_15 = ambil_pvalue(lb15), ARMA_Signif = arma_signif, Xreg_Signif = xreg_signif)
    }
  }
  list(hasil_model = hasil_model, rekap_model = bind_rows(rekap_model) %>% arrange(AIC, BIC))
}

hasil_arimax <- setNames(lapply(names(skenario_xreg), function(nm) estimasi_kandidat_arimax(y_train, skenario_xreg[[nm]], nm)), names(skenario_xreg))
hasil_xt <- hasil_arimax$Xt; hasil_xt1 <- hasil_arimax$Xt_1; hasil_xt_xt1 <- hasil_arimax$Xt_dan_Xt_1
rekap_model_arimax <- bind_rows(lapply(hasil_arimax, `[[`, "rekap_model")) %>% arrange(AIC, BIC)
model_lolos_lb15_xreg <- rekap_model_arimax %>% filter(Ljung_Box_15 > 0.05, Xreg_Signif) %>% arrange(AIC, BIC)
rekap_model_arimax; model_lolos_lb15_xreg
##        Skenario         Model p q       AIC       BIC Ljung_Box_15 ARMA_Signif
## 1            Xt ARIMAX(4,0,4) 4 4 -21739.10 -21671.71 0.0345126434        TRUE
## 2            Xt ARIMAX(4,0,1) 4 1 -21738.10 -21689.09 0.2280520025        TRUE
## 3   Xt_dan_Xt_1 ARIMAX(4,0,4) 4 4 -21737.14 -21663.62 0.0351533998        TRUE
## 4            Xt ARIMAX(2,0,4) 2 4 -21737.12 -21681.98 0.2062515325       FALSE
## 5            Xt ARIMAX(1,0,4) 1 4 -21737.00 -21687.99 0.1855400912        TRUE
## 6            Xt ARIMAX(4,0,2) 4 2 -21736.54 -21681.40 0.1878644752       FALSE
## 7            Xt ARIMAX(5,0,1) 5 1 -21736.34 -21681.20 0.1751061994       FALSE
## 8            Xt ARIMAX(4,0,3) 4 3 -21736.33 -21675.07 0.2446771502       FALSE
## 9   Xt_dan_Xt_1 ARIMAX(4,0,1) 4 1 -21736.14 -21681.00 0.2267472682        TRUE
## 10           Xt ARIMAX(1,0,5) 1 5 -21735.63 -21680.49 0.1541749836       FALSE
## 11  Xt_dan_Xt_1 ARIMAX(1,0,4) 1 4 -21735.04 -21679.90 0.1838410786        TRUE
## 12  Xt_dan_Xt_1 ARIMAX(4,0,2) 4 2 -21735.01 -21673.74 0.2059149218       FALSE
## 13  Xt_dan_Xt_1 ARIMAX(2,0,4) 2 4 -21734.70 -21673.43 0.1933831459       FALSE
## 14           Xt ARIMAX(5,0,3) 5 3 -21734.45 -21667.06 0.1928742031       FALSE
## 15  Xt_dan_Xt_1 ARIMAX(4,0,3) 4 3 -21734.38 -21666.99 0.2437917670       FALSE
## 16  Xt_dan_Xt_1 ARIMAX(5,0,1) 5 1 -21734.37 -21673.10 0.1740272313       FALSE
## 17           Xt ARIMAX(5,0,2) 5 2 -21734.10 -21672.84 0.1145735947        TRUE
## 18  Xt_dan_Xt_1 ARIMAX(5,0,5) 5 5 -21733.79 -21648.02 0.0487756498        TRUE
## 19           Xt ARIMAX(3,0,5) 3 5 -21733.78 -21666.39 0.1262074844        TRUE
## 20  Xt_dan_Xt_1 ARIMAX(1,0,5) 1 5 -21733.66 -21672.39 0.1520709782       FALSE
## 21  Xt_dan_Xt_1 ARIMAX(5,0,4) 5 4 -21733.28 -21653.64 0.0262021712        TRUE
## 22           Xt ARIMAX(0,0,5) 0 5 -21733.20 -21684.19 0.0625592103       FALSE
## 23           Xt ARIMAX(2,0,5) 2 5 -21733.19 -21671.93 0.0928622289        TRUE
## 24           Xt ARIMAX(5,0,4) 5 4 -21732.73 -21659.21 0.1159741707       FALSE
## 25           Xt ARIMAX(4,0,5) 4 5 -21732.72 -21659.20 0.1273585357       FALSE
## 26  Xt_dan_Xt_1 ARIMAX(3,0,5) 3 5 -21732.54 -21659.03 0.1697897165       FALSE
## 27  Xt_dan_Xt_1 ARIMAX(5,0,3) 5 3 -21732.48 -21658.96 0.1779204681       FALSE
## 28           Xt ARIMAX(5,0,0) 5 0 -21732.29 -21683.28 0.0467953625       FALSE
## 29  Xt_dan_Xt_1 ARIMAX(5,0,2) 5 2 -21732.14 -21664.75 0.1131920010        TRUE
## 30           Xt ARIMAX(5,0,5) 5 5 -21731.87 -21652.23 0.1071547566        TRUE
## 31  Xt_dan_Xt_1 ARIMAX(2,0,5) 2 5 -21731.23 -21663.84 0.0918482622        TRUE
## 32  Xt_dan_Xt_1 ARIMAX(0,0,5) 0 5 -21731.21 -21676.07 0.0619300035       FALSE
## 33  Xt_dan_Xt_1 ARIMAX(4,0,5) 4 5 -21730.88 -21651.23 0.1239483060        TRUE
## 34           Xt ARIMAX(4,0,0) 4 0 -21730.78 -21687.90 0.0234060834       FALSE
## 35           Xt ARIMAX(0,0,4) 0 4 -21730.41 -21687.52 0.0213091690       FALSE
## 36  Xt_dan_Xt_1 ARIMAX(5,0,0) 5 0 -21730.31 -21675.18 0.0462351823       FALSE
## 37           Xt ARIMAX(2,0,2) 2 2 -21729.81 -21686.92 0.0244067877       FALSE
## 38           Xt ARIMAX(3,0,4) 3 4 -21729.38 -21668.11 0.0295648429        TRUE
## 39  Xt_dan_Xt_1 ARIMAX(4,0,0) 4 0 -21728.80 -21679.79 0.0231367115       FALSE
## 40  Xt_dan_Xt_1 ARIMAX(0,0,4) 0 4 -21728.42 -21679.40 0.0210843963       FALSE
## 41           Xt ARIMAX(3,0,2) 3 2 -21727.92 -21678.91 0.0179118673       FALSE
## 42           Xt ARIMAX(2,0,3) 2 3 -21727.87 -21678.86 0.0179000295       FALSE
## 43  Xt_dan_Xt_1 ARIMAX(2,0,2) 2 2 -21727.82 -21678.81 0.0249447844       FALSE
## 44  Xt_dan_Xt_1 ARIMAX(3,0,4) 3 4 -21727.41 -21660.01 0.0301476385        TRUE
## 45           Xt ARIMAX(3,0,3) 3 3 -21727.23 -21672.09 0.0129668598        TRUE
## 46           Xt ARIMAX(1,0,1) 1 1 -21727.09 -21696.46 0.0074192028        TRUE
## 47           Xt ARIMAX(2,0,1) 2 1 -21726.53 -21689.77 0.0047060585       FALSE
## 48           Xt ARIMAX(1,0,2) 1 2 -21726.49 -21689.73 0.0046991381       FALSE
## 49  Xt_dan_Xt_1 ARIMAX(3,0,2) 3 2 -21725.97 -21670.83 0.0176683907       FALSE
## 50  Xt_dan_Xt_1 ARIMAX(2,0,3) 2 3 -21725.89 -21670.75 0.0171858261       FALSE
## 51  Xt_dan_Xt_1 ARIMAX(1,0,1) 1 1 -21725.17 -21688.41 0.0073318464        TRUE
## 52           Xt ARIMAX(3,0,1) 3 1 -21724.89 -21682.00 0.0031149017       FALSE
## 53           Xt ARIMAX(1,0,3) 1 3 -21724.72 -21681.84 0.0029933189       FALSE
## 54  Xt_dan_Xt_1 ARIMAX(2,0,1) 2 1 -21724.55 -21681.67 0.0047686961       FALSE
## 55  Xt_dan_Xt_1 ARIMAX(1,0,2) 1 2 -21724.50 -21681.62 0.0047449456       FALSE
## 56           Xt ARIMAX(0,0,3) 0 3 -21724.08 -21687.32 0.0020855269       FALSE
## 57           Xt ARIMAX(0,0,2) 0 2 -21723.92 -21693.29 0.0018517563       FALSE
## 58           Xt ARIMAX(3,0,0) 3 0 -21723.42 -21686.67 0.0016211699       FALSE
## 59           Xt ARIMAX(2,0,0) 2 0 -21723.07 -21692.43 0.0014098080       FALSE
## 60  Xt_dan_Xt_1 ARIMAX(3,0,1) 3 1 -21722.91 -21673.90 0.0030901451       FALSE
## 61  Xt_dan_Xt_1 ARIMAX(1,0,3) 1 3 -21722.76 -21673.75 0.0030145690       FALSE
## 62  Xt_dan_Xt_1 ARIMAX(0,0,3) 0 3 -21722.09 -21679.20 0.0020601232       FALSE
## 63  Xt_dan_Xt_1 ARIMAX(0,0,2) 0 2 -21721.93 -21685.17 0.0018358946       FALSE
## 64  Xt_dan_Xt_1 ARIMAX(3,0,0) 3 0 -21721.44 -21678.56 0.0015981226       FALSE
## 65  Xt_dan_Xt_1 ARIMAX(2,0,0) 2 0 -21721.08 -21684.32 0.0013896020       FALSE
## 66           Xt ARIMAX(0,0,0) 0 0 -21721.01 -21702.63 0.0007689282        TRUE
## 67           Xt ARIMAX(0,0,1) 0 1 -21720.48 -21695.98 0.0007995737       FALSE
## 68           Xt ARIMAX(1,0,0) 1 0 -21720.37 -21695.86 0.0007659966       FALSE
## 69  Xt_dan_Xt_1 ARIMAX(0,0,0) 0 0 -21719.47 -21694.96 0.0008719408        TRUE
## 70  Xt_dan_Xt_1 ARIMAX(0,0,1) 0 1 -21718.84 -21688.20 0.0008703596       FALSE
## 71  Xt_dan_Xt_1 ARIMAX(1,0,0) 1 0 -21718.73 -21688.10 0.0008387847       FALSE
## 72  Xt_dan_Xt_1 ARIMAX(3,0,3) 3 3 -21717.80 -21656.54 0.0006836181        TRUE
## 73         Xt_1 ARIMAX(5,0,4) 5 4 -21461.67 -21388.15 0.0652177115        TRUE
## 74         Xt_1 ARIMAX(4,0,5) 4 5 -21461.57 -21388.05 0.0626271775        TRUE
## 75         Xt_1 ARIMAX(5,0,5) 5 5 -21454.31 -21374.66 0.0054020628        TRUE
## 76         Xt_1 ARIMAX(4,0,4) 4 4 -21448.67 -21381.28 0.0135978796        TRUE
## 77         Xt_1 ARIMAX(4,0,1) 4 1 -21442.98 -21393.97 0.0157962350       FALSE
## 78         Xt_1 ARIMAX(4,0,2) 4 2 -21442.49 -21387.35 0.0164745643       FALSE
## 79         Xt_1 ARIMAX(1,0,4) 1 4 -21442.36 -21393.35 0.0141034949       FALSE
## 80         Xt_1 ARIMAX(5,0,1) 5 1 -21441.20 -21386.06 0.0100724383       FALSE
## 81         Xt_1 ARIMAX(1,0,5) 1 5 -21440.58 -21385.44 0.0088933910       FALSE
## 82         Xt_1 ARIMAX(3,0,5) 3 5 -21440.36 -21372.97 0.0124205560        TRUE
## 83         Xt_1 ARIMAX(0,0,4) 0 4 -21440.23 -21397.34 0.0055856769       FALSE
## 84         Xt_1 ARIMAX(5,0,3) 5 3 -21440.08 -21372.69 0.0163556968        TRUE
## 85         Xt_1 ARIMAX(4,0,0) 4 0 -21440.07 -21397.19 0.0052159155       FALSE
## 86         Xt_1 ARIMAX(2,0,4) 2 4 -21439.98 -21384.84 0.0072422192       FALSE
## 87         Xt_1 ARIMAX(2,0,2) 2 2 -21439.90 -21397.02 0.0053252732        TRUE
## 88         Xt_1 ARIMAX(3,0,3) 3 3 -21439.86 -21384.73 0.0077600709       FALSE
## 89         Xt_1 ARIMAX(0,0,5) 0 5 -21439.68 -21390.66 0.0049921264       FALSE
## 90         Xt_1 ARIMAX(5,0,0) 5 0 -21439.27 -21390.26 0.0040792145       FALSE
## 91         Xt_1 ARIMAX(5,0,2) 5 2 -21439.01 -21377.75 0.0051673967        TRUE
## 92         Xt_1 ARIMAX(0,0,1) 0 1 -21438.83 -21414.32 0.0025329047        TRUE
## 93         Xt_1 ARIMAX(2,0,1) 2 1 -21438.49 -21401.73 0.0029681986        TRUE
## 94         Xt_1 ARIMAX(1,0,0) 1 0 -21438.47 -21413.97 0.0023066958        TRUE
## 95         Xt_1 ARIMAX(2,0,3) 2 3 -21438.46 -21389.45 0.0034526696        TRUE
## 96         Xt_1 ARIMAX(2,0,5) 2 5 -21438.40 -21377.13 0.0045818110       FALSE
## 97         Xt_1 ARIMAX(1,0,2) 1 2 -21438.05 -21401.29 0.0025575695        TRUE
## 98         Xt_1 ARIMAX(2,0,0) 2 0 -21437.87 -21407.24 0.0019665572       FALSE
## 99         Xt_1 ARIMAX(3,0,2) 3 2 -21437.77 -21388.76 0.0028475995        TRUE
## 100        Xt_1 ARIMAX(0,0,2) 0 2 -21437.67 -21407.04 0.0018497282       FALSE
## 101        Xt_1 ARIMAX(3,0,0) 3 0 -21437.62 -21400.86 0.0022117559       FALSE
## 102        Xt_1 ARIMAX(3,0,1) 3 1 -21437.34 -21394.45 0.0023735849       FALSE
## 103        Xt_1 ARIMAX(4,0,3) 4 3 -21437.30 -21376.04 0.0030512322        TRUE
## 104        Xt_1 ARIMAX(1,0,1) 1 1 -21437.28 -21406.65 0.0016706575       FALSE
## 105        Xt_1 ARIMAX(0,0,3) 0 3 -21437.11 -21400.35 0.0018670548       FALSE
## 106        Xt_1 ARIMAX(1,0,3) 1 3 -21436.89 -21394.01 0.0020280132       FALSE
## 107        Xt_1 ARIMAX(3,0,4) 3 4 -21436.88 -21375.62 0.0026235701        TRUE
## 108        Xt_1 ARIMAX(0,0,0) 0 0 -21431.73 -21413.35 0.0003135110        TRUE
##     Xreg_Signif
## 1         FALSE
## 2          TRUE
## 3         FALSE
## 4          TRUE
## 5          TRUE
## 6          TRUE
## 7          TRUE
## 8          TRUE
## 9         FALSE
## 10         TRUE
## 11        FALSE
## 12        FALSE
## 13        FALSE
## 14         TRUE
## 15        FALSE
## 16        FALSE
## 17        FALSE
## 18        FALSE
## 19        FALSE
## 20        FALSE
## 21        FALSE
## 22         TRUE
## 23        FALSE
## 24         TRUE
## 25         TRUE
## 26        FALSE
## 27        FALSE
## 28         TRUE
## 29        FALSE
## 30        FALSE
## 31        FALSE
## 32        FALSE
## 33        FALSE
## 34         TRUE
## 35         TRUE
## 36        FALSE
## 37         TRUE
## 38        FALSE
## 39        FALSE
## 40        FALSE
## 41         TRUE
## 42         TRUE
## 43        FALSE
## 44        FALSE
## 45         TRUE
## 46         TRUE
## 47         TRUE
## 48         TRUE
## 49        FALSE
## 50        FALSE
## 51        FALSE
## 52         TRUE
## 53         TRUE
## 54        FALSE
## 55        FALSE
## 56         TRUE
## 57         TRUE
## 58         TRUE
## 59         TRUE
## 60        FALSE
## 61        FALSE
## 62        FALSE
## 63        FALSE
## 64        FALSE
## 65        FALSE
## 66         TRUE
## 67         TRUE
## 68         TRUE
## 69        FALSE
## 70        FALSE
## 71        FALSE
## 72        FALSE
## 73         TRUE
## 74         TRUE
## 75         TRUE
## 76        FALSE
## 77         TRUE
## 78         TRUE
## 79         TRUE
## 80         TRUE
## 81         TRUE
## 82        FALSE
## 83         TRUE
## 84        FALSE
## 85         TRUE
## 86         TRUE
## 87         TRUE
## 88         TRUE
## 89         TRUE
## 90         TRUE
## 91        FALSE
## 92         TRUE
## 93         TRUE
## 94         TRUE
## 95        FALSE
## 96         TRUE
## 97         TRUE
## 98         TRUE
## 99        FALSE
## 100        TRUE
## 101        TRUE
## 102        TRUE
## 103       FALSE
## 104        TRUE
## 105        TRUE
## 106        TRUE
## 107       FALSE
## 108        TRUE
##    Skenario         Model p q       AIC       BIC Ljung_Box_15 ARMA_Signif
## 1        Xt ARIMAX(4,0,1) 4 1 -21738.10 -21689.09   0.22805200        TRUE
## 2        Xt ARIMAX(2,0,4) 2 4 -21737.12 -21681.98   0.20625153       FALSE
## 3        Xt ARIMAX(1,0,4) 1 4 -21737.00 -21687.99   0.18554009        TRUE
## 4        Xt ARIMAX(4,0,2) 4 2 -21736.54 -21681.40   0.18786448       FALSE
## 5        Xt ARIMAX(5,0,1) 5 1 -21736.34 -21681.20   0.17510620       FALSE
## 6        Xt ARIMAX(4,0,3) 4 3 -21736.33 -21675.07   0.24467715       FALSE
## 7        Xt ARIMAX(1,0,5) 1 5 -21735.63 -21680.49   0.15417498       FALSE
## 8        Xt ARIMAX(5,0,3) 5 3 -21734.45 -21667.06   0.19287420       FALSE
## 9        Xt ARIMAX(0,0,5) 0 5 -21733.20 -21684.19   0.06255921       FALSE
## 10       Xt ARIMAX(5,0,4) 5 4 -21732.73 -21659.21   0.11597417       FALSE
## 11       Xt ARIMAX(4,0,5) 4 5 -21732.72 -21659.20   0.12735854       FALSE
## 12     Xt_1 ARIMAX(5,0,4) 5 4 -21461.67 -21388.15   0.06521771        TRUE
## 13     Xt_1 ARIMAX(4,0,5) 4 5 -21461.57 -21388.05   0.06262718        TRUE
##    Xreg_Signif
## 1         TRUE
## 2         TRUE
## 3         TRUE
## 4         TRUE
## 5         TRUE
## 6         TRUE
## 7         TRUE
## 8         TRUE
## 9         TRUE
## 10        TRUE
## 11        TRUE
## 12        TRUE
## 13        TRUE
# =========================
# 7. MODEL ARIMAX TERBAIK DAN DIAGNOSTIK
# =========================
model_arimax_terbaik <- hasil_xt$hasil_model[["ARIMAX(4,0,1)"]]
x_train_terbaik <- x_train_xt; x_test_terbaik <- x_test_xt
cat("Skenario terbaik: Xt\nModel ARIMAX terbaik: ARIMAX(4,0,1)\nAIC:", AIC(model_arimax_terbaik), "\nBIC:", BIC(model_arimax_terbaik), "\n")
## Skenario terbaik: Xt
## Model ARIMAX terbaik: ARIMAX(4,0,1)
## AIC: -21738.1 
## BIC: -21689.09
summary(model_arimax_terbaik); uji_signifikansi(model_arimax_terbaik)
## Series: y 
## Regression with ARIMA(4,0,1) errors 
## 
## Coefficients:
##           ar1     ar2      ar3      ar4     ma1  intercept      Xt
##       -0.6728  -0.056  -0.0533  -0.0742  0.6510      1e-04  0.2836
## s.e.   0.1017   0.021   0.0212   0.0177  0.1007      1e-04  0.0160
## 
## sigma^2 = 9.456e-05:  log likelihood = 10877.05
## AIC=-21738.1   AICc=-21738.06   BIC=-21689.09
## 
## Training set error measures:
##                         ME        RMSE         MAE MPE MAPE      MASE
## Training set -4.204684e-06 0.009714347 0.006784578 NaN  Inf 0.6697186
##                      ACF1
## Training set 0.0007546374
##   Parameter      Estimate    Std_Error    Z_value      p_value
## 1       ar1 -6.728417e-01 0.1017060436 -6.6155528 3.701659e-11
## 2       ar2 -5.597164e-02 0.0210315382 -2.6613193 7.783510e-03
## 3       ar3 -5.328292e-02 0.0211512969 -2.5191324 1.176444e-02
## 4       ar4 -7.422414e-02 0.0177204471 -4.1886153 2.806617e-05
## 5       ma1  6.509507e-01 0.1007468543  6.4612510 1.038409e-10
## 6 intercept  5.915819e-05 0.0001497484  0.3950507 6.928055e-01
## 7        Xt  2.835807e-01 0.0159566029 17.7719942 1.164754e-70
res_arimax <- na.omit(as.numeric(residuals(model_arimax_terbaik))); res_arimax2 <- res_arimax^2; fitdf_arimax <- 4 + 1
ljung_res_15 <- Box.test(res_arimax, lag = 15, type = "Ljung-Box", fitdf = fitdf_arimax)
jb_res <- tseries::jarque.bera.test(res_arimax)
par(mfrow = c(1,2)); acf(res_arimax2, lag.max = 30, main = "ACF Residual Kuadrat ARIMAX"); pacf(res_arimax2, lag.max = 30, main = "PACF Residual Kuadrat ARIMAX"); par(mfrow = c(1,1))

arch_lm_15 <- FinTS::ArchTest(res_arimax, lags = 15)

rekap_diagnostik_residual <- data.frame(Uji = c("Ljung-Box Residual lag 15","Jarque-Bera Normalitas Residual","ARCH-LM lag 15"), Statistik = c(as.numeric(ljung_res_15$statistic), as.numeric(jb_res$statistic), as.numeric(arch_lm_15$statistic)), P_Value = c(ljung_res_15$p.value, jb_res$p.value, arch_lm_15$p.value), Keputusan = c(ifelse(ljung_res_15$p.value > 0.05, "Tidak terdapat autokorelasi residual", "Terdapat autokorelasi residual"), ifelse(jb_res$p.value > 0.05, "Residual berdistribusi normal", "Residual tidak berdistribusi normal"),  ifelse(arch_lm_15$p.value > 0.05, "Tidak terdapat efek ARCH/GARCH", "Terdapat efek ARCH/GARCH")))
rekap_diagnostik_residual
##                               Uji  Statistik       P_Value
## 1       Ljung-Box Residual lag 15   12.92224  2.280520e-01
## 2 Jarque-Bera Normalitas Residual 8973.72566  0.000000e+00
## 3                  ARCH-LM lag 15  664.95295 5.434552e-132
##                              Keputusan
## 1 Tidak terdapat autokorelasi residual
## 2  Residual tidak berdistribusi normal
## 3             Terdapat efek ARCH/GARCH
# =========================
# 8. ARIMAX-GARCH(1,1)
# =========================
p_arimax <- 4; q_arimax <- 1; P_garch <- 1; Q_garch <- 1; lag_ljung <- 15
y_garch <- as.numeric(y_train); xreg_garch <- as.matrix(x_train_terbaik); stopifnot(length(y_garch) == nrow(xreg_garch))
spec_arimax_garch_11 <- rugarch::ugarchspec(variance.model = list(model = "sGARCH", garchOrder = c(P_garch,Q_garch)), mean.model = list(armaOrder = c(p_arimax,q_arimax), include.mean = TRUE, external.regressors = xreg_garch), distribution.model = "norm")
model_arimax_garch_terbaik <- rugarch::ugarchfit(spec = spec_arimax_garch_11, data = y_garch, solver = "hybrid")
model_arimax_garch_terbaik
## 
## *---------------------------------*
## *          GARCH Model Fit        *
## *---------------------------------*
## 
## Conditional Variance Dynamics    
## -----------------------------------
## GARCH Model  : sGARCH(1,1)
## Mean Model   : ARFIMA(4,0,1)
## Distribution : norm 
## 
## Optimal Parameters
## ------------------------------------
##         Estimate  Std. Error  t value Pr(>|t|)
## mu      0.000187    0.000111   1.6785 0.093254
## ar1    -0.675781    0.222168  -3.0418 0.002352
## ar2    -0.105452    0.024085  -4.3783 0.000012
## ar3    -0.094599    0.028050  -3.3725 0.000745
## ar4    -0.056395    0.018587  -3.0341 0.002412
## ma1     0.634412    0.222062   2.8569 0.004278
## mxreg1  0.252301    0.015536  16.2402 0.000000
## omega   0.000003    0.000002   1.8617 0.062644
## alpha1  0.103854    0.009733  10.6705 0.000000
## beta1   0.860659    0.005853 147.0387 0.000000
## 
## Robust Standard Errors:
##         Estimate  Std. Error  t value Pr(>|t|)
## mu      0.000187    0.000234  0.79956 0.423966
## ar1    -0.675781    0.205084 -3.29515 0.000984
## ar2    -0.105452    0.026596 -3.96499 0.000073
## ar3    -0.094599    0.028801 -3.28457 0.001021
## ar4    -0.056395    0.022259 -2.53363 0.011289
## ma1     0.634412    0.206089  3.07834 0.002082
## mxreg1  0.252301    0.025042 10.07506 0.000000
## omega   0.000003    0.000014  0.22201 0.824307
## alpha1  0.103854    0.034720  2.99117 0.002779
## beta1   0.860659    0.101088  8.51398 0.000000
## 
## LogLikelihood : 11309.17 
## 
## Information Criteria
## ------------------------------------
##                     
## Akaike       -6.6800
## Bayes        -6.6619
## Shibata      -6.6800
## Hannan-Quinn -6.6735
## 
## Weighted Ljung-Box Test on Standardized Residuals
## ------------------------------------
##                          statistic p-value
## Lag[1]                       1.365  0.2426
## Lag[2*(p+q)+(p+q)-1][14]     5.113  1.0000
## Lag[4*(p+q)+(p+q)-1][24]     9.476  0.8805
## d.o.f=5
## H0 : No serial correlation
## 
## Weighted Ljung-Box Test on Standardized Squared Residuals
## ------------------------------------
##                         statistic p-value
## Lag[1]                      3.124 0.07715
## Lag[2*(p+q)+(p+q)-1][5]     3.546 0.31628
## Lag[4*(p+q)+(p+q)-1][9]     4.854 0.45110
## d.o.f=2
## 
## Weighted ARCH LM Tests
## ------------------------------------
##             Statistic Shape Scale P-Value
## ARCH Lag[3]   0.03916 0.500 2.000  0.8431
## ARCH Lag[5]   1.05128 1.440 1.667  0.7178
## ARCH Lag[7]   1.88540 2.315 1.543  0.7416
## 
## Nyblom stability test
## ------------------------------------
## Joint Statistic:  7.9528
## Individual Statistics:              
## mu     0.27224
## ar1    0.22155
## ar2    0.13800
## ar3    0.07936
## ar4    0.76100
## ma1    0.21916
## mxreg1 3.20693
## omega  0.08314
## alpha1 0.15000
## beta1  0.09303
## 
## Asymptotic Critical Values (10% 5% 1%)
## Joint Statistic:          2.29 2.54 3.05
## Individual Statistic:     0.35 0.47 0.75
## 
## Sign Bias Test
## ------------------------------------
##                     t-value      prob sig
## Sign Bias           0.01242 9.901e-01    
## Negative Sign Bias  3.44084 5.870e-04 ***
## Positive Sign Bias  1.25723 2.088e-01    
## Joint Effect       23.72328 2.853e-05 ***
## 
## 
## Adjusted Pearson Goodness-of-Fit Test:
## ------------------------------------
##   group statistic p-value(g-1)
## 1    20     107.5    2.270e-14
## 2    30     129.0    1.402e-14
## 3    40     143.6    6.952e-14
## 4    50     146.9    1.046e-11
## 
## 
## Elapsed time : 0.8878551
coef_garch <- model_arimax_garch_terbaik@fit$matcoef
tabel_koef_garch <- data.frame(Parameter = rownames(coef_garch), Estimate = coef_garch[,1], Std_Error = coef_garch[,2], t_value = coef_garch[,3], p_value = coef_garch[,4], Keputusan = ifelse(coef_garch[,4] < 0.05, "Signifikan", "Tidak Signifikan"), row.names = NULL)
tabel_koef_garch
##    Parameter      Estimate    Std_Error    t_value      p_value
## 1         mu  1.868400e-04 1.113153e-04   1.678475 9.325444e-02
## 2        ar1 -6.757810e-01 2.221679e-01  -3.041758 2.352007e-03
## 3        ar2 -1.054520e-01 2.408493e-02  -4.378339 1.195875e-05
## 4        ar3 -9.459902e-02 2.805009e-02  -3.372504 7.448805e-04
## 5        ar4 -5.639537e-02 1.858688e-02  -3.034150 2.412142e-03
## 6        ma1  6.344124e-01 2.220624e-01   2.856910 4.277872e-03
## 7     mxreg1  2.523009e-01 1.553559e-02  16.240184 0.000000e+00
## 8      omega  3.194582e-06 1.715942e-06   1.861708 6.264429e-02
## 9     alpha1  1.038535e-01 9.732738e-03  10.670534 0.000000e+00
## 10     beta1  8.606585e-01 5.853280e-03 147.038663 0.000000e+00
##           Keputusan
## 1  Tidak Signifikan
## 2        Signifikan
## 3        Signifikan
## 4        Signifikan
## 5        Signifikan
## 6        Signifikan
## 7        Signifikan
## 8  Tidak Signifikan
## 9        Signifikan
## 10       Signifikan
std_resid <- na.omit(as.numeric(rugarch::residuals(model_arimax_garch_terbaik, standardize = TRUE))); std_resid2 <- std_resid^2
uji_lb_std <- Box.test(std_resid, lag = lag_ljung, type = "Ljung-Box", fitdf = p_arimax + q_arimax)
uji_lb_std2 <- Box.test(std_resid2, lag = lag_ljung, type = "Ljung-Box")
uji_arch_std <- FinTS::ArchTest(std_resid, lags = lag_ljung)
uji_jb_std <- tseries::jarque.bera.test(std_resid)

ringkasan_diagnostik_garch <- data.frame(Uji = c("Ljung-Box standardized residual lag 15","Ljung-Box standardized residual kuadrat lag 15","ARCH-LM standardized residual lag 15","Jarque-Bera standardized residual"), Statistik = c(as.numeric(uji_lb_std$statistic), as.numeric(uji_lb_std2$statistic), as.numeric(uji_arch_std$statistic), as.numeric(uji_jb_std$statistic)), P_Value = c(uji_lb_std$p.value, uji_lb_std2$p.value, uji_arch_std$p.value, uji_jb_std$p.value), Keputusan = c(ifelse(uji_lb_std$p.value > 0.05, "Tidak ada autokorelasi standardized residual", "Masih ada autokorelasi standardized residual"), ifelse(uji_lb_std2$p.value > 0.05, "Tidak ada autokorelasi standardized residual kuadrat", "Masih ada autokorelasi standardized residual kuadrat"), ifelse(uji_arch_std$p.value > 0.05, "Tidak ada efek ARCH/GARCH sisa", "Masih ada efek ARCH/GARCH sisa"), ifelse(uji_jb_std$p.value > 0.05, "Standardized residual berdistribusi normal", "Standardized residual tidak berdistribusi normal")))
ringkasan_diagnostik_garch
##                                              Uji   Statistik   P_Value
## 1         Ljung-Box standardized residual lag 15   12.300282 0.2654623
## 2 Ljung-Box standardized residual kuadrat lag 15    9.942555 0.8233377
## 3           ARCH-LM standardized residual lag 15    8.557149 0.8995122
## 4              Jarque-Bera standardized residual 1765.585351 0.0000000
##                                              Keputusan
## 1         Tidak ada autokorelasi standardized residual
## 2 Tidak ada autokorelasi standardized residual kuadrat
## 3                       Tidak ada efek ARCH/GARCH sisa
## 4     Standardized residual tidak berdistribusi normal
# =========================
# 9. PREDIKSI DATA UJI: ROLLING ONE-STEP-AHEAD
# =========================
n_test <- nrow(x_test_terbaik); pred_mean <- pred_sigma <- rep(NA_real_, n_test)
for(i in 1:n_test) {
  y_hist <- c(as.numeric(data_train_arimax$Return_IHSG), if(i == 1) NULL else as.numeric(data_test_arimax$Return_IHSG[1:(i-1)]))
  x_hist <- if(i == 1) as.matrix(x_train_terbaik) else rbind(as.matrix(x_train_terbaik), as.matrix(x_test_terbaik[1:(i-1), , drop = FALSE]))
  x_next <- as.matrix(x_test_terbaik[i, , drop = FALSE]); colnames(x_next) <- colnames(x_train_terbaik)
  spec_i <- rugarch::ugarchspec(variance.model = list(model = "sGARCH", garchOrder = c(P_garch,Q_garch)), mean.model = list(armaOrder = c(p_arimax,q_arimax), include.mean = TRUE, external.regressors = x_hist), distribution.model = "norm")
  fit_i <- tryCatch(rugarch::ugarchfit(spec = spec_i, data = y_hist, solver = "hybrid"), error = function(e) NULL)
  if(!is.null(fit_i) && fit_i@fit$convergence == 0) {fc_i <- tryCatch(rugarch::ugarchforecast(fit_i, n.ahead = 1, external.forecasts = list(mregfor = x_next)), error = function(e) NULL); if(!is.null(fc_i)) {pred_mean[i] <- as.numeric(fitted(fc_i)); pred_sigma[i] <- as.numeric(sigma(fc_i))}}
  if(i %% 25 == 0 || i == n_test) cat("Prediksi ke-", i, " dari ", n_test, " selesai\n", sep = "")
}
## Prediksi ke-25 dari 376 selesai
## Prediksi ke-50 dari 376 selesai
## Prediksi ke-75 dari 376 selesai
## Prediksi ke-100 dari 376 selesai
## Prediksi ke-125 dari 376 selesai
## Prediksi ke-150 dari 376 selesai
## Prediksi ke-175 dari 376 selesai
## Prediksi ke-200 dari 376 selesai
## Prediksi ke-225 dari 376 selesai
## Prediksi ke-250 dari 376 selesai
## Prediksi ke-275 dari 376 selesai
## Prediksi ke-300 dari 376 selesai
## Prediksi ke-325 dari 376 selesai
## Prediksi ke-350 dari 376 selesai
## Prediksi ke-375 dari 376 selesai
## Prediksi ke-376 dari 376 selesai
hasil_uji <- data.frame(Date = data_test_arimax$Date[1:n_test], Return_Aktual = as.numeric(data_test_arimax$Return_IHSG[1:n_test]), Return_Prediksi = pred_mean, Volatilitas_Prediksi = pred_sigma) %>% filter(!is.na(Return_Prediksi), !is.na(Volatilitas_Prediksi)) %>% mutate(Volatilitas_Aktual = abs(Return_Aktual))
head(hasil_uji); tail(hasil_uji)
##         Date Return_Aktual Return_Prediksi Volatilitas_Prediksi
## 1 2024-11-12   0.007612909    3.031242e-03          0.011194613
## 2 2024-11-13  -0.001820834    5.294784e-04          0.010630117
## 3 2024-11-14  -0.012960108   -6.911561e-05          0.010044875
## 4 2024-11-15  -0.007415263   -1.364952e-03          0.010355328
## 5 2024-11-18  -0.003774608   -1.787614e-03          0.009962973
## 6 2024-11-19   0.008573680    2.285253e-03          0.009437662
##   Volatilitas_Aktual
## 1        0.007612909
## 2        0.001820834
## 3        0.012960108
## 4        0.007415263
## 5        0.003774608
## 6        0.008573680
##           Date Return_Aktual Return_Prediksi Volatilitas_Prediksi
## 371 2026-06-17 -0.0054874770   -0.0034345328           0.02930924
## 372 2026-06-18 -0.0078108503   -0.0065935345           0.02752599
## 373 2026-06-19  0.0007773607    0.0020848062           0.02583810
## 374 2026-06-22 -0.0098342806    0.0026283842           0.02429807
## 375 2026-06-23 -0.0025143203    0.0006675728           0.02315296
## 376 2026-06-24 -0.0356413512   -0.0026774994           0.02177303
##     Volatilitas_Aktual
## 371       0.0054874770
## 372       0.0078108503
## 373       0.0007773607
## 374       0.0098342806
## 375       0.0025143203
## 376       0.0356413512
plot_dua_garis <- function(data, y1, y2, lab1, lab2, ylab) {d <- bind_rows(data %>% transmute(Date, Nilai = .data[[y1]], Keterangan = lab1), data %>% transmute(Date, Nilai = .data[[y2]], Keterangan = lab2)); ggplot(d, aes(Date, Nilai, color = Keterangan)) + geom_line(linewidth = 0.7) + scale_color_manual(values = setNames(c("blue","red"), c(lab1,lab2))) + labs(x = "Date", y = ylab, color = "Keterangan") + tema_ts()}
plot.return.uji <- plot_dua_garis(hasil_uji, "Return_Aktual", "Return_Prediksi", "Actual Return", "Prediction Return", "Return")
plot.vol.uji <- plot_dua_garis(hasil_uji, "Volatilitas_Aktual", "Volatilitas_Prediksi", "Actual Volatility", "Prediction Volatility", "Volatility")
plot.return.uji; plot.vol.uji

ringkasan_prediksi <- data.frame(Jumlah_Data_Uji = nrow(hasil_uji), Rata_rata_Return_Aktual = mean(hasil_uji$Return_Aktual), Rata_rata_Return_Prediksi = mean(hasil_uji$Return_Prediksi), Rata_rata_Volatilitas_Aktual = mean(hasil_uji$Volatilitas_Aktual), Rata_rata_Volatilitas_Prediksi = mean(hasil_uji$Volatilitas_Prediksi), Minimum_Volatilitas_Prediksi = min(hasil_uji$Volatilitas_Prediksi), Maksimum_Volatilitas_Prediksi = max(hasil_uji$Volatilitas_Prediksi))
ringkasan_prediksi
##   Jumlah_Data_Uji Rata_rata_Return_Aktual Rata_rata_Return_Prediksi
## 1             376           -0.0005595846              0.0005095166
##   Rata_rata_Volatilitas_Aktual Rata_rata_Volatilitas_Prediksi
## 1                   0.01033608                     0.01232308
##   Minimum_Volatilitas_Prediksi Maksimum_Volatilitas_Prediksi
## 1                  0.006374188                    0.03178697
evaluasi_akurasi <- function(actual, pred) {idx <- is.finite(actual) & is.finite(pred); actual <- as.numeric(actual[idx]); pred <- as.numeric(pred[idx]); mae <- mean(abs(actual - pred)); rmse <- sqrt(mean((actual - pred)^2)); jangkauan <- max(actual) - min(actual); data.frame(n = length(actual), MAE = mae, RMSE = rmse, Jangkauan = jangkauan, Rel_MAE_Persen = ifelse(jangkauan == 0, NA, mae / jangkauan * 100), Rel_RMSE_Persen = ifelse(jangkauan == 0, NA, rmse / jangkauan * 100))}
ringkasan_evaluasi <- bind_rows(evaluasi_akurasi(y_train, fitted(model_arimax_garch_terbaik)) %>% mutate(Data = "Latih", Komponen = "Return"), evaluasi_akurasi(abs(y_train), sigma(model_arimax_garch_terbaik)) %>% mutate(Data = "Latih", Komponen = "Volatilitas"), evaluasi_akurasi(hasil_uji$Return_Aktual, hasil_uji$Return_Prediksi) %>% mutate(Data = "Uji", Komponen = "Return"), evaluasi_akurasi(hasil_uji$Volatilitas_Aktual, hasil_uji$Volatilitas_Prediksi) %>% mutate(Data = "Uji", Komponen = "Volatilitas")) %>% dplyr::select(Data, Komponen, everything())
ringkasan_evaluasi
##    Data    Komponen    n         MAE        RMSE  Jangkauan Rel_MAE_Persen
## 1 Latih      Return 3383 0.006755425 0.009733083 0.19003853       3.554766
## 2 Latih Volatilitas 3383 0.005189624 0.006956661 0.09704184       5.347821
## 3   Uji      Return  376 0.010067145 0.014727597 0.15530952       6.481988
## 4   Uji Volatilitas  376 0.007546261 0.010718750 0.08229083       9.170233
##   Rel_RMSE_Persen
## 1        5.121636
## 2        7.168723
## 3        9.482740
## 4       13.025448
# =========================
# 10. VALUE AT RISK: NORMAL, FHS, STUDENT-t
# =========================
std_resid <- na.omit(as.numeric(rugarch::residuals(model_arimax_garch_terbaik, standardize = TRUE)))
ringkasan_std_resid <- data.frame(Mean = mean(std_resid), Std_Dev = sd(std_resid), Skewness = moments::skewness(std_resid), Kurtosis = moments::kurtosis(std_resid))
uji_jb_std_var <- tseries::jarque.bera.test(std_resid)
ringkasan_std_resid; uji_jb_std_var
##          Mean  Std_Dev   Skewness Kurtosis
## 1 -0.01885228 1.004539 -0.7338683 6.220455
## 
##  Jarque Bera Test
## 
## data:  std_resid
## X-squared = 1765.6, df = 2, p-value < 2.2e-16
alpha_95 <- 0.05; alpha_99 <- 0.01
q_norm_95 <- qnorm(alpha_95); q_norm_99 <- qnorm(alpha_99)
q_fhs_95 <- as.numeric(quantile(std_resid, alpha_95, na.rm = TRUE)); q_fhs_99 <- as.numeric(quantile(std_resid, alpha_99, na.rm = TRUE))
fit_t <- tryCatch(MASS::fitdistr(std_resid, densfun = "t"), error = function(e) NULL)
## Warning in dt((x - m)/s, df, log = TRUE): NaNs produced
if(!is.null(fit_t)) {est_t <- fit_t$estimate; loc_t <- if("m" %in% names(est_t)) as.numeric(est_t["m"]) else 0; scale_t <- if("s" %in% names(est_t)) abs(as.numeric(est_t["s"])) else 1; df_t <- if("df" %in% names(est_t)) as.numeric(est_t["df"]) else NA_real_; q_t_95 <- loc_t + scale_t * qt(alpha_95, df_t); q_t_99 <- loc_t + scale_t * qt(alpha_99, df_t)} else {loc_t <- scale_t <- df_t <- q_t_95 <- q_t_99 <- NA_real_}

rekap_kuantil_var <- data.frame(Metode = c("Normal 95%","Normal 99%","FHS 95%","FHS 99%","Student-t 95%","Student-t 99%"), Alpha = c(alpha_95,alpha_99,alpha_95,alpha_99,alpha_95,alpha_99), Kuantil_Standardized_Residual = c(q_norm_95,q_norm_99,q_fhs_95,q_fhs_99,q_t_95,q_t_99))
param_student_t <- data.frame(Location = loc_t, Scale = scale_t, df = df_t)
rekap_kuantil_var; param_student_t
##          Metode Alpha Kuantil_Standardized_Residual
## 1    Normal 95%  0.05                     -1.644854
## 2    Normal 99%  0.01                     -2.326348
## 3       FHS 95%  0.05                     -1.687754
## 4       FHS 99%  0.01                     -2.947449
## 5 Student-t 95%  0.05                     -1.556590
## 6 Student-t 99%  0.01                     -2.558209
##     Location     Scale       df
## 1 0.02491875 0.8047289 5.645247
hasil_var <- hasil_uji %>% mutate(Loss_Aktual = -Return_Aktual, VaR_Normal_95 = -(Return_Prediksi + Volatilitas_Prediksi * q_norm_95), VaR_Normal_99 = -(Return_Prediksi + Volatilitas_Prediksi * q_norm_99), VaR_FHS_95 = -(Return_Prediksi + Volatilitas_Prediksi * q_fhs_95), VaR_FHS_99 = -(Return_Prediksi + Volatilitas_Prediksi * q_fhs_99), VaR_t_95 = -(Return_Prediksi + Volatilitas_Prediksi * q_t_95), VaR_t_99 = -(Return_Prediksi + Volatilitas_Prediksi * q_t_99))
head(hasil_var); tail(hasil_var)
##         Date Return_Aktual Return_Prediksi Volatilitas_Prediksi
## 1 2024-11-12   0.007612909    3.031242e-03          0.011194613
## 2 2024-11-13  -0.001820834    5.294784e-04          0.010630117
## 3 2024-11-14  -0.012960108   -6.911561e-05          0.010044875
## 4 2024-11-15  -0.007415263   -1.364952e-03          0.010355328
## 5 2024-11-18  -0.003774608   -1.787614e-03          0.009962973
## 6 2024-11-19   0.008573680    2.285253e-03          0.009437662
##   Volatilitas_Aktual  Loss_Aktual VaR_Normal_95 VaR_Normal_99 VaR_FHS_95
## 1        0.007612909 -0.007612909    0.01538226    0.02301132 0.01586251
## 2        0.001820834  0.001820834    0.01695551    0.02419987 0.01741154
## 3        0.012960108  0.012960108    0.01659147    0.02343699 0.01702239
## 4        0.007415263  0.007415263    0.01839795    0.02545505 0.01884220
## 5        0.003774608  0.003774608    0.01817525    0.02496495 0.01860266
## 6        0.008573680 -0.008573680    0.01323832    0.01967003 0.01364320
##   VaR_FHS_99   VaR_t_95   VaR_t_99
## 1 0.02996431 0.01439419 0.02560692
## 2 0.03080225 0.01601726 0.02666459
## 3 0.02967587 0.01570487 0.02576601
## 4 0.03188675 0.01748396 0.02785605
## 5 0.03115297 0.01729588 0.02727499
## 6 0.02553177 0.01240532 0.02185826
##           Date Return_Aktual Return_Prediksi Volatilitas_Prediksi
## 371 2026-06-17 -0.0054874770   -0.0034345328           0.02930924
## 372 2026-06-18 -0.0078108503   -0.0065935345           0.02752599
## 373 2026-06-19  0.0007773607    0.0020848062           0.02583810
## 374 2026-06-22 -0.0098342806    0.0026283842           0.02429807
## 375 2026-06-23 -0.0025143203    0.0006675728           0.02315296
## 376 2026-06-24 -0.0356413512   -0.0026774994           0.02177303
##     Volatilitas_Aktual   Loss_Aktual VaR_Normal_95 VaR_Normal_99 VaR_FHS_95
## 371       0.0054874770  0.0054874770    0.05164394    0.07161802 0.05290131
## 372       0.0078108503  0.0078108503    0.05186976    0.07062857 0.05305063
## 373       0.0007773607 -0.0007773607    0.04041509    0.05802361 0.04152355
## 374       0.0098342806  0.0098342806    0.03733838    0.05389737 0.03838077
## 375       0.0025143203  0.0025143203    0.03741565    0.05319426 0.03840892
## 376       0.0356413512  0.0356413512    0.03849095    0.05332915 0.03942502
##     VaR_FHS_99   VaR_t_95   VaR_t_99
## 371 0.08982202 0.04905702 0.07841371
## 372 0.08772499 0.04944023 0.07701078
## 373 0.07407168 0.03813454 0.06401447
## 374 0.06898892 0.03519375 0.05953115
## 375 0.06757458 0.03537210 0.05856254
## 376 0.06685240 0.03656919 0.05837747
# =========================
# 11. PLOT VaR DENGAN KETERANGAN DAN EXCEEDANCE
# =========================
tema_var <- function() theme_minimal(base_family = "serif") + theme(plot.title = element_text(size = 16, face = "bold", color = "black"), plot.subtitle = element_text(size = 11, color = "black"), axis.text.x = element_text(angle = 45, hjust = 1, size = 11, color = "black"), axis.text.y = element_text(size = 11, color = "black"), axis.title = element_text(size = 14, color = "black"), legend.position = "right", legend.title = element_text(size = 12, face = "bold", color = "black"), legend.text = element_text(size = 11, color = "black"), panel.grid.minor = element_blank(), panel.grid.major = element_line(color = "grey80", linewidth = 0.4), axis.line = element_line(color = "black", linewidth = 0.4))
warna_plot <- c("Actual Loss" = "black", "VaR Normal" = "dodgerblue2", "VaR FHS" = "yellow3", "VaR Student-t" = "green2", "Exceedance" = "darkred")

plot_var_individu <- function(data, var_col, metode, tingkat) {
  data_plot <- data %>% mutate(VaR = .data[[var_col]], Exceedance = Loss_Aktual > VaR)
  ggplot(data_plot, aes(Date)) + geom_line(aes(y = Loss_Aktual, color = "Actual Loss"), linewidth = 0.5) + geom_line(aes(y = VaR, color = metode), linewidth = 0.7) + geom_point(data = data_plot %>% filter(Exceedance), aes(y = Loss_Aktual, color = "Exceedance"), size = 2.3) + scale_color_manual(name = "Keterangan", values = warna_plot, breaks = c("Actual Loss", metode, "Exceedance")) + scale_x_date(date_breaks = "3 months", date_labels = "%Y-%m") + labs(title = paste0(metode, " ", tingkat), subtitle = "Titik exceedance menunjukkan saat actual loss melebihi nilai VaR", x = "Date", y = "Loss / VaR") + guides(color = guide_legend(title = "Keterangan", override.aes = list(linewidth = c(0.8,0.8,0), shape = c(NA,NA,16), size = c(NA,NA,3)))) + tema_var()
}

plot_var_gabungan <- function(data, tingkat = c("95","99")) {
  tingkat <- match.arg(tingkat)
  data_long <- if(tingkat == "95") bind_rows(transmute(data, Date, Loss_Aktual, Metode = "VaR Normal", VaR = VaR_Normal_95), transmute(data, Date, Loss_Aktual, Metode = "VaR FHS", VaR = VaR_FHS_95), transmute(data, Date, Loss_Aktual, Metode = "VaR Student-t", VaR = VaR_t_95)) else bind_rows(transmute(data, Date, Loss_Aktual, Metode = "VaR Normal", VaR = VaR_Normal_99), transmute(data, Date, Loss_Aktual, Metode = "VaR FHS", VaR = VaR_FHS_99), transmute(data, Date, Loss_Aktual, Metode = "VaR Student-t", VaR = VaR_t_99))
  data_long <- data_long %>% mutate(Exceedance = Loss_Aktual > VaR)
  ggplot() + geom_line(data = distinct(data_long, Date, Loss_Aktual), aes(Date, Loss_Aktual, color = "Actual Loss"), linewidth = 0.4) + geom_line(data = data_long, aes(Date, VaR, color = Metode), linewidth = 0.7) + geom_point(data = data_long %>% filter(Exceedance), aes(Date, Loss_Aktual, color = "Exceedance"), size = 2.3) + scale_color_manual(name = "Keterangan", values = warna_plot, breaks = c("Actual Loss","VaR Normal","VaR FHS","VaR Student-t","Exceedance")) + scale_x_date(date_breaks = "3 months", date_labels = "%Y-%m") + labs(title = paste0("Comparison of VaR Estimates at ", tingkat, "% Confidence Level"), subtitle = "Titik exceedance menunjukkan saat actual loss melebihi nilai VaR", x = "Date", y = "Loss / VaR") + guides(color = guide_legend(title = "Keterangan", override.aes = list(linewidth = c(0.8,0.8,0.8,0.8,0), shape = c(NA,NA,NA,NA,16), size = c(NA,NA,NA,NA,3)))) + tema_var()
}

plot_normal_95 <- plot_var_individu(hasil_var, "VaR_Normal_95", "VaR Normal", "95%")
plot_fhs_95 <- plot_var_individu(hasil_var, "VaR_FHS_95", "VaR FHS", "95%")
plot_t_95 <- plot_var_individu(hasil_var, "VaR_t_95", "VaR Student-t", "95%")
plot_normal_99 <- plot_var_individu(hasil_var, "VaR_Normal_99", "VaR Normal", "99%")
plot_fhs_99 <- plot_var_individu(hasil_var, "VaR_FHS_99", "VaR FHS", "99%")
plot_t_99 <- plot_var_individu(hasil_var, "VaR_t_99", "VaR Student-t", "99%")
plot_gabungan_95 <- plot_var_gabungan(hasil_var, "95")
plot_gabungan_99 <- plot_var_gabungan(hasil_var, "99")
plot_normal_95; plot_fhs_95; plot_t_95; plot_normal_99; plot_fhs_99; plot_t_99; plot_gabungan_95; plot_gabungan_99

# =========================
# 12. BACKTESTING VaR: KUPIEC UNCONDITIONAL COVERAGE TEST
# =========================
kupiec_test <- function(loss, var, alpha) {
  idx <- is.finite(loss) & is.finite(var); loss <- as.numeric(loss[idx]); var <- as.numeric(var[idx]); violation <- ifelse(loss > var, 1, 0)
  n <- length(violation); x <- sum(violation); p_hat <- x / n; eps <- 1e-10; p_hat_adj <- min(max(p_hat, eps), 1 - eps); alpha_adj <- min(max(alpha, eps), 1 - eps)
  logL_H0 <- (n - x) * log(1 - alpha_adj) + x * log(alpha_adj); logL_H1 <- (n - x) * log(1 - p_hat_adj) + x * log(p_hat_adj); LR_Kupiec <- -2 * (logL_H0 - logL_H1); p_value <- 1 - pchisq(LR_Kupiec, df = 1)
  data.frame(Jumlah_Data = n, Pelanggaran = x, Pelanggaran_Harapan = n * alpha, Rasio_Pelanggaran = p_hat, LR_Kupiec = LR_Kupiec, P_Value_Kupiec = p_value, Keputusan_Kupiec = ifelse(p_value > 0.05, "Lolos Kupiec", "Tidak lolos Kupiec"))
}
buat_backtest_kupiec <- function(data, nama_metode, kolom_var, alpha) data.frame(Metode = nama_metode, Tingkat = paste0((1 - alpha) * 100, "%"), kupiec_test(data$Loss_Aktual, data[[kolom_var]], alpha))
bt_spec <- data.frame(Metode = c("Normal","FHS","Student-t","Normal","FHS","Student-t"), Kolom = c("VaR_Normal_95","VaR_FHS_95","VaR_t_95","VaR_Normal_99","VaR_FHS_99","VaR_t_99"), Alpha = c(alpha_95,alpha_95,alpha_95,alpha_99,alpha_99,alpha_99))
rekap_backtest_kupiec <- bind_rows(lapply(1:nrow(bt_spec), function(i) buat_backtest_kupiec(hasil_var, bt_spec$Metode[i], bt_spec$Kolom[i], bt_spec$Alpha[i]))) %>% arrange(Tingkat, desc(P_Value_Kupiec))
rekap_backtest_kupiec
##      Metode Tingkat Jumlah_Data Pelanggaran Pelanggaran_Harapan
## 1       FHS     95%         376          27               18.80
## 2    Normal     95%         376          29               18.80
## 3 Student-t     95%         376          32               18.80
## 4       FHS     99%         376           5                3.76
## 5 Student-t     99%         376           9                3.76
## 6    Normal     99%         376          15                3.76
##   Rasio_Pelanggaran  LR_Kupiec P_Value_Kupiec   Keputusan_Kupiec
## 1        0.07180851  3.3366189   6.775370e-02       Lolos Kupiec
## 2        0.07712766  5.0335378   2.486107e-02 Tidak lolos Kupiec
## 3        0.08510638  8.1341743   4.343863e-03 Tidak lolos Kupiec
## 4        0.01329787  0.3743248   5.406563e-01       Lolos Kupiec
## 5        0.02393617  5.3046129   2.126903e-02 Tidak lolos Kupiec
## 6        0.03989362 19.3718042   1.075838e-05 Tidak lolos Kupiec