Pendahuluan

Pasar modal Indonesia, khususnya saham PT Bali Bintang Sejahtera Tbk (BOLA), merupakan instrumen investasi yang cukup populer. Fluktuasi harga saham yang dinamis memerlukan analisis mendalam untuk membantu investor membuat keputusan yang lebih tepat. Peramalan (forecasting) harga saham merupakan salah satu cara untuk memprediksi tren harga di masa depan berdasarkan data historis.

Dalam laporan ini, kami menggunakan data historis harga penutupan saham BOLA untuk melakukan peramalan menggunakan metode ARIMA (AutoRegressive Integrated Moving Average). ARIMA adalah salah satu model time series yang paling populer dan terbukti efektif untuk peramalan data deret waktu.

1. Loading Library

library(forecast)
library(tseries)
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
library(ggplot2)

2. Membaca dan Persiapan Data

# Membaca file CSV
data <- read.csv("C:/Users/qonit/Downloads/Data_Historis_BOLA.csv")

# Tampilkan beberapa baris pertama
head(data, 10)
##       Tanggal Terakhir Pembukaan Tertinggi Terendah    Vol. Perubahan.
## 1  28/05/2025      102       104       104      101 275,90K      0,00%
## 2  27/05/2025      102       103       104      101 394,60K     -0,97%
## 3  26/05/2025      103        97       109       96   9,31M      6,19%
## 4  23/05/2025       97        96        98       96 107,70K      2,11%
## 5  22/05/2025       95        98       105       94   3,61M     -3,06%
## 6  21/05/2025       98        96        99       95   1,54M      1,03%
## 7  20/05/2025       97        95       100       95   1,84M      1,04%
## 8  19/05/2025       96        95        96       95 368,20K      1,05%
## 9  16/05/2025       95        94        96       93 463,50K      1,06%
## 10 15/05/2025       94        96        97       94 407,70K     -2,08%
# Konversi tanggal
data$Tanggal <- as.Date(data$Tanggal, format = "%d/%m/%Y")

# Urutkan berdasarkan tanggal (ascending)
data <- data[order(data$Tanggal), ]

# Ekstrak harga penutupan (kolom "Terakhir")
harga <- data$Terakhir
  • Jumlah data: 270

  • Harga min: 80

  • Harga max: 115

  • Mean: 96.5074074

  • Median: 96

3. Visualisasi Data Asli

Grafik berikut menunjukkan pergerakan harga saham BOLA dari waktu ke waktu:

# Convert to time series
ts_harga <- ts(harga)

# Plot data asli
plot(ts_harga, 
     main = "Harga Penutupan Saham BOLA",
     xlab = "Periode",
     ylab = "Harga (Rp)",
     type = "l",
     col = "blue",
     lwd = 2)
grid()

Interpretasi:

  • Data menunjukkan fluktuasi harga yang signifikan

  • Ada indikasi trend dan volatilitas dalam data

  • Data perlu diperiksa stationeritas-nya sebelum modeling ARIMA

4. Uji Stationeritas (ADF Test)

Konsep Stationeritas

Stationeritas adalah prasyarat penting dalam analisis ARIMA. Data dikatakan stasioner jika mean, variance, dan autocorrelation-nya konstan sepanjang waktu.

# ADF Test untuk cek stationeritas
adf_test <- adf.test(harga)
print(adf_test)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  harga
## Dickey-Fuller = -3.0308, Lag order = 6, p-value = 0.1418
## alternative hypothesis: stationary
d_parameter = 1

Interpretasi:

Data tidak stasioner (p-value >= 0.05) sehingga perlu differencing

5. Differencing

# Jika tidak stationer, lakukan differencing
if(d_parameter == 1) {
  harga_diff <- diff(harga, differences = 1)
  
  adf_test_diff <- adf.test(harga_diff)
  print(adf_test_diff)
  if(adf_test_diff$p.value < 0.05) {
  cat("\n✓ Data STATIONER (p-value < 0.05)\n")
  d_parameter <- 1
  } 
  else {
  cat("\n✗ Data TIDAK STATIONER (p-value >= 0.05)\n")
  cat("  Perlu differencing (d > 0)\n")}
  
  # Plot data asli
  plot(ts_harga, main = "Data Asli", type = "l", col = "blue")
  
  # Plot differencing
  plot(harga_diff, main = "Data Setelah Differencing (d=1)", type = "l", col = "red")
}
## Warning in adf.test(harga_diff): p-value smaller than printed p-value
## 
##  Augmented Dickey-Fuller Test
## 
## data:  harga_diff
## Dickey-Fuller = -7.6349, Lag order = 6, p-value = 0.01
## alternative hypothesis: stationary
## 
## 
## ✓ Data STATIONER (p-value < 0.05)

Jumlah data setelah differencing: 269

Hilang 1 observasi karena perhitungan selisih

Statistik data setelah differencing

Statistik Perubahan Harga:

  • Mean: 0.063197

  • Std dev: 2.5051631

  • Minimum: -9

  • Maksimum: 15

6. Plot ACF dan PACF

# ACF plot
acf(harga_diff, main = "ACF Plot", lag.max = 20)

# PACF plot
pacf(harga_diff, main = "PACF Plot", lag.max = 20)

7. Pembagian Data (Train-Test Split)

Untuk mendapatkan evaluasi yang objektif dan realistis, data dibagi menjadi:

  • Training Data (80%): Digunakan untuk melatih model

  • Testing Data (20%): Digunakan untuk evaluasi pada data yang belum pernah dilihat sebelumnya

Pembagian data training dan testing

# Hitung ukuran masing-masing
n_total <- length(harga)
train_size <- round(0.8 * n_total)
test_size <- n_total - train_size

# Split data
train_data <- harga[1:train_size]
test_data <- harga[(train_size + 1):n_total]

# Visualisasi split
plot(ts(harga),
     main = "Data Split: Training (Biru) vs Testing (Merah)",
     xlab = "Periode",
     ylab = "Harga (Rp)",
     type = "l",
     lwd = 1,
     col = "gray")

lines(ts(train_data),
      col = "steelblue",
      lwd = 2)

lines((train_size + 1):(train_size + test_size),
      test_data,
      col = "darkred",
      lwd = 2)

legend("topright",
       legend = c("Training Data", "Testing Data"),
       col = c("steelblue", "darkred"),
       lty = 1,
       lwd = 2)
grid(col = "gray80")

  • Jumlah data training: 216 (80%)

  • Jumlah data testing: 54 (20%)

8. Auto ARIMA - Otomatis Cari Parameter Terbaik

# Auto ARIMA akan otomatis mencari p, d, q terbaik
model_auto <- auto.arima(train_data, 
                         seasonal = FALSE,
                         stepwise = TRUE,
                         trace = TRUE)
## 
##  Fitting models using approximations to speed things up...
## 
##  ARIMA(2,1,2) with drift         : 1002.287
##  ARIMA(0,1,0) with drift         : 1006.719
##  ARIMA(1,1,0) with drift         : 1006.401
##  ARIMA(0,1,1) with drift         : 1006.398
##  ARIMA(0,1,0)                    : 1004.693
##  ARIMA(1,1,2) with drift         : 1008.043
##  ARIMA(2,1,1) with drift         : 1000.737
##  ARIMA(1,1,1) with drift         : 1007.747
##  ARIMA(2,1,0) with drift         : 1006.959
##  ARIMA(3,1,1) with drift         : 1007.887
##  ARIMA(3,1,0) with drift         : 1006.257
##  ARIMA(3,1,2) with drift         : Inf
##  ARIMA(2,1,1)                    : 998.8087
##  ARIMA(1,1,1)                    : 1005.729
##  ARIMA(2,1,0)                    : 1004.912
##  ARIMA(3,1,1)                    : 1005.775
##  ARIMA(2,1,2)                    : 1000.444
##  ARIMA(1,1,0)                    : 1004.385
##  ARIMA(1,1,2)                    : 1006.004
##  ARIMA(3,1,0)                    : 1004.168
##  ARIMA(3,1,2)                    : Inf
## 
##  Now re-fitting the best model(s) without approximations...
## 
##  ARIMA(2,1,1)                    : 1004.684
## 
##  Best model: ARIMA(2,1,1)

Ringkasan model

summary(model_auto)
## Series: train_data 
## ARIMA(2,1,1) 
## 
## Coefficients:
##          ar1      ar2      ma1
##       0.7552  -0.0055  -0.8805
## s.e.  0.1244   0.0781   0.1044
## 
## sigma^2 = 6.112:  log likelihood = -498.25
## AIC=1004.49   AICc=1004.68   BIC=1017.98
## 
## Training set error measures:
##                      ME     RMSE      MAE         MPE     MAPE     MASE
## Training set 0.04485744 2.449281 1.617058 0.004448716 1.659647 1.016571
##                      ACF1
## Training set -0.001796969

Parameter yang dipilih:

  • p (AR): 2

  • d (I-differencing): 1

  • q (MA): 1

  • Model: ARIMA(2,1,1)

Persamaan model ARIMA(2,1,1) tanpa konstan:

\[ Y(T) = Y(t-1) + \phi_1 \Delta Y(t-1) + \phi_2 \Delta Y(t-2) + \theta_1 \varepsilon(t-1) + \varepsilon(t) \] Dimana:

  • \(Y(T)\) = harga pada periode \(t\)

  • \(Y(t-1)\) = harga periode sebelumnya

  • \(\phi_1\) = koefisien AR lag 1

  • \(\phi_2\) = koefisien AR lag 2

  • \(\theta_1\) = koefisien MA lag 1

  • \(\varepsilon(t)\) = error pada periode \(t\)

9. Diagnostik Model

Periksa apakah residual model adalah white noise:

tsdiag(model_auto, gof.lag = 20)

1. Standardized Residuals

Observasi:

  • Residual tersebar random sepanjang periode waktu (0-250)

  • Hampir semua residual berada dalam rentang -2 sampai +2

  • Tidak ada pola atau trend yang terlihat

  • Hanya ada beberapa spike minor di sekitar periode 100-120

Interpretasi:

  • Residual sudah white noise

  • Tidak ada autokorelasi yang signifikan

  • Tidak ada seasonal pattern

  • Tidak ada trend dalam residual

  • Model sudah menangkap pola data dengan baik

2. ACF of Residuals

Observasi:

  • Semua lag berada dalam garis biru confidence interval

  • Tidak ada bar yang keluar dari gatas atas atau bawah

  • ACF hampir flat (rata) di sekitar 0

  • Lag 0 selalu 1.0 (standard untuk ACF)

Interpretasi:

  • Tidak ada autokorelasi residual

  • Tidak ada lag yang signifikan secara statistik

  • Residual benar-benar independent (saling tidak bergantung)

  • Ini membuktikan model ARIMA(2,1,1) sudah optimal

  • Tidak ada pola yang terlewatkan

3. Ljung-Box Test (p-values)

Observasi:

  • Semua p-value berada jauh di atas 0.05 (sekitar 0.5-1.0)

  • Tidak ada satu pun yang turun ke bawah garis 0.05

  • p-value tetap tinggi dan stabil di semua lag

Interpretasi:

  • Residual definitif adalah white noise

  • Ljung-Box test tidak signifikan di semua lag

  • Secara statistik terbukti:

    • H₀: “Residual adalah white noise” DITERIMA

    • H₁: “Residual ada autokorelasi” DITOLAK

  • Confidence 95%: residual adalah pure random

10. Forecasting pada Testing Period

Setelah model dilatih, dilakukan forecast untuk periode testing

# Forecast untuk test_size hari ke depan
forecast_test <- forecast(model_auto, h = test_size)

# Ambil nilai prediksi
forecast_values <- as.numeric(forecast_test$mean)

Forecast untuk 54 hari ke depan menggunakan model yang dilatih dari training data

# Buat tabel perbandingan
comparison <- data.frame(
  Hari = 1:test_size,
  Prediksi = round(forecast_values, 0),
  Aktual = test_data,
  Error = round(test_data - forecast_values, 0),
  Persen_Error = round(abs(test_data - forecast_values) / test_data * 100, 2)
)

Tabel Prediksi vs Aktual (10 baris awal)

print(head(comparison, 10))
##    Hari Prediksi Aktual Error Persen_Error
## 1     1       89     91     2         1.98
## 2     2       89     91     2         1.81
## 3     3       89     92     3         2.76
## 4     4       90     90     0         0.50
## 5     5       90     91     1         1.53
## 6     6       90     90     0         0.38
## 7     7       90     90     0         0.34
## 8     8       90     91     1         1.41
## 9     9       90     90     0         0.29
## 10   10       90     92     2         2.44

Tabel Prediksi vs Aktual (10 baris akhir)

print(tail(comparison, 10))
##    Hari Prediksi Aktual Error Persen_Error
## 45   45       90     94     4         4.47
## 46   46       90     95     5         5.48
## 47   47       90     96     6         6.46
## 48   48       90     97     7         7.43
## 49   49       90     98     8         8.37
## 50   50       90     95     5         5.48
## 51   51       90     97     7         7.43
## 52   52       90    103    13        12.82
## 53   53       90    102    12        11.96
## 54   54       90    102    12        11.96

11. Evaluasi Model pada Testing Data

Menghitung Metrik Akurasi (Out-of-Sample)

# Hitung error
errors <- test_data - forecast_values

# MAE (Mean Absolute Error)
mae_test <- mean(abs(errors))

# RMSE (Root Mean Squared Error)
rmse_test <- sqrt(mean(errors^2))

# MAPE (Mean Absolute Percentage Error)
mape_test <- mean(abs(errors / test_data)) * 100

Metrik Akurasi pada Testing Data

  • MAE (Mean Absolute Error): 4.5200835

  • RMSE (Root Mean Squared Error): 5.5880863

  • MAPE (Mean Absolute Percentage Error): 4.8738392%

Interpretasi Akurasi:

# Interpretasi akurasi
if(mape_test < 5) {
  cat("MODEL SANGAT BAIK (MAPE < 5%)\n")
} else if(mape_test < 10) {
  cat("MODEL BAIK (MAPE < 10%)\n")
} else if(mape_test < 20) {
  cat("MODEL CUKUP (MAPE < 20%)\n")
} else {
  cat("MODEL KURANG BAIK (MAPE >= 20%)\n")
}
## MODEL SANGAT BAIK (MAPE < 5%)

12. Visualisasi: Prediksi vs Aktual

par(mfrow = c(3, 1), mar = c(4, 4, 3, 1))

# Plot 1: Prediksi vs Aktual
plot(1:test_size, test_data,
     type = "l",
     col = "steelblue",
     lwd = 2.5,
     main = "Testing Period: Prediksi vs Nilai Aktual",
     xlab = "Hari (dalam Testing Period)",
     ylab = "Harga (Rp)",
     ylim = range(c(test_data, forecast_values), na.rm = TRUE))

lines(1:test_size, forecast_values,
      col = "red",
      lwd = 2.5,
      lty = 2)

legend("topright",
       legend = c("Aktual", "Prediksi"),
       col = c("steelblue", "red"),
       lty = c(1, 2),
       lwd = 2.5,
       cex = 1.2)

grid(col = "gray80")

# Plot 2: Forecast Error
plot(1:test_size, errors,
     type = "l",
     col = "darkred",
     lwd = 2,
     main = "Forecast Error (Aktual - Prediksi)",
     xlab = "Hari (dalam Testing Period)",
     ylab = "Error (Rp)")

abline(h = 0, col = "blue", lty = 2, lwd = 2)
abline(h = c(mae_test, -mae_test), col = "green", lty = 3, lwd = 1.5)

legend("topright",
       legend = c("Error", "MAE Boundary"),
       col = c("darkred", "green"),
       lty = c(1, 3),
       lwd = 2)

grid(col = "gray80")

# Plot 3: Histogram Error
hist(errors,
     main = "Distribusi Error Testing Data",
     xlab = "Error (Rp)",
     ylab = "Frekuensi",
     breaks = 15,
     col = "lightblue",
     border = "steelblue")

abline(v = 0, col = "red", lty = 2, lwd = 2)
abline(v = c(mae_test, -mae_test), col = "green", lty = 3, lwd = 1.5)

par(mfrow = c(1, 1))

Statistik Error pada Testing Data:

  • Mean Error 2.0429048

  • Median Error 1.7250179

  • Std Dev Error 5.2501131

  • Min Error -9.7940779

  • Max Error 13.2027289

13. Forecasting untuk Periode Mendatang

Retrain Model dengan Semua Data

Setelah validasi berhasil, dilakukan retrain model menggunakan semua data untuk forecast periode mendatang:

# Retrain dengan semua data
model_final <- arima(harga, order = c(0, 1, 0), method = "ML")

# Forecast 15 hari ke depan
h_forecast <- 15
forecast_final <- forecast(model_final, h = h_forecast)

print(forecast_final)
##     Point Forecast    Lo 80    Hi 80    Lo 95    Hi 95
## 271            102 98.79445 105.2055 97.09754 106.9025
## 272            102 97.46667 106.5333 95.06688 108.9331
## 273            102 96.44783 107.5522 93.50869 110.4913
## 274            102 95.58891 108.4111 92.19508 111.8049
## 275            102 94.83218 109.1678 91.03777 112.9622
## 276            102 94.14805 109.8520 89.99148 114.0085
## 277            102 93.51892 110.4811 89.02931 114.9707
## 278            102 92.93335 111.0667 88.13375 115.8662
## 279            102 92.38336 111.6166 87.29262 116.7074
## 280            102 91.86317 112.1368 86.49706 117.5029
## 281            102 91.36841 112.6316 85.74038 118.2596
## 282            102 90.89566 113.1043 85.01738 118.9826
## 283            102 90.44224 113.5578 84.32393 119.6761
## 284            102 90.00595 113.9941 83.65668 120.3433
## 285            102 89.58497 114.4150 83.01286 120.9871

Tabel di atas menunjukkan hasil forecasting untuk 15 hari ke depan

14. Tabel Hasil Forecasting Lengkap

# Buat tabel hasil forecasting
forecast_table <- data.frame(
  Hari_Ke = 1:h_forecast,
  Prediksi = round(as.numeric(forecast_final$mean), 0),
  Batas_Bawah_95 = round(as.numeric(forecast_final$lower[, 2]), 0),
  Batas_Atas_95 = round(as.numeric(forecast_final$upper[, 2]), 0)
)

print(forecast_table)
##    Hari_Ke Prediksi Batas_Bawah_95 Batas_Atas_95
## 1        1      102             97           107
## 2        2      102             95           109
## 3        3      102             94           110
## 4        4      102             92           112
## 5        5      102             91           113
## 6        6      102             90           114
## 7        7      102             89           115
## 8        8      102             88           116
## 9        9      102             87           117
## 10      10      102             86           118
## 11      11      102             86           118
## 12      12      102             85           119
## 13      13      102             84           120
## 14      14      102             84           120
## 15      15      102             83           121

Keterangan:

  • Prediksi: Nilai ramalan titik (point forecast)

  • Batas_Bawah/Atas_95: Interval kepercayaan 95% (lebih luas, lebih konservatif)

15. Visualisasi Hasil Forecasting

par(mfrow = c(2, 1), mar = c(4, 4, 3, 1))

# Plot 1: Forecast dengan base R
plot(forecast_final,
     main = "Forecasting Harga Saham BOLA - 15 Hari Ke Depan",
     xlab = "Periode",
     ylab = "Harga (Rp)",
     lwd = 2)
grid(col = "gray80")

# Plot 2: Forecast dengan autoplot (lebih cantik)
autoplot(forecast_final) +
  ggtitle("Forecasting Harga Saham BOLA - ARIMA(2,1,1)") +
  xlab("Periode") +
  ylab("Harga (Rp)") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
    panel.grid.major = element_line(colour = "gray80"),
    panel.grid.minor = element_line(colour = "gray90")
  )

par(mfrow = c(1, 1))

Interpretasi Forecast: - Garis biru = prediksi titik (point forecast)

  • Area gelap = 95% confidence interval (range paling mungkin)

  • Confidence interval membesar seiring periode yang lebih jauh (ketidakpastian meningkat)

Kesimpulan

Berdasarkan analisis ARIMA yang telah dilakukan:

✓ Model ARIMA(2,1,1) berhasil dibangun dan divalidasi

✓ Akurasi model pada testing data: MAPE = 4.8738392%

✓ Model menunjukkan performa yang baik untuk forecasting jangka pendek

✓ Residual model sudah white noise (model adequately captures pattern)