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:
- [Data Wrangling] Persiapan & Transformasi Data
- [Statistik Deskriptif] Eksplorasi Deret Waktu
- [Uji Normalitas] Pengujian Asumsi Distribusi Log-Return
- [Simulasi] Estimasi Parameter & Monte Carlo
- [Analisis] Interval Kepercayaan & Proyeksi Final
🔗 Tautan Terkait: GitHub Repository | Dashboard Interaktif R Shiny
Sebelum membangun model prediktif, penting untuk memahami karakteristik statistik dari data yang akan dimodelkan. Pada tahap ini terdapat dua objek analisis utama:
\[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.
# 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 HTMLInstalasi: Jalankan
install.packages(c("dplyr", "lubridate", "moments", "knitr", "kableExtra"))jika paket belum tersedia.
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.
## 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
## Tipe data: Date, numeric
# 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.
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)| 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 |
## 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.
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\).
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.
## Jumlah observasi log-return: 1267 (n - 1 dari 1268 )
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")| 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 |
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.
## µ (drift harian) : 0.00012605
## σ (volatilitas harian) : 0.00321561
## µ (drift tahunan) : 0.031764 (3.1764%)
## σ (volatilitas tahunan): 0.051046 (5.1046%)
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)| 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 |
## === 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.
## === 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.
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:
## - params_gbm.rds → parameter µ dan σ untuk GBM
## - df_return.rds → dataframe lengkap dengan kolom Log_Return
## === 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
## µ (drift harian) : 0.00012605
## σ (volatilitas har.) : 0.00321561
## µ (drift tahunan) : 0.031764
## σ (volatilitas thn.) : 0.051046
| 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 |
Dengan parameter \(\mu\) dan \(\sigma\) yang sudah tersimpan, analisis berlanjut ke:
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.9Dokumen ini merupakan bagian dari seri analisis kurs USD/IDR menggunakan pendekatan Geometric Brownian Motion dan Simulasi Monte Carlo.