Câu 1:

#Đọc dữ liệu
df <- read.csv("C:/Users/Admin/Desktop/Nguyễn Châu Quý_C5_TH.csv", 
                 header = TRUE, sep = ",")
# Cài đặt và tải các thư viện cần thiết
packages <- c("readr", "dplyr", "tseries", "ggplot2", "PerformanceAnalytics", "tidyr")
lapply(packages, function(pkg) {
  if (!require(pkg, character.only = TRUE)) install.packages(pkg, dependencies = TRUE)
  library(pkg, character.only = TRUE)
})
## [[1]]
## [1] "readr"     "stats"     "graphics"  "grDevices" "utils"     "datasets" 
## [7] "methods"   "base"     
## 
## [[2]]
## [1] "dplyr"     "readr"     "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"     
## 
## [[3]]
##  [1] "tseries"   "dplyr"     "readr"     "stats"     "graphics"  "grDevices"
##  [7] "utils"     "datasets"  "methods"   "base"     
## 
## [[4]]
##  [1] "ggplot2"   "tseries"   "dplyr"     "readr"     "stats"     "graphics" 
##  [7] "grDevices" "utils"     "datasets"  "methods"   "base"     
## 
## [[5]]
##  [1] "PerformanceAnalytics" "xts"                  "zoo"                 
##  [4] "ggplot2"              "tseries"              "dplyr"               
##  [7] "readr"                "stats"                "graphics"            
## [10] "grDevices"            "utils"                "datasets"            
## [13] "methods"              "base"                
## 
## [[6]]
##  [1] "tidyr"                "PerformanceAnalytics" "xts"                 
##  [4] "zoo"                  "ggplot2"              "tseries"             
##  [7] "dplyr"                "readr"                "stats"               
## [10] "graphics"             "grDevices"            "utils"               
## [13] "datasets"             "methods"              "base"
# Đổi tên cột ngày
colnames(df)[1] <- "Date"
df$Date <- as.Date(df$Date, format = "%d/%m/%Y")
# Ép kiểu numeric cho toàn bộ các cột giá cổ phiếu (trừ cột Date)
df <- df %>%
  mutate(across(-Date, ~ as.numeric(gsub(",", "", .))))
# Sắp xếp theo ngày
df <- df %>% arrange(Date)
# Tính log return
returns <- df %>%
  mutate(across(-Date, ~ log(. / lag(.)), .names = "ret_{.col}")) %>%
  drop_na()

Kiểm định tính dừng của chuỗi lợi suất

Trước khi ước lượng mô hình hồi quy CAPM, cần đảm bảo rằng các chuỗi dữ liệu sử dụng trong mô hình (cụ thể là lợi suất log của cổ phiếu và chỉ số thị trường) là chuỗi dừng. Nếu chuỗi không dừng, mô hình hồi quy có thể rơi vào trạng thái giả mạo, khiến kết quả ước lượng thiếu tin cậy.

Do đó, em tiến hành kiểm định ADF (Augmented Dickey-Fuller) cho từng chuỗi log return.

# Kiểm định tính dừng ADF
ret_cols <- names(returns)[grepl("^ret_", names(returns))]
adf_results <- sapply(ret_cols, function(col) {
  test <- adf.test(returns[[col]])
  c(statistic = test$statistic, p.value = test$p.value, stationary = test$p.value < 0.05)
})

adf_results <- as.data.frame(t(adf_results))
print(adf_results)
##         statistic.Dickey-Fuller p.value stationary
## ret_BID               -8.708162    0.01          1
## ret_MBB               -9.323858    0.01          1
## ret_OCB              -10.614608    0.01          1
## ret_MSB              -11.729339    0.01          1
## ret_ACB              -10.368532    0.01          1
## ret_VCB               -9.727220    0.01          1
## ret_VNI               -9.232274    0.01          1

Giả thuyết kiểm định:

\[ \begin{aligned} H_0 &: \text{Chuỗi có đơn vị gốc (không dừng)} \\ H_1 &: \text{Chuỗi không có đơn vị gốc (dừng)} \end{aligned} \]

Tất cả chuỗi có thống kê ADF âm lớn và p-value nhỏ hơn 0.05. Vì vậy bác bỏ giả thuyết H0 , nghĩa là các chuỗi là dừng. Điều này cho phép tiếp tục các bước phân tích hồi quy và định lượng theo mô hình CAPM mà không vi phạm giả định thống kê.

Ước lượng CAPM

# Ước lượng CAPM
# Giả định:
Rf <- 0.04 / 252  # ≈ 0.000159
E_Rm <- 0.08 / 252  # ≈ 0.000317

market_ret <- returns$ret_VNI
capm_list <- list()

for (col in ret_cols[ret_cols != "ret_VNI"]) {
  model <- lm(returns[[col]] ~ market_ret)
  beta <- coef(model)[2]
  alpha <- coef(model)[1]
  E_Ri_actual <- mean(returns[[col]])
  E_Ri_CAPM <- Rf + beta * (E_Rm - Rf)
  capm_list[[col]] <- data.frame(
    ticker = gsub("ret_", "", col),
    alpha = alpha,
    beta = beta,
    E_Ri_actual = E_Ri_actual,
    E_Ri_CAPM = E_Ri_CAPM
  )
}
capm_df <- do.call(rbind, capm_list)
print(capm_df)
##         ticker         alpha      beta   E_Ri_actual    E_Ri_CAPM
## ret_BID    BID -7.766722e-05 1.1303748 -1.755662e-05 0.0003381547
## ret_MBB    MBB  9.043461e-05 1.2438075  1.565773e-04 0.0003561599
## ret_OCB    OCB -6.128751e-04 1.0944845 -5.546730e-04 0.0003324579
## ret_MSB    MSB -2.496220e-04 1.1602584 -1.879222e-04 0.0003428982
## ret_ACB    ACB -1.808567e-04 1.0566457 -1.246668e-04 0.0003264517
## ret_VCB    VCB -3.204503e-04 0.7381066 -2.811996e-04 0.0002758899

Kết quả ước lượng mô hình CAPM cho thấy đa số cổ phiếu trong nhóm đều có Beta lớn hơn 1, điển hình như MBB (1,24), BID (1,13), MSB (1,16), OCB (1,09) và ACB (1,06). Điều này phản ánh các cổ phiếu này có mức độ biến động cao hơn so với thị trường chung, đồng nghĩa với rủi ro hệ thống lớn hơn. Riêng VCB có Beta thấp nhất (0,74), cho thấy cổ phiếu này ổn định hơn, ít biến động hơn thị trường.

Xét về Alpha, chỉ có MBB đạt Alpha dương (+0,000090), nghĩa là cổ phiếu này mang lại suất sinh lợi vượt mức kỳ vọng theo lý thuyết CAPM. Các mã còn lại đều có Alpha âm (OCB thấp nhất), thể hiện lợi suất thực tế thấp hơn mức kỳ vọng, hàm ý khả năng các cổ phiếu này đang được thị trường định giá cao hơn giá trị hợp lý.

So sánh suất sinh lợi thực tế (E[Ri]) và suất sinh lợi theo CAPM, MBB tiếp tục nổi bật khi E[Ri] thực tế dương (+0,000156) và gần sát mức dự báo. Ngược lại, các cổ phiếu khác đều có E[Ri] thực tế âm, cho thấy mức sinh lợi thực tế thấp hơn so với rủi ro mà nhà đầu tư gánh chịu.

Tóm lại, MBB là cổ phiếu duy nhất trong nhóm có dấu hiệu định giá thấp, phù hợp để cân nhắc đầu tư thêm. Các cổ phiếu còn lại có thể đang định giá cao so với mô hình CAPM, nên nhà đầu tư cần thận trọng, tránh mua mới hoặc nên kết hợp thêm các phân tích khác để ra quyết định chính xác hơn.

Câu 2:

# Tạo grid Beta
beta_seq <- seq(min(capm_df$beta) - 0.2, max(capm_df$beta) + 0.2, by = 0.05)
sml_line <- data.frame(
  Beta = beta_seq,
  ER_SML = Rf + beta_seq * (E_Rm - Rf)
)

# Vẽ
ggplot() +
  geom_line(data = sml_line, aes(x = Beta, y = ER_SML), color = "red", linetype = "dashed") +
  geom_point(data = capm_df, aes(x = beta, y = E_Ri_actual), color = "blue", size = 3) +
  geom_text(data = capm_df, aes(x = beta, y = E_Ri_actual, label = ticker), vjust = -1) +
  labs(title = "Security Market Line (SML)",
       subtitle = paste0("Rf = ", round(Rf, 6), " | E(Rm) = ", round(E_Rm, 6)),
       x = "Beta",
       y = "Expected Return") +
  theme_minimal()

Quan sát trên đường Security Market Line (SML), ta thấy toàn bộ các cổ phiếu BID, MBB, ACB, MSB, OCB, VCB đều nằm dưới đường SML lý thuyết. Điều này hàm ý rằng:

Suất sinh lợi thực tế (E[Ri]) của các cổ phiếu này thấp hơn suất sinh lợi kỳ vọng theo rủi ro hệ thống (beta) được mô hình CAPM tính toán.Do đó, các cổ phiếu này có dấu hiệu đang được định giá cao hơn mức hợp lý. Nhà đầu tư đang chấp nhận lợi suất thấp so với mức rủi ro, có thể do thị trường kỳ vọng yếu hoặc đang giai đoạn điều chỉnh.Trong bối cảnh này, nhà đầu tư cần cân nhắc thận trọng, hạn chế mua mới, và có thể xem xét chốt lời hoặc tái cơ cấu danh mục sang các cổ phiếu có định giá hợp lý hơn.

Câu 3:

Ma trận hiệp phương sai

# Ma trận hiệp phương sai
ret_matrix <- returns %>%
  select(all_of(ret_cols[ret_cols != "ret_VNI"])) %>%
  as.matrix()
cov_matrix <- cov(ret_matrix)
print(round(cov_matrix, 6))
##          ret_BID  ret_MBB  ret_OCB  ret_MSB  ret_ACB  ret_VCB
## ret_BID 0.000418 0.000252 0.000210 0.000215 0.000207 0.000145
## ret_MBB 0.000252 0.000418 0.000251 0.000284 0.000253 0.000136
## ret_OCB 0.000210 0.000251 0.000598 0.000311 0.000190 0.000108
## ret_MSB 0.000215 0.000284 0.000311 0.000456 0.000218 0.000116
## ret_ACB 0.000207 0.000253 0.000190 0.000218 0.000389 0.000128
## ret_VCB 0.000145 0.000136 0.000108 0.000116 0.000128 0.000405

VaR của cổ phiếu và danh mục

VaR_95_individual <- apply(ret_matrix, 2, function(x) quantile(x, 0.05))
weights <- rep(1 / ncol(ret_matrix), ncol(ret_matrix))
returns$Portfolio <- ret_matrix %*% weights
VaR_95_portfolio <- quantile(returns$Portfolio, 0.05)

cat("\n>>> VaR 95% (1 ngày) từng cổ phiếu (%):\n")
## 
## >>> VaR 95% (1 ngày) từng cổ phiếu (%):
print(round(abs(VaR_95_individual) * 100, 4))
## ret_BID ret_MBB ret_OCB ret_MSB ret_ACB ret_VCB 
##  3.0732  2.9773  3.2564  3.3630  2.6043  2.2222
cat("\n>>> VaR 95% (1 ngày) danh mục (%):\n")
## 
## >>> VaR 95% (1 ngày) danh mục (%):
cat(round(abs(VaR_95_portfolio) * 100, 4), "\n")
## 2.5307