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—
Model GBM mengasumsikan bahwa log-return harian (\(r_t\)) berdistribusi normal:
\[r_t = \ln\left(\frac{S_t}{S_{t-1}}\right) \sim \mathcal{N}(\mu,\ \sigma^2)\]
Apabila asumsi ini tidak terpenuhi, hasil simulasi Monte Carlo perlu diinterpretasikan dengan kehati-hatian. Validasi dilakukan melalui dua pendekatan:
# Manajemen data
library(dplyr)
# Uji statistik
library(tseries) # Uji Jarque-Bera
# Tabel output
library(knitr)
library(kableExtra)
#visualisasi
library(ggplot2)
library(scales)Instalasi: Jalankan
install.packages(c("dplyr", "tseries", "knitr", "kableExtra"))jika paket belum tersedia.
# Baca langsung dari CSV — tidak bergantung pada file RDS
df_raw <- read.csv(
"../data/Data Historis USD_IDR.csv",
header = TRUE,
stringsAsFactors = FALSE,
fileEncoding = "UTF-8-BOM"
)
# Wrangling ulang (ringkas)
df_usd_idr <- df_raw %>%
select(Tanggal, Terakhir) %>%
mutate(
Kurs = as.numeric(gsub(",", ".", gsub("\\.", "", Terakhir))),
Tanggal = as.Date(Tanggal, format = "%d/%m/%Y")
) %>%
select(Tanggal, Kurs) %>%
arrange(Tanggal)
# Hitung log-return
df_return <- df_usd_idr %>%
mutate(Log_Return = log(Kurs / lag(Kurs))) %>%
filter(!is.na(Log_Return))
rt <- df_return$Log_Return
cat("Data berhasil dimuat dari CSV.\n")## Data berhasil dimuat dari CSV.
## Jumlah observasi : 1267
cat("Periode :", format(min(df_return$Tanggal), "%d %B %Y"),
"–", format(max(df_return$Tanggal), "%d %B %Y"), "\n")## Periode : 04 January 2021 – 31 December 2025
## Mean return (µ) : 0.00012605
## Std dev (σ) : 0.00321561
Uji KS membandingkan fungsi distribusi kumulatif empiris (ECDF) dari data sampel dengan CDF distribusi normal teoretis. Hipotesis yang diuji:
Keputusan diambil pada tingkat signifikansi \(\alpha = 0{,}05\): - Jika \(p\text{-value} \geq 0{,}05\) → Gagal tolak \(H_0\) (distribusi normal) - Jika \(p\text{-value} < 0{,}05\) → Tolak \(H_0\) (tidak normal)
# Uji KS: bandingkan data dengan distribusi normal N(µ, σ²)
ks_result <- ks.test(
x = rt,
y = "pnorm",
mean = mean(rt),
sd = sd(rt)
)
# Tampilkan output mentah
print(ks_result)##
## Asymptotic one-sample Kolmogorov-Smirnov test
##
## data: rt
## D = 0.088672, p-value = 4.448e-09
## alternative hypothesis: two-sided
ks_table <- data.frame(
Komponen = c(
"Metode Uji",
"Jumlah Observasi (n)",
"Statistik D",
"p-value",
"Tingkat Signifikansi (α)",
"Keputusan",
"Kesimpulan"
),
Hasil = c(
"Kolmogorov-Smirnov One-Sample Test",
formatC(length(rt), format = "d"),
formatC(ks_result$statistic, format = "f", digits = 6),
ifelse(ks_result$p.value < 0.001,
"< 0,001",
formatC(ks_result$p.value, format = "f", digits = 6)),
"0,05",
ifelse(ks_result$p.value < 0.05, "Tolak H₀", "Gagal Tolak H₀"),
ifelse(ks_result$p.value < 0.05,
"Log-return TIDAK berdistribusi normal",
"Log-return berdistribusi normal")
)
)
kable(ks_table,
caption = "Tabel 1.1 — Hasil Uji Kolmogorov-Smirnov",
align = c("l", "l"),
col.names = c("Komponen", "Hasil")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "bordered"),
full_width = FALSE) %>%
row_spec(6, bold = TRUE,
color = ifelse(ks_result$p.value < 0.05, "white", "white"),
background = ifelse(ks_result$p.value < 0.05, "#c0392b", "#27ae60")) %>%
row_spec(7, bold = TRUE, color = "#2c3e50",
background = ifelse(ks_result$p.value < 0.05, "#fdecea", "#eafaf1"))| Komponen | Hasil |
|---|---|
| Metode Uji | Kolmogorov-Smirnov One-Sample Test |
| Jumlah Observasi (n) | 1267 |
| Statistik D | 0.088672 |
| p-value | < 0,001 |
| Tingkat Signifikansi (α) | 0,05 |
| Keputusan | Tolak H₀ |
| Kesimpulan | Log-return TIDAK berdistribusi normal |
Uji Jarque-Bera (JB) menguji normalitas berdasarkan skewness dan kurtosis secara bersamaan menggunakan statistik:
\[JB = \frac{n}{6}\left(S^2 + \frac{(K-3)^2}{4}\right)\]
di mana \(S\) adalah skewness dan \(K\) adalah kurtosis sampel. Di bawah \(H_0\), statistik JB mengikuti distribusi \(\chi^2\) dengan 2 derajat kebebasan.
# Uji Jarque-Bera menggunakan paket tseries
jb_result <- jarque.bera.test(rt)
# Tampilkan output mentah
print(jb_result)##
## Jarque Bera Test
##
## data: rt
## X-squared = 1116.3, df = 2, p-value < 2.2e-16
# Uji KS: bandingkan data dengan distribusi normal N(µ, σ²)
ks_result <- ks.test(
x = rt,
y = "pnorm",
mean = mean(rt),
sd = sd(rt)
)
# Tampilkan output mentah
print(ks_result)##
## Asymptotic one-sample Kolmogorov-Smirnov test
##
## data: rt
## D = 0.088672, p-value = 4.448e-09
## alternative hypothesis: two-sided
ks_table <- data.frame(
Komponen = c(
"Metode Uji",
"Jumlah Observasi (n)",
"Statistik D",
"p-value",
"Tingkat Signifikansi (α)",
"Keputusan",
"Kesimpulan"
),
Hasil = c(
"Kolmogorov-Smirnov One-Sample Test",
formatC(length(rt), format = "d"),
formatC(ks_result$statistic, format = "f", digits = 6),
ifelse(ks_result$p.value < 0.001,
"< 0,001",
formatC(ks_result$p.value, format = "f", digits = 6)),
"0,05",
ifelse(ks_result$p.value < 0.05, "Tolak H₀", "Gagal Tolak H₀"),
ifelse(ks_result$p.value < 0.05,
"Log-return TIDAK berdistribusi normal",
"Log-return berdistribusi normal")
)
)
kable(ks_table,
caption = "Tabel 1.1 — Hasil Uji Kolmogorov-Smirnov",
align = c("l", "l"),
col.names = c("Komponen", "Hasil")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "bordered"),
full_width = FALSE) %>%
row_spec(6, bold = TRUE,
color = "white",
background = ifelse(ks_result$p.value < 0.05, "#c0392b", "#27ae60")) %>%
row_spec(7, bold = TRUE, color = "#2c3e50",
background = ifelse(ks_result$p.value < 0.05, "#fdecea", "#eafaf1"))| Komponen | Hasil |
|---|---|
| Metode Uji | Kolmogorov-Smirnov One-Sample Test |
| Jumlah Observasi (n) | 1267 |
| Statistik D | 0.088672 |
| p-value | < 0,001 |
| Tingkat Signifikansi (α) | 0,05 |
| Keputusan | Tolak H₀ |
| Kesimpulan | Log-return TIDAK berdistribusi normal |
# Uji Jarque-Bera menggunakan paket tseries
jb_result <- jarque.bera.test(rt)
# Tampilkan output mentah
print(jb_result)##
## Jarque Bera Test
##
## data: rt
## X-squared = 1116.3, df = 2, p-value < 2.2e-16
# Hitung skewness dan kurtosis manual untuk ditampilkan
n <- length(rt)
mu <- mean(rt)
sig <- sd(rt)
skew <- (n / ((n-1)*(n-2))) * sum(((rt - mu) / sig)^3)
kurt <- (n*(n+1)/((n-1)*(n-2)*(n-3))) *
sum(((rt - mu)/sig)^4) -
3*(n-1)^2/((n-2)*(n-3))
jb_table <- data.frame(
Komponen = c(
"Metode Uji",
"Jumlah Observasi (n)",
"Skewness (S)",
"Excess Kurtosis (K−3)",
"Statistik JB",
"Derajat Kebebasan",
"p-value",
"Tingkat Signifikansi (α)",
"Keputusan",
"Kesimpulan"
),
Hasil = c(
"Jarque-Bera Test",
formatC(n, format = "d"),
formatC(skew, format = "f", digits = 6),
formatC(kurt, format = "f", digits = 6),
formatC(jb_result$statistic, format = "f", digits = 4),
"2",
ifelse(jb_result$p.value < 0.001,
"< 0,001",
formatC(jb_result$p.value, format = "f", digits = 6)),
"0,05",
ifelse(jb_result$p.value < 0.05, "Tolak H₀", "Gagal Tolak H₀"),
ifelse(jb_result$p.value < 0.05,
"Log-return TIDAK berdistribusi normal",
"Log-return berdistribusi normal")
)
)
kable(jb_table,
caption = "Tabel 2.1 — Hasil Uji Jarque-Bera",
align = c("l", "l"),
col.names = c("Komponen", "Hasil")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "bordered"),
full_width = FALSE) %>%
row_spec(9, bold = TRUE,
color = "white",
background = ifelse(jb_result$p.value < 0.05, "#c0392b", "#27ae60")) %>%
row_spec(10, bold = TRUE, color = "#2c3e50",
background = ifelse(jb_result$p.value < 0.05, "#fdecea", "#eafaf1")) %>%
pack_rows("Input Statistik", 3, 5) %>%
pack_rows("Hasil Uji", 6, 10)| Komponen | Hasil |
|---|---|
| Metode Uji | Jarque-Bera Test |
| Jumlah Observasi (n) | 1267 |
| Input Statistik | |
| Skewness (S) | -0.434896 |
| Excess Kurtosis (K−3) | 4.538207 |
| Statistik JB | 1116.2937 |
| Hasil Uji | |
| Derajat Kebebasan | 2 |
| p-value | < 0,001 |
| Tingkat Signifikansi (α) | 0,05 |
| Keputusan | Tolak H₀ |
| Kesimpulan | Log-return TIDAK berdistribusi normal |
Untuk melengkapi pengujian formal di atas, bagian ini menyajikan evaluasi visual menggunakan histogram (dengan overlay kurva teoretis) dan QQ-Plot.
library(ggplot2)
df_plot_rt <- data.frame(Log_Return = rt)
plot_hist <- ggplot(df_plot_rt, aes(x = Log_Return)) +
geom_histogram(aes(y = ..density..), bins = 50,
fill = "#34495e", colour = "white", alpha = 0.8) +
geom_density(colour = "#e74c3c", linewidth = 1) +
stat_function(
fun = dnorm,
args = list(mean = mean(rt), sd = sd(rt)),
colour = "#2ecc71", linewidth = 1, linetype = "dashed"
) +
labs(
title = "Histogram Log-Return USD/IDR vs Kurva Normal Teoretis",
subtitle = "Garis Merah = Kepadatan Empiris | Garis Hijau Putus-putus = Distribusi Normal Sempurna",
x = "Log-Return Harian",
y = "Density"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_text(face = "bold", colour = "#1a252f"),
panel.grid.major = element_line(colour = "#dce3ea", linewidth = 0.4)
)
# Tampilkan di dokumen HTML
plot_histplot_qq <- ggplot(df_plot_rt, aes(sample = Log_Return)) +
stat_qq(colour = "#3498db", alpha = 0.6, size = 1.5) +
stat_qq_line(colour = "#e74c3c", linewidth = 0.8, linetype = "dashed") +
labs(
title = "QQ-Plot Log-Return USD/IDR",
subtitle = "Penyimpangan di ujung garis mengonfirmasi fenomena Fat Tails (Ekor Tebbal)",
x = "Kuantil Teoretis (Normal)",
y = "Kuantil Sampel (Empiris)"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_text(face = "bold", colour = "#1a252f"),
panel.grid.major = element_line(colour = "#dce3ea", linewidth = 0.4)
)
# Tampilkan di dokumen HTML
plot_qqrecap <- data.frame(
Uji = c("Kolmogorov-Smirnov", "Jarque-Bera"),
Statistik = c(
formatC(ks_result$statistic, format = "f", digits = 6),
formatC(jb_result$statistic, format = "f", digits = 4)
),
p_value = c(
ifelse(ks_result$p.value < 0.001, "< 0,001",
formatC(ks_result$p.value, format = "f", digits = 6)),
ifelse(jb_result$p.value < 0.001, "< 0,001",
formatC(jb_result$p.value, format = "f", digits = 6))
),
Keputusan = c(
ifelse(ks_result$p.value < 0.05, "Tolak H₀", "Gagal Tolak H₀"),
ifelse(jb_result$p.value < 0.05, "Tolak H₀", "Gagal Tolak H₀")
),
Keterangan = c(
"Berbasis jarak ECDF vs CDF normal",
"Berbasis skewness & kurtosis simultan"
)
)
kable(recap,
caption = "Tabel 3.1 — Rekapitulasi Hasil Uji Normalitas",
align = c("l", "r", "r", "c", "l"),
col.names = c("Uji", "Statistik", "p-value", "Keputusan", "Keterangan")) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "bordered"),
full_width = TRUE) %>%
column_spec(4, bold = TRUE,
color = "white",
background = ifelse(
c(ks_result$p.value, jb_result$p.value) < 0.05,
"#c0392b", "#27ae60"
))| Uji | Statistik | p-value | Keputusan | Keterangan | |
|---|---|---|---|---|---|
| D | Kolmogorov-Smirnov | 0.088672 | < 0,001 | Tolak H₀ | Berbasis jarak ECDF vs CDF normal |
| X-squared | Jarque-Bera | 1116.2937 | < 0,001 | Tolak H₀ | Berbasis skewness & kurtosis simultan |
## === INTERPRETASI HASIL UJI NORMALITAS ===
## Uji Kolmogorov-Smirnov : TOLAK H₀ (D = 0.088672, p < 0,001)
## Uji Jarque-Bera : TOLAK H₀ (JB = 1116.2937, p < 0,001)
## Kedua uji secara konsisten menolak hipotesis nol pada α = 0,05.
## Artinya, log-return USD/IDR secara formal TIDAK berdistribusi normal sempurna.
##
## Penyebab utama penolakan:
## • Skewness = -0.4349 → distribusi tidak simetris sempurna
## • Excess kurtosis = 4.5382 → ekor distribusi lebih tebal dari normal
## (fenomena 'fat tails' yang umum pada data keuangan)
## === IMPLIKASI TERHADAP MODEL GBM ===
## Meskipun uji formal menolak normalitas sempurna, GBM tetap
## dapat digunakan dengan pertimbangan berikut:
## 1. JUSTIFIKASI PENGGUNAAN GBM:
## • GBM adalah model standar industri untuk pemodelan harga aset keuangan
## (Black-Scholes, 1973) dan tetap digunakan secara luas meskipun
## normalitas tidak terpenuhi secara sempurna.
## • Penyimpangan terjadi terutama di ekor distribusi (return ekstrem),
## sementara bagian tengah (return normal sehari-hari) mendekati normal.
## • Central Limit Theorem mendukung pendekatan normal untuk return
## agregat dalam jangka panjang.
## 2. CATATAN PENTING DALAM INTERPRETASI:
## • Interval kepercayaan dari simulasi Monte Carlo mungkin UNDERESTIMATE
## risiko ekstrem (krisis, shock mendadak) karena ekor lebih tebal.
## • Hasil simulasi lebih valid untuk kondisi normal (bukan krisis).
## • Semakin panjang horizon simulasi, asumsi normalitas semakin kritis.
## 3. KEPUTUSAN:
## ✓ GBM LAYAK digunakan untuk simulasi Monte Carlo kurs USD/IDR
## dengan catatan interpretasi di atas.
# Simpan hasil uji normalitas untuk referensi laporan
hasil_normalitas <- list(
ks_statistic = as.numeric(ks_result$statistic),
ks_pvalue = ks_result$p.value,
ks_keputusan = ifelse(ks_result$p.value < 0.05, "Tolak H0", "Gagal Tolak H0"),
jb_statistic = as.numeric(jb_result$statistic),
jb_pvalue = jb_result$p.value,
jb_keputusan = ifelse(jb_result$p.value < 0.05, "Tolak H0", "Gagal Tolak H0"),
skewness = skew,
excess_kurt = kurt,
gbm_layak = TRUE, # Keputusan akhir penggunaan GBM
catatan = "GBM layak digunakan dengan catatan fat tails"
)
saveRDS(hasil_normalitas, "hasil_normalitas.rds")
cat("✓ Hasil uji disimpan ke: hasil_normalitas.rds\n\n")## ✓ Hasil uji disimpan ke: hasil_normalitas.rds
## === RINGKASAN UNTUK LAPORAN ===
cat(sprintf("KS Test : D = %.6f, p %s → %s\n",
hasil_normalitas$ks_statistic,
ifelse(hasil_normalitas$ks_pvalue < 0.001, "< 0,001",
round(hasil_normalitas$ks_pvalue, 6)),
hasil_normalitas$ks_keputusan))## KS Test : D = 0.088672, p < 0,001 → Tolak H0
cat(sprintf("JB Test : JB = %.4f, p %s → %s\n",
hasil_normalitas$jb_statistic,
ifelse(hasil_normalitas$jb_pvalue < 0.001, "< 0,001",
round(hasil_normalitas$jb_pvalue, 6)),
hasil_normalitas$jb_keputusan))## JB Test : JB = 1116.2937, p < 0,001 → Tolak H0
## Skewness : -0.4349
## Ex. Kurt : 4.5382
## GBM Layak: TRUE
| Sub-Tahap | Aktivitas | Output Utama |
|---|---|---|
| 4.1 | Uji Kolmogorov-Smirnov | Statistik D, p-value, keputusan H₀ |
| 4.2 | Uji Jarque-Bera | Statistik JB, p-value, keputusan H₀ |
| 4.3 | Rekapitulasi kedua uji | Tabel perbandingan kedua uji |
| 4.4 | Interpretasi & implikasi GBM | Justifikasi kelayakan GBM |
| 4.5 | Simpan hasil uji | hasil_normalitas.rds |
Dengan validasi normalitas yang sudah dilakukan, analisis berlanjut ke tahap inti:
\[S_{t+\Delta t} = S_t \cdot \exp\left[\left(\mu - \frac{\sigma^2}{2}\right)\Delta t + \sigma\sqrt{\Delta t}\,Z_t\right], \quad Z_t \sim \mathcal{N}(0,1)\]
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] scales_1.4.0 ggplot2_4.0.2 kableExtra_1.4.0 knitr_1.51
## [5] tseries_0.10-61 dplyr_1.2.0
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.10 generics_0.1.4 xml2_1.5.2 stringi_1.8.7
## [5] lattice_0.22-7 digest_0.6.38 magrittr_2.0.4 evaluate_1.0.5
## [9] grid_4.5.1 RColorBrewer_1.1-3 fastmap_1.2.0 jsonlite_2.0.0
## [13] viridisLite_0.4.2 textshaping_1.0.5 jquerylib_0.1.4 cli_3.6.5
## [17] rlang_1.1.7 withr_3.0.2 cachem_1.1.0 yaml_2.3.12
## [21] otel_0.2.0 tools_4.5.1 curl_7.0.0 vctrs_0.7.1
## [25] R6_2.6.1 zoo_1.8-15 lifecycle_1.0.5 stringr_1.6.0
## [29] ragg_1.5.1 pkgconfig_2.0.3 pillar_1.11.1 bslib_0.10.0
## [33] gtable_0.3.6 glue_1.8.0 quantmod_0.4.28 systemfonts_1.3.2
## [37] xfun_0.56 tibble_3.3.0 tidyselect_1.2.1 rstudioapi_0.18.0
## [41] farver_2.1.2 htmltools_0.5.9 labeling_0.4.3 rmarkdown_2.30
## [45] xts_0.14.1 svglite_2.2.2 compiler_4.5.1 quadprog_1.5-8
## [49] S7_0.2.1 TTR_0.24.4Dokumen ini merupakan bagian dari seri analisis kurs USD/IDR menggunakan pendekatan Geometric Brownian Motion dan Simulasi Monte Carlo.