Load Library

library(quantmod)
library(ggplot2)
library(dplyr)

Soal 1 - Scraping Data dari Yahoo Finance

# Daftar 50 saham Indonesia kapitalisasi terbesar (suffix .JK untuk Yahoo Finance)
tickers <- c(
  "BBCA.JK", "BBRI.JK", "BMRI.JK", "BYAN.JK", "TLKM.JK",
  "ASII.JK", "BBNI.JK", "GOTO.JK", "UNVR.JK", "ICBP.JK",
  "KLBF.JK", "HMSP.JK", "EXCL.JK", "PGAS.JK", "SMGR.JK",
  "INDF.JK", "GGRM.JK", "AALI.JK", "BSDE.JK", "LPKR.JK",
  "PTBA.JK", "ADRO.JK", "INCO.JK", "ANTM.JK", "MNCN.JK",
  "ITMG.JK", "BUMI.JK", "HRUM.JK", "MEDC.JK", "BRPT.JK",
  "INKP.JK", "TPIA.JK", "AKRA.JK", "JSMR.JK", "BBTN.JK",
  "SCMA.JK", "TBIG.JK", "MAPI.JK", "ERAA.JK", "SIDO.JK",
  "DSNG.JK", "PALM.JK", "LPPF.JK", "UNTR.JK", "MDKA.JK",
  "ESSA.JK", "SRTG.JK", "DNET.JK", "BRIS.JK", "ACES.JK"
)

# Ambil data dari Yahoo Finance (Jan 2024 - Mar 2026)
start_date <- "2024-01-01"
end_date   <- "2026-03-31"

# Simpan semua data ke dalam satu dataframe
all_stocks <- data.frame()

for (ticker in tickers) {
  tryCatch({
    data <- getSymbols(ticker, src = "yahoo",
                       from = start_date, to = end_date,
                       auto.assign = FALSE)
    
    df <- data.frame(
      Date   = index(data),
      Ticker = gsub(".JK", "", ticker),
      Price  = as.numeric(Ad(data))  # Adjusted Close Price
    )
    
    all_stocks <- rbind(all_stocks, df)
    
  }, error = function(e) {
    cat("Gagal ambil data:", ticker, "\n")
  })
}

# Cek hasil
cat("Jumlah baris data:", nrow(all_stocks), "\n")
## Jumlah baris data: 26350
cat("Jumlah saham:", length(unique(all_stocks$Ticker)), "\n")
## Jumlah saham: 50
cat("Rentang tanggal:", as.character(min(all_stocks$Date)),
    "sampai", as.character(max(all_stocks$Date)), "\n")
## Rentang tanggal: 2024-01-02 sampai 2026-03-30

Soal 2 - Exploratory Data Analysis (EDA)

Ringkasan Statistik Awal

summary(all_stocks)
##       Date               Ticker              Price        
##  Min.   :2024-01-02   Length:26350       Min.   :   50.0  
##  1st Qu.:2024-07-30   Class :character   1st Qu.:  932.5  
##  Median :2025-02-12   Mode  :character   Median : 1936.7  
##  Mean   :2025-02-14                      Mean   : 4057.3  
##  3rd Qu.:2025-09-10                      3rd Qu.: 4760.6  
##  Max.   :2026-03-30                      Max.   :31975.0

Cek Missing Value

# Jumlah missing value per kolom
cat("Missing value per kolom:\n")
## Missing value per kolom:
colSums(is.na(all_stocks))
##   Date Ticker  Price 
##      0      0      0
cat("\nTotal missing value:", sum(is.na(all_stocks)), "\n")
## 
## Total missing value: 0

Hapus Missing Value

# Hapus baris yang mengandung missing value
stocks_clean <- all_stocks[complete.cases(all_stocks), ]

cat("Jumlah baris sebelum:", nrow(all_stocks), "\n")
## Jumlah baris sebelum: 26350
cat("Jumlah baris sesudah:", nrow(stocks_clean), "\n")
## Jumlah baris sesudah: 26350
cat("Missing value setelah dibersihkan:", sum(is.na(stocks_clean)), "\n")
## Missing value setelah dibersihkan: 0

Ringkasan Statistik Setelah Dibersihkan

summary(stocks_clean)
##       Date               Ticker              Price        
##  Min.   :2024-01-02   Length:26350       Min.   :   50.0  
##  1st Qu.:2024-07-30   Class :character   1st Qu.:  932.5  
##  Median :2025-02-12   Mode  :character   Median : 1936.7  
##  Mean   :2025-02-14                      Mean   : 4057.3  
##  3rd Qu.:2025-09-10                      3rd Qu.: 4760.6  
##  Max.   :2026-03-30                      Max.   :31975.0

Soal 3 - Visualisasi Harga Saham Individual

# Ambil daftar ticker
ticker_list <- unique(stocks_clean$Ticker)

# Plot per batch 10 saham
for (i in seq(1, length(ticker_list), by = 10)) {
  
  batch <- ticker_list[i:min(i+9, length(ticker_list))]
  data_batch <- stocks_clean[stocks_clean$Ticker %in% batch, ]
  
  p <- ggplot(data_batch, aes(x = Date, y = Price, color = Ticker)) +
    geom_line() +
    facet_wrap(~ Ticker, scales = "free_y") +
    labs(
      title = "Harga Saham Individual",
      x = "Tanggal",
      y = "Harga (IDR)"
    ) +
    theme_minimal() +
    theme(legend.position = "none",
          axis.text.x = element_text(angle = 45, hjust = 1))
  
  print(p)
}


Soal 4 - Pengelompokan Saham Berdasarkan Pola Harga

Saham dikelompokkan berdasarkan accumulative return dan volatilitas menggunakan K-Means clustering.

# Hitung return harian
stocks_clean <- stocks_clean %>%
  arrange(Ticker, Date) %>%
  group_by(Ticker) %>%
  mutate(Daily_Return = (Price / lag(Price)) - 1) %>%
  ungroup()

stocks_clean <- stocks_clean[!is.na(stocks_clean$Daily_Return), ]

# Buat ringkasan per saham
summary_saham <- stocks_clean %>%
  group_by(Ticker) %>%
  summarise(
    Accum_Return = prod(1 + Daily_Return) - 1,
    Volatility   = sd(Daily_Return)
  )

# Normalisasi data untuk clustering
data_cluster <- scale(summary_saham[, c("Accum_Return", "Volatility")])

# K-Means dengan 4 kelompok
set.seed(123)
hasil_cluster <- kmeans(data_cluster, centers = 4, nstart = 20)

summary_saham$Cluster <- as.factor(hasil_cluster$cluster)

# Tampilkan hasil
print(summary_saham)
## # A tibble: 50 × 4
##    Ticker Accum_Return Volatility Cluster
##    <chr>         <dbl>      <dbl> <fct>  
##  1 AALI         0.158      0.0158 2      
##  2 ACES        -0.414      0.0243 3      
##  3 ADRO         1.21       0.0288 4      
##  4 AKRA         0.0952     0.0256 3      
##  5 ANTM         1.30       0.0308 4      
##  6 ASII         0.345      0.0204 2      
##  7 BBCA        -0.238      0.0164 3      
##  8 BBNI        -0.112      0.0209 3      
##  9 BBRI        -0.303      0.0204 3      
## 10 BBTN         0.103      0.0254 3      
## # ℹ 40 more rows
# Visualisasi cluster
ggplot(summary_saham, aes(x = Volatility, y = Accum_Return,
                           color = Cluster, label = Ticker)) +
  geom_point(size = 3) +
  geom_text(vjust = -0.8, size = 3) +
  labs(
    title = "Pengelompokan Saham Berdasarkan Return dan Volatilitas",
    x = "Volatilitas (Std Dev Return Harian)",
    y = "Accumulative Return",
    color = "Kelompok"
  ) +
  theme_minimal()


Soal 5 - Pilih 2 Saham dengan Accumulative Return Tertinggi

# Urutkan dari return tertinggi
top2 <- summary_saham %>%
  arrange(desc(Accum_Return)) %>%
  slice(1:2)

cat("2 saham dengan accumulative return tertinggi:\n")
## 2 saham dengan accumulative return tertinggi:
print(top2)
## # A tibble: 2 × 4
##   Ticker Accum_Return Volatility Cluster
##   <chr>         <dbl>      <dbl> <fct>  
## 1 DSNG           2.38     0.0339 4      
## 2 BUMI           1.38     0.0463 4
top2_tickers <- top2$Ticker

Buat Portofolio Equal Weight

# Filter data hanya untuk 2 saham terpilih
data_top2 <- stocks_clean %>%
  filter(Ticker %in% top2_tickers) %>%
  select(Date, Ticker, Daily_Return)

# Pivot ke wide format
data_wide <- data_top2 %>%
  tidyr::pivot_wider(names_from = Ticker, values_from = Daily_Return) %>%
  tidyr::drop_na()

# Hitung return portofolio (rata-rata dari 2 saham = equal weight)
data_wide$Portfolio_Return <- rowMeans(data_wide[, top2_tickers])

# Hitung cumulative return portofolio
data_wide$Portfolio_Cumulative <- cumprod(1 + data_wide$Portfolio_Return) - 1

head(data_wide)
## # A tibble: 6 × 5
##   Date          BUMI     DSNG Portfolio_Return Portfolio_Cumulative
##   <date>       <dbl>    <dbl>            <dbl>                <dbl>
## 1 2024-01-03  0.0316 -0.0275           0.00203              0.00203
## 2 2024-01-04  0.0102  0.0283           0.0193               0.0213 
## 3 2024-01-05  0      -0.00917         -0.00459              0.0166 
## 4 2024-01-08 -0.0202  0               -0.0101               0.00637
## 5 2024-01-09 -0.0103 -0.0185          -0.0144              -0.00814
## 6 2024-01-10  0       0.00943          0.00472             -0.00346

Soal 6 - Statistik Portofolio

accum_return_portf <- tail(data_wide$Portfolio_Cumulative, 1)
mean_return_portf  <- mean(data_wide$Portfolio_Return)
sd_return_portf    <- sd(data_wide$Portfolio_Return)

cat("=== Statistik Portofolio ===\n")
## === Statistik Portofolio ===
cat("Accumulative Return :", round(accum_return_portf * 100, 2), "%\n")
## Accumulative Return : 240.81 %
cat("Mean Return Harian  :", round(mean_return_portf * 100, 4), "%\n")
## Mean Return Harian  : 0.2783 %
cat("Standard Deviation  :", round(sd_return_portf * 100, 4), "%\n")
## Standard Deviation  : 3.0186 %

Soal 7 - Perbandingan dengan IHSG

Ambil Data IHSG

ihsg_data <- getSymbols("^JKSE", src = "yahoo",
                         from = start_date, to = end_date,
                         auto.assign = FALSE)

ihsg_df <- data.frame(
  Date  = index(ihsg_data),
  IHSG  = as.numeric(Ad(ihsg_data))
)

ihsg_df <- ihsg_df[!is.na(ihsg_df$IHSG), ]

# Hitung return harian IHSG
ihsg_df$IHSG_Return     <- c(NA, diff(ihsg_df$IHSG) / head(ihsg_df$IHSG, -1))
ihsg_df                 <- ihsg_df[!is.na(ihsg_df$IHSG_Return), ]
ihsg_df$IHSG_Cumulative <- cumprod(1 + ihsg_df$IHSG_Return) - 1

Statistik IHSG

accum_return_ihsg <- tail(ihsg_df$IHSG_Cumulative, 1)
mean_return_ihsg  <- mean(ihsg_df$IHSG_Return)
sd_return_ihsg    <- sd(ihsg_df$IHSG_Return)

cat("=== Statistik IHSG ===\n")
## === Statistik IHSG ===
cat("Accumulative Return :", round(accum_return_ihsg * 100, 2), "%\n")
## Accumulative Return : -3.17 %
cat("Mean Return Harian  :", round(mean_return_ihsg * 100, 4), "%\n")
## Mean Return Harian  : 7e-04 %
cat("Standard Deviation  :", round(sd_return_ihsg * 100, 4), "%\n")
## Standard Deviation  : 1.1589 %

Tabel Perbandingan

tabel_perbandingan <- data.frame(
  Metrik = c("Accumulative Return", "Mean Return Harian", "Standard Deviation"),
  Portofolio = c(
    paste0(round(accum_return_portf * 100, 2), "%"),
    paste0(round(mean_return_portf  * 100, 4), "%"),
    paste0(round(sd_return_portf    * 100, 4), "%")
  ),
  IHSG = c(
    paste0(round(accum_return_ihsg * 100, 2), "%"),
    paste0(round(mean_return_ihsg  * 100, 4), "%"),
    paste0(round(sd_return_ihsg    * 100, 4), "%")
  )
)

print(tabel_perbandingan)
##                Metrik Portofolio    IHSG
## 1 Accumulative Return    240.81%  -3.17%
## 2  Mean Return Harian    0.2783%  7e-04%
## 3  Standard Deviation    3.0186% 1.1589%

Soal 8 - Visualisasi Overlay Portofolio vs IHSG

# Gabungkan data portofolio dan IHSG berdasarkan tanggal
data_portf_plot <- data_wide[, c("Date", "Portfolio_Cumulative")]
data_ihsg_plot  <- ihsg_df[, c("Date", "IHSG_Cumulative")]

combined <- merge(data_portf_plot, data_ihsg_plot, by = "Date")

# Faktor skala untuk double axis
scale_factor <- max(combined$Portfolio_Cumulative) / max(combined$IHSG_Cumulative)

ggplot(combined, aes(x = Date)) +
  geom_line(aes(y = Portfolio_Cumulative, color = "Portofolio"), linewidth = 1) +
  geom_line(aes(y = IHSG_Cumulative * scale_factor, color = "IHSG"),
            linewidth = 1, linetype = "dashed") +
  scale_y_continuous(
    name = "Cumulative Return - Portofolio",
    labels = scales::percent,
    sec.axis = sec_axis(
      transform = ~ . / scale_factor,
      name = "Cumulative Return - IHSG",
      labels = scales::percent
    )
  ) +
  scale_color_manual(values = c("Portofolio" = "steelblue", "IHSG" = "tomato")) +
  labs(
    title = "Pergerakan Portofolio vs IHSG",
    x = "Tanggal",
    color = "Keterangan"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))


Soal 9 - Interpretasi

Kelebihan Portofolio

  1. Return tinggi secara historis. Portofolio dipilih berdasarkan 2 saham dengan return tertinggi, sehingga secara historis menunjukkan performa yang baik dibandingkan IHSG.

  2. Mudah dikelola. Hanya 2 saham dengan bobot sama (50:50), sehingga sangat sederhana untuk dipantau dan di-rebalancing.

  3. Saham likuid. Kedua saham berasal dari daftar 50 saham terbesar di IDX, artinya mudah dibeli dan dijual di pasar.

Kekurangan Portofolio

  1. Risiko konsentrasi tinggi. Hanya 2 saham berarti jika salah satu saham turun drastis, portofolio langsung terdampak besar. Tidak ada diversifikasi yang memadai.

  2. Bias historis. Saham dipilih karena return masa lalu tertinggi. Return masa lalu tidak menjamin return masa depan yang sama.

  3. Volatilitas lebih tinggi. Portofolio dengan sedikit saham cenderung lebih fluktuatif dibandingkan indeks seperti IHSG yang terdiri dari ratusan saham.

  4. Tidak mempertimbangkan korelasi antar saham. Portofolio yang baik seharusnya memilih saham yang tidak bergerak bersamaan (korelasi rendah) agar risiko lebih tersebar.

Kesimpulan

Portofolio yang dibangun menunjukkan performa yang baik secara historis, namun memiliki risiko yang lebih tinggi karena hanya terdiri dari 2 saham. Jika dibandingkan dengan IHSG, portofolio ini bisa menghasilkan return lebih tinggi, tetapi juga lebih berisiko. Untuk investasi jangka panjang yang lebih aman, disarankan untuk menambah jumlah saham dan mempertimbangkan korelasi antar saham.


Data bersumber dari Yahoo Finance menggunakan package quantmod di R.