Analisis Return Portofolio Data Saham Sektor Pertambangan

A. Setup

# Library

library(quantmod)
library(PerformanceAnalytics)
library(tseries)
library(moments)
library(xts)
library(NMOF)
options(stringsAsFactors = FALSE)
 knitr::opts_chunk$set(message = FALSE, warning = FALSE)

B. Data Saham

2.1. Import Data Saham

saham <- c("BUMI.JK","ADRO.JK","PTBA.JK","ITMG.JK")
start <- as.Date("2022-01-01")
end   <- as.Date("2026-01-31")

getSymbols(saham, src="yahoo", from=start, to=end)
## [1] "BUMI.JK" "ADRO.JK" "PTBA.JK" "ITMG.JK"
getSymbols("^JKSE", src="yahoo", from=start, to=end)
## [1] "JKSE"

2.2. Data Harian, Mingguan, Bulanan

price_daily <- merge(Cl(BUMI.JK),Cl(ADRO.JK),Cl(PTBA.JK),Cl(ITMG.JK))
colnames(price_daily) <- c("BUMI","ADRO","PTBA","ITMG")

price_weekly <- merge(
  Cl(to.weekly(BUMI.JK)),
  Cl(to.weekly(ADRO.JK)),
  Cl(to.weekly(PTBA.JK)),
  Cl(to.weekly(ITMG.JK))
)

price_monthly <- merge(
  Cl(to.monthly(BUMI.JK)),
  Cl(to.monthly(ADRO.JK)),
  Cl(to.monthly(PTBA.JK)),
  Cl(to.monthly(ITMG.JK))
)

colnames(price_weekly) <- colnames(price_monthly) <- c("BUMI","ADRO","PTBA","ITMG")

IHSG_daily <- Cl(JKSE)
IHSG_weekly <- Cl(to.weekly(JKSE))
IHSG_monthly <- Cl(to.monthly(JKSE))

C. Exploratory Data Analysis Data Harga Saham

Variabel Data Saham

colnames(BUMI.JK)
## [1] "BUMI.JK.Open"     "BUMI.JK.High"     "BUMI.JK.Low"      "BUMI.JK.Close"   
## [5] "BUMI.JK.Volume"   "BUMI.JK.Adjusted"
colnames(ADRO.JK)
## [1] "ADRO.JK.Open"     "ADRO.JK.High"     "ADRO.JK.Low"      "ADRO.JK.Close"   
## [5] "ADRO.JK.Volume"   "ADRO.JK.Adjusted"
colnames(PTBA.JK)
## [1] "PTBA.JK.Open"     "PTBA.JK.High"     "PTBA.JK.Low"      "PTBA.JK.Close"   
## [5] "PTBA.JK.Volume"   "PTBA.JK.Adjusted"
colnames(ITMG.JK)
## [1] "ITMG.JK.Open"     "ITMG.JK.High"     "ITMG.JK.Low"      "ITMG.JK.Close"   
## [5] "ITMG.JK.Volume"   "ITMG.JK.Adjusted"

3.1. Statistik Deskriptif

# Harian
stat_daily <- data.frame(
  Mean = apply(price_daily,2,mean),
  Median = apply(price_daily,2,median),
  SD = apply(price_daily,2,sd),
  Skew = apply(price_daily,2,skewness),
  Kurt = apply(price_daily,2,kurtosis)
)

# Mingguan
stat_weekly <- data.frame(
  Mean = apply(price_weekly,2,mean),
  Median = apply(price_weekly,2,median),
  SD = apply(price_weekly,2,sd),
  Skew = apply(price_weekly,2,skewness),
  Kurt = apply(price_weekly,2,kurtosis)
)

# Bulanan
stat_monthly <- data.frame(
  Mean = apply(price_monthly,2,mean),
  Median = apply(price_monthly,2,median),
  SD = apply(price_monthly,2,sd),
  Skew = apply(price_monthly,2,skewness),
  Kurt = apply(price_monthly,2,kurtosis)
)

stat_daily
##            Mean Median         SD      Skew      Kurt
## BUMI   124.9499    115   60.64147 2.7599721 13.362920
## ADRO  2704.1769   2670  650.69454 0.3364373  2.239059
## PTBA  3016.3599   2800  600.01368 0.7818295  2.381942
## ITMG 28159.3303  26200 6220.98458 1.1184813  3.124306
stat_weekly
##            Mean  Median         SD      Skew      Kurt
## BUMI   124.6571   114.5   61.95721 2.8181845 13.303760
## ADRO  2688.1667  2650.0  647.38735 0.3613671  2.265259
## PTBA  3016.6667  2795.0  602.69120 0.8150152  2.453663
## ITMG 28055.5952 26237.5 6111.27783 1.1507279  3.246369
stat_monthly
##            Mean Median         SD      Skew     Kurt
## BUMI   122.7959    115   55.19435 2.1714503 9.630716
## ADRO  2668.0612   2620  660.52364 0.4269606 2.184748
## PTBA  3021.6327   2770  636.98622 0.8759115 2.444875
## ITMG 28167.8571  26475 6366.39738 1.1336184 3.097009

3.2. Plot Harga Saham

# Harga Harian
plot.zoo(price_daily,col=1:4,lwd=2,screens = 1,main="Plot Harga Saham Harian",xlab="Waktu",ylab="Harga")
legend("topleft",legend=colnames(price_daily),col=1:4,lty=1,lwd=2,cex=0.8,bty="o")

# Harga Mingguan
plot.zoo(price_weekly,col=1:4,lwd=2,screens = 1,main="Plot Harga Saham Mingguan",xlab="Waktu",ylab="Harga")
legend("topleft",legend=colnames(price_weekly),col=1:4,lty=1,lwd=2,cex=0.8,bty="o")

# Harga Bulanan
plot.zoo(price_monthly,col=1:4,lwd=2,screens = 1,main="Plot Harga Saham Bulanan",xlab="Waktu",ylab="Harga")
legend("topleft",legend=colnames(price_monthly),col=1:4,lty=1,lwd=2,cex=0.8,bty="o")

3.3. Uji Normalitas

# Harian
lapply(as.data.frame(price_daily), jarque.bera.test)
## $BUMI
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 5617.8, df = 2, p-value < 2.2e-16
## 
## 
## $ADRO
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 42.046, df = 2, p-value = 7.412e-10
## 
## 
## $PTBA
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 115.2, df = 2, p-value < 2.2e-16
## 
## 
## $ITMG
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 204.54, df = 2, p-value < 2.2e-16
# Mingguan
lapply(as.data.frame(price_weekly), jarque.bera.test)
## $BUMI
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 1206.9, df = 2, p-value < 2.2e-16
## 
## 
## $ADRO
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 9.2942, df = 2, p-value = 0.00959
## 
## 
## $PTBA
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 25.86, df = 2, p-value = 2.424e-06
## 
## 
## $ITMG
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 46.877, df = 2, p-value = 6.618e-11
# Bulanan
lapply(as.data.frame(price_monthly), jarque.bera.test)
## $BUMI
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 128.27, df = 2, p-value < 2.2e-16
## 
## 
## $ADRO
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 2.8457, df = 2, p-value = 0.241
## 
## 
## $PTBA
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 6.8948, df = 2, p-value = 0.03183
## 
## 
## $ITMG
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 10.514, df = 2, p-value = 0.005211

Interpretasi: Mayoritas harga saham tidak berdistribusi normal (p-value < 0,05), yang menunjukkan adanya skewness dan kurtosis cukup tinggi pada data harga saham.

D. Exploratory Data Analysis Return Saham

4.1. Perhitungan Return

ret_daily <- na.omit(diff(log(price_daily)))
ret_weekly <- na.omit(diff(log(price_weekly)))
ret_monthly <- na.omit(diff(log(price_monthly)))

ret_IHSG_daily <- na.omit(diff(log(IHSG_daily)))
ret_IHSG_weekly <- na.omit(diff(log(IHSG_weekly)))
ret_IHSG_monthly <- na.omit(diff(log(IHSG_monthly)))

4.2. Statistik Deskriptif Return

# Harian
stat_ret_daily <- data.frame(
  Mean = apply(ret_daily,2,mean),
  Median = apply(ret_daily,2,median),
  SD = apply(ret_daily,2,sd),
  Skew = apply(ret_daily,2,skewness),
  Kurt = apply(ret_daily,2,kurtosis)
)

# Mingguan
stat_ret_weekly <- data.frame(
  Mean = apply(ret_weekly,2,mean),
  Median = apply(ret_weekly,2,median),
  SD = apply(ret_weekly,2,sd),
  Skew = apply(ret_weekly,2,skewness),
  Kurt = apply(ret_weekly,2,kurtosis)
)

# Bulanan
stat_ret_monthly <- data.frame(
  Mean = apply(ret_monthly,2,mean),
  Median = apply(ret_monthly,2,median),
  SD = apply(ret_monthly,2,sd),
  Skew = apply(ret_monthly,2,skewness),
  Kurt = apply(ret_monthly,2,kurtosis)
)

stat_ret_daily
##               Mean Median         SD        Skew      Kurt
## BUMI  1.395399e-03      0 0.04079782  1.11501853  8.146396
## ADRO -7.154293e-05      0 0.02906341 -1.46793098 23.155825
## PTBA -7.555774e-05      0 0.02131433 -1.31702579 15.323541
## ITMG  1.145986e-04      0 0.01997331 -0.04499322  7.616235
stat_ret_weekly
##               Mean      Median         SD       Skew      Kurt
## BUMI  0.0063801525 0.000000000 0.09434855  0.8303784  8.132777
## ADRO -0.0004540610 0.000000000 0.06777162 -3.1058165 29.509530
## PTBA -0.0005635552 0.000000000 0.05179232 -1.7474730 11.698095
## ITMG  0.0004391658 0.002305477 0.04425017 -0.5875460  5.550950
stat_ret_monthly
##               Mean       Median         SD       Skew     Kurt
## BUMI  0.0254630468 -0.014197433 0.20323286  0.6506388 3.247368
## ADRO -0.0002809031  0.009226184 0.14239860 -1.7860744 7.450743
## PTBA -0.0028970924 -0.009997421 0.09343629 -0.6936896 4.126560
## ITMG  0.0002867018 -0.001770337 0.10218631 -0.7958272 7.557619

4.3. Plot Return

# Return Harian
plot.zoo(ret_daily,col=1:4,lwd=2,screens = 1,main="Plot Return Saham Harian",xlab="Waktu",ylab="Return")
abline(h=0, col="black", lty=2)
legend("topleft",legend=colnames(ret_daily),col=1:4,lty=1,lwd=2,cex=0.8,bty="o")

# Return Mingguan
plot.zoo(ret_weekly,col=1:4,lwd=2,screens = 1,main="Plot Return Saham Mingguan",xlab="Waktu",ylab="Return")
abline(h=0, col="black", lty=2)
legend("topleft",legend=colnames(ret_weekly),col=1:4,lty=1,lwd=2,cex=0.8,bty="o")

# Return Bulanan
plot.zoo(ret_monthly,col=1:4,lwd=2,screens = 1,main="Plot Return Saham Bulanan",xlab="Waktu",ylab="Return")
abline(h=0, col="black", lty=2)
legend("topleft",legend=colnames(ret_monthly),col=1:4,lty=1,lwd=2,cex=0.8,bty="o")

4.4. Uji Normalitas Return

lapply(as.data.frame(ret_daily), jarque.bera.test)
## $BUMI
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 1280.6, df = 2, p-value < 2.2e-16
## 
## 
## $ADRO
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 16889, df = 2, p-value < 2.2e-16
## 
## 
## $PTBA
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 6464.8, df = 2, p-value < 2.2e-16
## 
## 
## $ITMG
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 867.81, df = 2, p-value < 2.2e-16
lapply(as.data.frame(ret_weekly), jarque.bera.test)
## $BUMI
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 253.44, df = 2, p-value < 2.2e-16
## 
## 
## $ADRO
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 6455.8, df = 2, p-value < 2.2e-16
## 
## 
## $PTBA
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 765.21, df = 2, p-value < 2.2e-16
## 
## 
## $ITMG
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 68.693, df = 2, p-value = 1.221e-15
lapply(as.data.frame(ret_monthly), jarque.bera.test)
## $BUMI
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 3.509, df = 2, p-value = 0.173
## 
## 
## $ADRO
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 65.139, df = 2, p-value = 7.216e-15
## 
## 
## $PTBA
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 6.3879, df = 2, p-value = 0.04101
## 
## 
## $ITMG
## 
##  Jarque Bera Test
## 
## data:  X[[i]]
## X-squared = 46.611, df = 2, p-value = 7.562e-11

Interpretasi: Beberapa return saham tidak berdistribusi normal (pvalue < 0.05), yang menunjukkan adanya risiko ekstrem yang lebih besar dibanding distribusi normal.

E. Analisis Korelasi

5.1. Korelasi Antar Harga Saham

# Harian
cor(price_daily)
##             BUMI        ADRO        PTBA      ITMG
## BUMI  1.00000000 -0.03982633 -0.08859637 0.0850005
## ADRO -0.03982633  1.00000000  0.66990407 0.7379559
## PTBA -0.08859637  0.66990407  1.00000000 0.8435244
## ITMG  0.08500050  0.73795587  0.84352445 1.0000000
# Mingguan
cor(price_weekly)
##             BUMI        ADRO       PTBA      ITMG
## BUMI  1.00000000 -0.07071611 -0.1155737 0.0547528
## ADRO -0.07071611  1.00000000  0.6622358 0.7342504
## PTBA -0.11557373  0.66223583  1.0000000 0.8374002
## ITMG  0.05475280  0.73425041  0.8374002 1.0000000
# Bulanan
cor(price_monthly)
##             BUMI        ADRO       PTBA       ITMG
## BUMI  1.00000000 -0.07934218 -0.1081567 0.07783415
## ADRO -0.07934218  1.00000000  0.7164461 0.77553887
## PTBA -0.10815668  0.71644612  1.0000000 0.85698816
## ITMG  0.07783415  0.77553887  0.8569882 1.00000000

5.2. Korelasi Antar Return Saham

# Harian
cor(ret_daily)
##           BUMI      ADRO      PTBA      ITMG
## BUMI 1.0000000 0.2147141 0.2338302 0.2439917
## ADRO 0.2147141 1.0000000 0.4450246 0.4657511
## PTBA 0.2338302 0.4450246 1.0000000 0.5123897
## ITMG 0.2439917 0.4657511 0.5123897 1.0000000
# Mingguan
cor(ret_weekly)
##           BUMI      ADRO      PTBA      ITMG
## BUMI 1.0000000 0.1464760 0.1481013 0.1965911
## ADRO 0.1464760 1.0000000 0.4525642 0.4707697
## PTBA 0.1481013 0.4525642 1.0000000 0.4725777
## ITMG 0.1965911 0.4707697 0.4725777 1.0000000
# Bulanan
cor(ret_monthly)
##            BUMI       ADRO       PTBA      ITMG
## BUMI 1.00000000 0.07401165 0.08662688 0.2092151
## ADRO 0.07401165 1.00000000 0.62892380 0.4410295
## PTBA 0.08662688 0.62892380 1.00000000 0.5896995
## ITMG 0.20921513 0.44102951 0.58969946 1.0000000

Interpretasi: Korelasi antar return saham cenderung positif dengan kekuatan beragam dari rendah-sedang-cukup tinggi, yang menunjukkan adanya bermacam signifikansi hubungan pergerakan antar saham dalam sektor yang sama.

5.2. Korelasi Antara Return Saham dan IHSG

# Harian
cor(merge(ret_daily, ret_IHSG_daily))
##                 BUMI      ADRO      PTBA      ITMG JKSE.Close
## BUMI       1.0000000 0.2147141 0.2338302 0.2439917  0.3219237
## ADRO       0.2147141 1.0000000 0.4450246 0.4657511  0.3382819
## PTBA       0.2338302 0.4450246 1.0000000 0.5123897  0.3518270
## ITMG       0.2439917 0.4657511 0.5123897 1.0000000  0.2446942
## JKSE.Close 0.3219237 0.3382819 0.3518270 0.2446942  1.0000000
# Mingguan
cor(merge(ret_weekly, ret_IHSG_weekly))
##                 BUMI      ADRO      PTBA      ITMG JKSE.Close
## BUMI       1.0000000 0.1464760 0.1481013 0.1965911  0.3051425
## ADRO       0.1464760 1.0000000 0.4525642 0.4707697  0.3148872
## PTBA       0.1481013 0.4525642 1.0000000 0.4725777  0.2586385
## ITMG       0.1965911 0.4707697 0.4725777 1.0000000  0.1804497
## JKSE.Close 0.3051425 0.3148872 0.2586385 0.1804497  1.0000000
# Bulanan
cor(merge(ret_monthly, ret_IHSG_monthly))
##                  BUMI       ADRO       PTBA      ITMG JKSE.Close
## BUMI       1.00000000 0.07401165 0.08662688 0.2092151  0.2858497
## ADRO       0.07401165 1.00000000 0.62892380 0.4410295  0.4177462
## PTBA       0.08662688 0.62892380 1.00000000 0.5896995  0.3337377
## ITMG       0.20921513 0.44102951 0.58969946 1.0000000  0.2653724
## JKSE.Close 0.28584973 0.41774620 0.33373774 0.2653724  1.0000000

Interpretasi: Seluruh saham pertambangan memiliki korelasi positif dengan IHSG, yang menunjukkan bahwa pergerakan saham cenderung mengikuti arah pasar secara umum.

F. Uji Stasioneritas (ADF)

6.1. ADF Harga Saham Harian

lapply(as.data.frame(price_daily), adf.test)
## $BUMI
## 
##  Augmented Dickey-Fuller Test
## 
## data:  X[[i]]
## Dickey-Fuller = -2.6406, Lag order = 9, p-value = 0.3071
## alternative hypothesis: stationary
## 
## 
## $ADRO
## 
##  Augmented Dickey-Fuller Test
## 
## data:  X[[i]]
## Dickey-Fuller = -2.7678, Lag order = 9, p-value = 0.2533
## alternative hypothesis: stationary
## 
## 
## $PTBA
## 
##  Augmented Dickey-Fuller Test
## 
## data:  X[[i]]
## Dickey-Fuller = -3.3964, Lag order = 9, p-value = 0.05405
## alternative hypothesis: stationary
## 
## 
## $ITMG
## 
##  Augmented Dickey-Fuller Test
## 
## data:  X[[i]]
## Dickey-Fuller = -3.2374, Lag order = 9, p-value = 0.08147
## alternative hypothesis: stationary

Interpretasi: Nilai p-value pada seluruh saham lebih besar dari 0,05 sehingga H0 tidak ditolak. Hal ini menunjukkan bahwa data harga saham tidak stasioner dan masih mengandung tren (non-stationary).

6.2. ADF Return Saham Harian

lapply(as.data.frame(ret_daily), adf.test)
## $BUMI
## 
##  Augmented Dickey-Fuller Test
## 
## data:  X[[i]]
## Dickey-Fuller = -8.6641, Lag order = 9, p-value = 0.01
## alternative hypothesis: stationary
## 
## 
## $ADRO
## 
##  Augmented Dickey-Fuller Test
## 
## data:  X[[i]]
## Dickey-Fuller = -10.214, Lag order = 9, p-value = 0.01
## alternative hypothesis: stationary
## 
## 
## $PTBA
## 
##  Augmented Dickey-Fuller Test
## 
## data:  X[[i]]
## Dickey-Fuller = -10.769, Lag order = 9, p-value = 0.01
## alternative hypothesis: stationary
## 
## 
## $ITMG
## 
##  Augmented Dickey-Fuller Test
## 
## data:  X[[i]]
## Dickey-Fuller = -9.7808, Lag order = 9, p-value = 0.01
## alternative hypothesis: stationary

Interpretasi: Seluruh p-value kurang dari 0,05 sehingga H0 ditolak, yang berarti return saham sudah stasioner. Hal ini menunjukkan bahwa transformasi log return berhasil menghilangkan tren pada data harga.

Kesimpulan hasil Uji ADF: Hasil uji Augmented Dickey-Fuller menunjukkan bahwa Harga saham bersifat non-stasioner sedangkan return saham sudah stasioner, sehingga analisis lanjutan dilakukan menggunakan data return.

G. Diagnostik Time Series (ACF PACF)

# BUMI
acf(ret_daily$BUMI, main="ACF BUMI")

pacf(ret_daily$BUMI, main="PACF BUMI")

# ADRO
acf(ret_daily$ADRO, main="ACF ADRO")

pacf(ret_daily$ADRO,main="PACF ADRO")

# PTBA
acf(ret_daily$PTBA, main="ACF PTBA")

pacf(ret_daily$PTBA, main="PACF PTBA")

# ITMG
acf(ret_daily$ITMG, main="ACF ITMG")

pacf(ret_daily$ITMG, main="PACF ITMG")

Interpretasi: Analisis ACF dan PACF menunjukkan bahwa return saham tidak memiliki autokorelasi yang signifikan, sehingga bersifat mendekati white noise. Oleh karena itu, return saham dianggap tidak memiliki pola ketergantungan waktu dan analisis selanjutnya difokuskan pada distribusi return dan hubungan antar saham.

H. Portofolio Markowitz

8.1. Pembentukan Efficient Frontier

# Return matrix
ret_mat <- diff(log(as.matrix(price_daily)))
ret_mat <- ret_mat[complete.cases(ret_mat), ]

nm <- colnames(ret_mat)

# Annualisasi
m_ann   <- colMeans(ret_mat) * 252
var_ann <- cov(ret_mat) * 252

# Target return
targets <- seq(min(m_ann)*0.5, max(m_ann)*1.3, length.out=200)

# Statistik
observasi <- data.frame(Observasi_Hari_Trading = nrow(ret_mat))
ret_tahunan <- round(m_ann * 100, 2) 
vol_tahunan <- round(sqrt(diag(var_ann)) * 100, 2) 
matriks_korelasi <- round(cor(ret_mat), 3) 
observasi
##   Observasi_Hari_Trading
## 1                    977
ret_tahunan
##  BUMI  ADRO  PTBA  ITMG 
## 35.16 -1.80 -1.90  2.89
vol_tahunan
##  BUMI  ADRO  PTBA  ITMG 
## 64.76 46.14 33.84 31.71
matriks_korelasi
##       BUMI  ADRO  PTBA  ITMG
## BUMI 1.000 0.215 0.234 0.244
## ADRO 0.215 1.000 0.445 0.466
## PTBA 0.234 0.445 1.000 0.512
## ITMG 0.244 0.466 0.512 1.000
# Tanpa Short Sales
ef1 <- lapply(targets, function(r){
  w <- tryCatch(
    NMOF::mvPortfolio(m=m_ann, var=var_ann, min.return=r,
                      wmin=0, wmax=1),
    error=function(e) NULL
  )
  if(is.null(w)) return(NULL)
  list(ret=sum(w*m_ann),
       vol=sqrt(t(w)%*%var_ann%*%w),
       w=w)
})
ef1 <- Filter(Negate(is.null), ef1)
# dengan short sales
ef2 <- lapply(targets, function(r){
  w <- tryCatch(
    NMOF::mvPortfolio(m=m_ann, var=var_ann, min.return=r,
                      wmin=-0.5, wmax=1.5),
    error=function(e) NULL
  )
  if(is.null(w)) return(NULL)
  list(ret=sum(w*m_ann),
       vol=sqrt(t(w)%*%var_ann%*%w),
       w=w)
})
ef2 <- Filter(Negate(is.null), ef2)

8.2. Minimum Variance Portfolio (MVP)

# MVP Tanpa Short Sales
i_mvp1 <- which.min(sapply(ef1, function(x) x$vol))
w_mvp1 <- ef1[[i_mvp1]]$w
names(w_mvp1) <- nm

# MVP Dengan Short Sales
i_mvp2 <- which.min(sapply(ef2, function(x) x$vol))
w_mvp2 <- ef2[[i_mvp2]]$w
names(w_mvp2) <- nm

rbind(
  MVP_Tanpa_SS = round(w_mvp1,4),
  MVP_Dengan_SS = round(w_mvp2,4)
)
##                 BUMI   ADRO   PTBA   ITMG
## MVP_Tanpa_SS  0.0714 0.0655 0.3726 0.4904
## MVP_Dengan_SS 0.0714 0.0655 0.3726 0.4904

Interptetasi: Bobot terbesar terdapat pada saham ITMG dan PTBA, yang menunjukkan bahwa kedua saham tersebut memiliki kontribusi terbesar dalam menurunkan risiko portofolio. Saham dengan volatilitas lebih rendah cenderung memiliki bobot yang lebih besar dalam portofolio optimal.

Bobot portofolio yang diperoleh dari data harian digunakan secara konsisten untuk menghitung return portofolio pada frekuensi mingguan dan bulanan. Pendekatan ini bertujuan untuk menjaga komposisi portofolio tetap sama, sehingga perbedaan risiko yang diamati murni disebabkan oleh perbedaan horizon waktu.

8.3. Plot Return Portofolio

ret_port_daily   <- ret_daily %*% w_mvp1
ret_port_weekly  <- ret_weekly %*% w_mvp1
ret_port_monthly <- ret_monthly %*% w_mvp1
par(mfrow=c(3,2)) 

par(mfrow=c(3,1))

# Harian
plot.zoo(ret_port_daily, col="purple", lwd=2,
         main="Return Portofolio Harian",
         xlab="Waktu", ylab="Return")
abline(h=0, col="black", lty=2)

# Mingguan
plot.zoo(ret_port_weekly, col="blue", lwd=2,
         main="Return Portofolio Mingguan",
         xlab="Waktu", ylab="Return")
abline(h=0, col="black", lty=2)

# Bulanan
plot.zoo(ret_port_monthly, col="darkgreen", lwd=2,
         main="Return Portofolio Bulanan",
         xlab="Waktu", ylab="Return")
abline(h=0, col="black", lty=2)

par(mfrow=c(1,1))

8.4. Statistik Portofolio

stat_port <- rbind(
  Harian = c(
    Mean = mean(ret_port_daily),
    SD   = sd(ret_port_daily),
    Skew = skewness(ret_port_daily)
  ),
  Mingguan = c(
    Mean = mean(ret_port_weekly),
    SD   = sd(ret_port_weekly),
    Skew = skewness(ret_port_weekly)
  ),
  Bulanan = c(
    Mean = mean(ret_port_monthly),
    SD   = sd(ret_port_monthly),
    Skew = skewness(ret_port_monthly)
  )
)

round(stat_port,6)
##              Mean       SD      Skew
## Harian   0.000123 0.017574  0.066395
## Mingguan 0.000431 0.039717 -0.498892
## Bulanan  0.000862 0.085513 -1.165178

Interpretasi: Risiko portofolio (SD) meningkat seiring horizon waktu, sementara nilai skewness negatif pada periode lebih panjang menunjukkan kecenderungan risiko penurunan (downside risk).

I. Analisis Value at Risk (VaR)

Value at Risk (VaR) digunakan untuk mengukur potensi kerugian maksimum pada tingkat kepercayaan tertentu dalam suatu periode waktu.

9.1. Setup VaR

set.seed(2026)
alpha <- 0.05
modal <- 10000000 #untuk implementasi VaR misalkan modal saham 10 juta rupiah

9.2. Nilai VaR Return

VaR dengan 4 metode yaitu Historical, Mean-Variance (MV), Monte Carlo, dan Cornish-Fisher Expansion.

Fungsi Menghitung VaR

hitung_VaR <- function(ret){
  VaR_hist <- quantile(ret, probs=alpha)
  VaR_mv <- mean(ret) + qnorm(alpha)*sd(ret)
  sim <- rnorm(10000, mean=mean(ret), sd=sd(ret))
  VaR_mc <- quantile(sim, probs=alpha)
  s <- skewness(ret)
  k <- kurtosis(ret)
  z <- qnorm(alpha)
  z_cf <- z +
    (1/6)*(z^2-1)*s +
    (1/24)*(z^3-3*z)*(k-3) -
    (1/36)*(2*z^3-5*z)*s^2
  VaR_cf <- mean(ret) + z_cf*sd(ret)
  VaR <- c(
    Historical = VaR_hist,
    MeanVariance = VaR_mv,
    MonteCarlo = VaR_mc,
    CornishFisher = VaR_cf
  )
  VaR_rp <- modal * abs(VaR)
  list(VaR=VaR, Rupiah=VaR_rp)
}

9.3. Summary dan Implementasi VaR

# VaR Harian
VaR_daily <- hitung_VaR(ret_port_daily)
round(VaR_daily$VaR,6)
## Historical.5%  MeanVariance MonteCarlo.5% CornishFisher 
##     -0.028808     -0.028784     -0.028606     -0.027610
round(VaR_daily$Rupiah,0)
## Historical.5%  MeanVariance MonteCarlo.5% CornishFisher 
##        288078        287837        286058        276105
# VaR Mingguan
VaR_weekly <- hitung_VaR(ret_port_weekly)
round(VaR_weekly$VaR,6)
## Historical.5%  MeanVariance MonteCarlo.5% CornishFisher 
##     -0.075344     -0.064898     -0.064483     -0.069511
round(VaR_weekly$Rupiah,0)
## Historical.5%  MeanVariance MonteCarlo.5% CornishFisher 
##        753444        648980        644826        695112
# VaR Bulanan
VaR_monthly <- hitung_VaR(ret_port_monthly)
round(VaR_monthly$VaR,6)
## Historical.5%  MeanVariance MonteCarlo.5% CornishFisher 
##     -0.101945     -0.139794     -0.140172     -0.156747
round(VaR_monthly$Rupiah,0)
## Historical.5%  MeanVariance MonteCarlo.5% CornishFisher 
##       1019445       1397941       1401721       1567471

A. Interpretasi Nilai VaR:

  • Nilai VaR harian sekitar 2,8% menunjukkan bahwa potensi kerugian maksimum dalam satu hari relatif terbatas dibanding horizon yang lebih panjang.

  • Nilai VaR Mingguan lebih besar dibanding harian, yang menunjukkan adanya peningkatan risiko akibat akumulasi volatilitas dalam periode waktu yang lebih panjang.

  • Nilai VaR Bulanan menunjukkan risiko yang jauh lebih tinggi, yang mengindikasikan bahwa ketidakpastian investasi meningkat secara signifikan dalam horizon jangka panjang.

B. Interpretasi Implementasi VaR:

  • Dengan modal Rp10.000.000, potensi kerugian maksimum berbeda pada setiap horizon waktu, di mana semakin panjang periode investasi maka nilai risiko yang dihadapi semakin besar. Pada periode harian kerugian relatif kecil, namun pada periode mingguan dan terutama bulanan dapat meningkat hingga lebih dari Rp1.000.000, yang menunjukkan bahwa risiko investasi akan terakumulasi seiring bertambahnya horizon waktu dan mencerminkan tingginya ketidakpastian pada investasi jangka panjang.