Tentang Proyek Portofolio Ini

Dokumen ini merupakan bagian dari rangkaian analisis peramalan kurs USD/IDR periode 2021–2025 menggunakan pemodelan stokastik Geometric Brownian Motion (GBM) dan Simulasi Monte Carlo. Proyek ini bertujuan untuk mengestimasi lintasan nilai tukar harian ke depan berdasarkan volatilitas dan drift historis.

Struktur Publikasi:

  1. [Data Wrangling] Persiapan & Transformasi Data
  2. [Statistik Deskriptif] Eksplorasi Deret Waktu
  3. [Uji Normalitas] Pengujian Asumsi Distribusi Log-Return
  4. [Simulasi] Estimasi Parameter & Monte Carlo
  5. [Analisis] Interval Kepercayaan & Proyeksi Final

🔗 Tautan Terkait: GitHub Repository | Dashboard Interaktif R Shiny

1 Latar Belakang

Sebelum membangun model prediktif, penting untuk memahami karakteristik statistik dari data yang akan dimodelkan. Pada tahap ini terdapat dua objek analisis utama:

  1. Kurs penutupan harian (\(S_t\)) — nilai tukar USD/IDR setiap hari perdagangan
  2. Log-return harian (\(r_t\)) — perubahan relatif kurs antar hari yang didefinisikan sebagai:

\[r_t = \ln\left(\frac{S_t}{S_{t-1}}\right)\]

Log-return dipilih karena memiliki sifat aditif secara waktu dan merupakan besaran yang langsung dimodelkan dalam kerangka GBM.


2 Persiapan: Memuat Paket & Data

2.1 Memuat Paket

# Manajemen data
library(dplyr)      # Manipulasi dataframe
library(lubridate)  # Fungsi tanggal

# Statistik lanjutan
library(moments)    # Skewness dan kurtosis

# Tabel output
library(knitr)      # Tabel output yang rapi
library(kableExtra) # Styling tabel HTML

Instalasi: Jalankan install.packages(c("dplyr", "lubridate", "moments", "knitr", "kableExtra")) jika paket belum tersedia.

2.2 Memuat Dataset Bersih

Dataset yang digunakan adalah hasil output dari Bagian 1 (Data Wrangling), yaitu df_usd_idr_clean.rds.

# Muat dataset bersih dari Bagian 1
df_usd_idr <- readRDS("df_usd_idr_clean.rds")

# Konfirmasi data berhasil dimuat
cat("Dataset berhasil dimuat.\n")
## Dataset berhasil dimuat.
cat("Dimensi  :", nrow(df_usd_idr), "baris x", ncol(df_usd_idr), "kolom\n")
## Dimensi  : 1268 baris x 2 kolom
cat("Periode  :", format(min(df_usd_idr$Tanggal), "%d %B %Y"),
    "–", format(max(df_usd_idr$Tanggal), "%d %B %Y"), "\n")
## Periode  : 01 January 2021 – 31 December 2025
cat("Tipe data:", paste(sapply(df_usd_idr, class), collapse = ", "), "\n")
## Tipe data: Date, numeric

3 Tahap 1 — Statistik Deskriptif Kurs Penutupan

3.1 Perhitungan Statistik

# Hitung seluruh statistik deskriptif kurs penutupan
n_obs      <- nrow(df_usd_idr)
kurs_mean  <- mean(df_usd_idr$Kurs)
kurs_med   <- median(df_usd_idr$Kurs)
kurs_sd    <- sd(df_usd_idr$Kurs)
kurs_min   <- min(df_usd_idr$Kurs)
kurs_max   <- max(df_usd_idr$Kurs)
kurs_q1    <- quantile(df_usd_idr$Kurs, 0.25)
kurs_q3    <- quantile(df_usd_idr$Kurs, 0.75)
kurs_iqr   <- IQR(df_usd_idr$Kurs)
kurs_range <- kurs_max - kurs_min
kurs_cv    <- (kurs_sd / kurs_mean) * 100   # Koefisien variasi (%)

cat("Statistik deskriptif kurs penutupan berhasil dihitung.\n")
## Statistik deskriptif kurs penutupan berhasil dihitung.

3.2 Tabel Ringkasan

tabel_kurs <- data.frame(
  Statistik = c(
    "Jumlah Observasi (n)",
    "Rata-rata (Mean)",
    "Median",
    "Standar Deviasi (SD)",
    "Koefisien Variasi (CV)",
    "Nilai Minimum",
    "Kuartil 1 — Q1 (P25)",
    "Kuartil 3 — Q3 (P75)",
    "Rentang Interkuartil (IQR)",
    "Rentang (Max − Min)",
    "Nilai Maksimum"
  ),
  Nilai = c(
    formatC(n_obs,      format = "d",  big.mark = "."),
    formatC(kurs_mean,  format = "f", digits = 2, big.mark = ".", decimal.mark = ","),
    formatC(kurs_med,   format = "f", digits = 2, big.mark = ".", decimal.mark = ","),
    formatC(kurs_sd,    format = "f", digits = 2, big.mark = ".", decimal.mark = ","),
    paste0(formatC(kurs_cv, format = "f", digits = 2, decimal.mark = ","), "%"),
    formatC(kurs_min,   format = "f", digits = 1, big.mark = ".", decimal.mark = ","),
    formatC(kurs_q1,    format = "f", digits = 1, big.mark = ".", decimal.mark = ","),
    formatC(kurs_q3,    format = "f", digits = 1, big.mark = ".", decimal.mark = ","),
    formatC(kurs_iqr,   format = "f", digits = 1, big.mark = ".", decimal.mark = ","),
    formatC(kurs_range, format = "f", digits = 1, big.mark = ".", decimal.mark = ","),
    formatC(kurs_max,   format = "f", digits = 1, big.mark = ".", decimal.mark = ",")
  ),
  Satuan = c(
    "hari perdagangan",
    "IDR", "IDR", "IDR", "—",
    "IDR", "IDR", "IDR", "IDR", "IDR", "IDR"
  )
)

kable(tabel_kurs,
      caption = "Tabel 1.1 — Statistik Deskriptif Kurs Penutupan USD/IDR (2021–2025)",
      align   = c("l", "r", "l")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "bordered"),
                full_width = FALSE) %>%
  row_spec(2, bold = TRUE, color = "#2c7bb6") %>%    # mean
  row_spec(6, bold = TRUE, color = "#c0392b") %>%    # min
  row_spec(11, bold = TRUE, color = "#c0392b") %>%   # max
  pack_rows("Ukuran Pemusatan", 2, 3) %>%
  pack_rows("Ukuran Penyebaran", 4, 10) %>%
  pack_rows("Nilai Ekstrem", 11, 11)
Tabel 1.1 — Statistik Deskriptif Kurs Penutupan USD/IDR (2021–2025)
Statistik Nilai Satuan
Jumlah Observasi (n) 1.268 hari perdagangan
Ukuran Pemusatan
Rata-rata (Mean) 15.358,15 IDR
Median 15.420,00 IDR
Ukuran Penyebaran
Standar Deviasi (SD) 818,79 IDR
Koefisien Variasi (CV) 5,33%
Nilai Minimum 13.880,0 IDR
Kuartil 1 — Q1 (P25) 14.515,0 IDR
Kuartil 3 — Q3 (P75) 16.170,0 IDR
Rentang Interkuartil (IQR) 1.655,0 IDR
Rentang (Max − Min) 2.990,0 IDR
Nilai Ekstrem
Nilai Maksimum 16.870,0 IDR

3.3 Interpretasi

## Selama periode 2021–2025, kurs USD/IDR rata-rata berada di Rp 15.358 per USD,
## dengan median Rp 15.420. Standar deviasi sebesar Rp 819 mencerminkan
## tingkat volatilitas absolut kurs selama periode tersebut.
## Koefisien variasi sebesar 5.33% menunjukkan bahwa fluktuasi kurs
## relatif terhadap rata-ratanya tergolong sedang.

4 Tahap 2 — Perhitungan Log-Return Harian

4.1 Definisi dan Formula

Log-return harian dihitung sebagai selisih logaritmik antara harga penutupan hari ini (\(S_t\)) dan hari sebelumnya (\(S_{t-1}\)):

\[r_t = \ln(S_t) - \ln(S_{t-1}) = \ln\left(\frac{S_t}{S_{t-1}}\right)\]

Karena membutuhkan data hari sebelumnya, baris pertama (\(t=1\)) akan menghasilkan NA dan dibuang, sehingga jumlah observasi log-return adalah \(n - 1\).

4.2 Perhitungan

df_return <- df_usd_idr %>%
  mutate(
    # Log-return: ln(S_t / S_{t-1})
    # lag(Kurs) mengambil nilai Kurs dari baris sebelumnya
    Log_Return = log(Kurs / lag(Kurs))
  ) %>%
  filter(!is.na(Log_Return))   # Buang baris pertama yang NA

cat("Log-return berhasil dihitung.\n")
## Log-return berhasil dihitung.
cat("Jumlah observasi log-return:", nrow(df_return), "(n - 1 dari", nrow(df_usd_idr), ")\n")
## Jumlah observasi log-return: 1267 (n - 1 dari 1268 )

4.3 Preview Data dengan Log-Return

kable(head(df_return, 8),
      caption = "Tabel 2.1 — Data dengan Log-Return Harian (8 Baris Pertama)",
      align   = c("c", "r", "r"),
      digits  = 6,
      col.names = c("Tanggal", "Kurs Penutupan (IDR)", "Log-Return (r_t)")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE) %>%
  column_spec(3, bold = TRUE, color = "#2c7bb6")
Tabel 2.1 — Data dengan Log-Return Harian (8 Baris Pertama)
Tanggal Kurs Penutupan (IDR) Log-Return (r_t)
2021-01-04 13885 -0.023397
2021-01-05 13900 0.001080
2021-01-06 13880 -0.001440
2021-01-07 13890 0.000720
2021-01-08 13980 0.006459
2021-01-11 14080 0.007128
2021-01-12 14120 0.002837
2021-01-13 14055 -0.004614

5 Tahap 3 — Statistik Deskriptif Log-Return

5.1 Perhitungan Statistik Return

rt         <- df_return$Log_Return

# Statistik utama
rt_n       <- length(rt)
rt_mean    <- mean(rt)          # µ — drift harian
rt_sd      <- sd(rt)            # σ — volatilitas harian
rt_median  <- median(rt)
rt_min     <- min(rt)
rt_max     <- max(rt)
rt_skew    <- skewness(rt)      # Kemiringan distribusi
rt_kurt    <- kurtosis(rt)      # Keruncingan distribusi (excess kurtosis)
rt_q1      <- quantile(rt, 0.25)
rt_q3      <- quantile(rt, 0.75)

# Annualized (disetahunkan) — asumsi 252 hari perdagangan per tahun
rt_mean_annual <- rt_mean * 252
rt_sd_annual   <- rt_sd * sqrt(252)

cat("Statistik log-return berhasil dihitung.\n")
## Statistik log-return berhasil dihitung.
cat(sprintf("µ (drift harian)       : %.8f\n", rt_mean))
## µ (drift harian)       : 0.00012605
cat(sprintf("σ (volatilitas harian) : %.8f\n", rt_sd))
## σ (volatilitas harian) : 0.00321561
cat(sprintf("µ (drift tahunan)      : %.6f  (%.4f%%)\n", rt_mean_annual, rt_mean_annual * 100))
## µ (drift tahunan)      : 0.031764  (3.1764%)
cat(sprintf("σ (volatilitas tahunan): %.6f  (%.4f%%)\n", rt_sd_annual,   rt_sd_annual   * 100))
## σ (volatilitas tahunan): 0.051046  (5.1046%)

5.2 Tabel Ringkasan Return

tabel_return <- data.frame(
  Statistik = c(
    "Jumlah Observasi (n)",
    "Mean Return Harian (µ)",
    "Standar Deviasi Harian (σ)",
    "Median Return",
    "Skewness",
    "Kurtosis (excess)",
    "Nilai Minimum",
    "Kuartil 1 — Q1",
    "Kuartil 3 — Q3",
    "Nilai Maksimum",
    "µ Tahunan (× 252)",
    "σ Tahunan (× √252)"
  ),
  Nilai = c(
    formatC(rt_n,            format = "d"),
    formatC(rt_mean,         format = "e", digits = 4),
    formatC(rt_sd,           format = "f", digits = 6),
    formatC(rt_median,       format = "e", digits = 4),
    formatC(rt_skew,         format = "f", digits = 4),
    formatC(rt_kurt,         format = "f", digits = 4),
    formatC(rt_min,          format = "f", digits = 6),
    formatC(rt_q1,           format = "f", digits = 6),
    formatC(rt_q3,           format = "f", digits = 6),
    formatC(rt_max,          format = "f", digits = 6),
    paste0(formatC(rt_mean_annual * 100, format = "f", digits = 4), "%"),
    paste0(formatC(rt_sd_annual   * 100, format = "f", digits = 4), "%")
  ),
  Keterangan = c(
    "Hari perdagangan aktif",
    "Parameter drift GBM",
    "Parameter volatilitas GBM",
    "Titik tengah distribusi",
    "< 0: ekor kiri lebih panjang",
    "> 3: lebih runcing dari normal",
    "Return negatif terbesar",
    "25% data di bawah nilai ini",
    "75% data di bawah nilai ini",
    "Return positif terbesar",
    "Drift disetahunkan",
    "Volatilitas disetahunkan"
  )
)

kable(tabel_return,
      caption = "Tabel 3.1 — Statistik Deskriptif Log-Return Harian USD/IDR",
      align   = c("l", "r", "l")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "bordered"),
                full_width = TRUE) %>%
  row_spec(2, bold = TRUE, background = "#eaf4fb") %>%   # mean / µ
  row_spec(3, bold = TRUE, background = "#eaf4fb") %>%   # sd / σ
  row_spec(5, bold = TRUE, color = "#8e44ad") %>%        # skewness
  row_spec(6, bold = TRUE, color = "#8e44ad") %>%        # kurtosis
  row_spec(11:12, bold = TRUE, background = "#fef9e7") %>%  # annualized
  pack_rows("Statistik Harian", 1, 10) %>%
  pack_rows("Parameter GBM Disetahunkan", 11, 12)
Tabel 3.1 — Statistik Deskriptif Log-Return Harian USD/IDR
Statistik Nilai Keterangan
Statistik Harian
Jumlah Observasi (n) 1267 Hari perdagangan aktif
Mean Return Harian (µ) 1.2605e-04 Parameter drift GBM
Standar Deviasi Harian (σ) 0.003216 Parameter volatilitas GBM
Median Return 0.0000e+00 Titik tengah distribusi
Skewness -0.4344 < 0: ekor kiri lebih panjang
Kurtosis (excess) 7.5156 > 3: lebih runcing dari normal
Nilai Minimum -0.023397 Return negatif terbesar
Kuartil 1 — Q1 -0.001281 25% data di bawah nilai ini
Kuartil 3 — Q3 0.001837 75% data di bawah nilai ini
Nilai Maksimum 0.018256 Return positif terbesar
Parameter GBM Disetahunkan
µ Tahunan (× 252) 3.1764% Drift disetahunkan
σ Tahunan (× √252) 5.1046% Volatilitas disetahunkan

6 Tahap 4 — Interpretasi Statistik Return

6.1 Analisis Skewness dan Kurtosis

## === INTERPRETASI SKEWNESS ===
## Nilai skewness = -0.4344 → distribusi return mendekati simetris (|skewness| < 0,5).
## === INTERPRETASI KURTOSIS ===
## Nilai kurtosis = 7.5156 → distribusi return bersifat leptokurtik (kurtosis = 7.5156 > 3) — distribusi lebih runcing dari normal,
##     artinya return ekstrem (ekor tebal) lebih sering terjadi dari yang diprediksi
##     distribusi normal. Ini adalah ciri khas data keuangan.

6.2 Implikasi terhadap Asumsi GBM

## === IMPLIKASI TERHADAP ASUMSI GBM ===
## GBM mengasumsikan bahwa log-return mengikuti distribusi normal.
## Berdasarkan statistik deskriptif di atas:
##   • Skewness = -0.4344 →
## Asumsi simetri terpenuhi dengan baik.
##   • Kurtosis = 7.5156 →
## Ekor distribusi lebih tebal dari normal (excess kurtosis > 0).
##     Ini umum pada data keuangan dan menjadi catatan penting
##     dalam interpretasi hasil simulasi Monte Carlo.
## 
## Kesimpulan awal ini akan diverifikasi secara formal
## pada Bagian 3 menggunakan uji Kolmogorov-Smirnov.

7 Tahap 5 — Simpan Parameter untuk GBM

Parameter \(\mu\) dan \(\sigma\) yang dihitung di bagian ini akan digunakan langsung pada tahap estimasi GBM dan simulasi Monte Carlo.

# Simpan parameter GBM dan dataset return ke file RDS
params_gbm <- list(
  mu_harian      = rt_mean,
  sigma_harian   = rt_sd,
  mu_tahunan     = rt_mean_annual,
  sigma_tahunan  = rt_sd_annual,
  n_return       = rt_n,
  periode_awal   = min(df_return$Tanggal),
  periode_akhir  = max(df_return$Tanggal),
  kurs_terakhir  = tail(df_usd_idr$Kurs, 1)   # S_0 untuk simulasi
)

# Simpan parameter
saveRDS(params_gbm,  "params_gbm.rds")

# Simpan dataframe return (dipakai di visualisasi & uji normalitas)
saveRDS(df_return,   "df_return.rds")

cat("✓ File yang disimpan:\n")
## ✓ File yang disimpan:
cat("  - params_gbm.rds  → parameter µ dan σ untuk GBM\n")
##   - params_gbm.rds  → parameter µ dan σ untuk GBM
cat("  - df_return.rds   → dataframe lengkap dengan kolom Log_Return\n\n")
##   - df_return.rds   → dataframe lengkap dengan kolom Log_Return
cat("=== RINGKASAN PARAMETER GBM ===\n")
## === RINGKASAN PARAMETER GBM ===
cat(sprintf("  S₀  (kurs terakhir)    : Rp %s\n",
    formatC(params_gbm$kurs_terakhir, format = "f", digits = 1, big.mark = ".")))
##   S₀  (kurs terakhir)    : Rp 16.675.0
cat(sprintf("  µ   (drift harian)     : %.8f\n",  params_gbm$mu_harian))
##   µ   (drift harian)     : 0.00012605
cat(sprintf("  σ   (volatilitas har.) : %.8f\n",  params_gbm$sigma_harian))
##   σ   (volatilitas har.) : 0.00321561
cat(sprintf("  µ   (drift tahunan)    : %.6f\n",  params_gbm$mu_tahunan))
##   µ   (drift tahunan)    : 0.031764
cat(sprintf("  σ   (volatilitas thn.) : %.6f\n",  params_gbm$sigma_tahunan))
##   σ   (volatilitas thn.) : 0.051046

8 Ringkasan Tahap 2

Tabel 5.1 — Ringkasan Aktivitas Bagian 2
Sub-Tahap Aktivitas Output Utama
2.1 Statistik deskriptif kurs penutupan Mean, median, SD, CV, Q1, Q3
2.2 Perhitungan log-return harian Kolom Log_Return pada df_return
2.3 Statistik deskriptif log-return µ, σ, skewness, kurtosis, annualized
2.4 Interpretasi skewness & kurtosis Konfirmasi/catatan asumsi normalitas GBM
2.5 Simpan parameter GBM params_gbm.rds, df_return.rds

9 Langkah Selanjutnya

Dengan parameter \(\mu\) dan \(\sigma\) yang sudah tersimpan, analisis berlanjut ke:

  • Bagian 3 — Visualisasi (R Shiny): Plot time series kurs, histogram distribusi log-return dengan kurva normal overlay, dan QQ-plot.
  • Bagian 4 — Uji Normalitas: Kolmogorov-Smirnov test untuk memvalidasi asumsi GBM secara formal.
  • Bagian 5 — Estimasi Parameter & Simulasi Monte Carlo: Menggunakan \(\mu\) dan \(\sigma\) dari bagian ini untuk mensimulasikan lintasan kurs ke depan.

sessionInfo()
## R version 4.5.1 (2025-06-13 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=English_United States.utf8 
## [2] LC_CTYPE=English_United States.utf8   
## [3] LC_MONETARY=English_United States.utf8
## [4] LC_NUMERIC=C                          
## [5] LC_TIME=English_United States.utf8    
## 
## time zone: Asia/Jakarta
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] kableExtra_1.4.0 knitr_1.51       moments_0.14.1   lubridate_1.9.4 
## [5] dplyr_1.2.0     
## 
## loaded via a namespace (and not attached):
##  [1] jsonlite_2.0.0     compiler_4.5.1     tidyselect_1.2.1   xml2_1.5.2        
##  [5] stringr_1.6.0      jquerylib_0.1.4    textshaping_1.0.5  systemfonts_1.3.2 
##  [9] scales_1.4.0       yaml_2.3.12        fastmap_1.2.0      R6_2.6.1          
## [13] generics_0.1.4     tibble_3.3.0       svglite_2.2.2      bslib_0.10.0      
## [17] pillar_1.11.1      RColorBrewer_1.1-3 rlang_1.1.7        cachem_1.1.0      
## [21] stringi_1.8.7      xfun_0.56          sass_0.4.10        otel_0.2.0        
## [25] viridisLite_0.4.2  timechange_0.3.0   cli_3.6.5          magrittr_2.0.4    
## [29] digest_0.6.38      rstudioapi_0.18.0  lifecycle_1.0.5    vctrs_0.7.1       
## [33] evaluate_1.0.5     glue_1.8.0         farver_2.1.2       rmarkdown_2.30    
## [37] tools_4.5.1        pkgconfig_2.0.3    htmltools_0.5.9

Dokumen ini merupakan bagian dari seri analisis kurs USD/IDR menggunakan pendekatan Geometric Brownian Motion dan Simulasi Monte Carlo.