PHÂN TÍCH CAPM CỔ PHIẾU NGÀNH BÁN LẺ

library(csv)
## Warning: package 'csv' was built under R version 4.4.3
library(dplyr)
data <- read.csv("D:/TIỂU LUẬN C5/dulieu.csv")
head(data)
##         Date   DGW   MWG   PNJ   MSN   PSD    FRT   PET     VNI
## 1 12/08/2025 48250 71900 87000 84400 15100 150700 36550 1592.68
## 2 11/08/2025 47300 72100 86500 82000 15000 151000 35800 1596.86
## 3 08/08/2025 45800 72000 85800 76700 15000 151500 33500 1584.95
## 4 07/08/2025 46450 72400 86400 76200 14700 152000 33650 1581.81
## 5 06/08/2025 46700 72400 86300 76000 14600 154000 34150 1573.71
## 6 05/08/2025 43700 69300 85500 74600 14300 151000 33800 1547.15
# Chuyển đổi cột Date sang định dạng ngày tháng và sắp xếp dữ liệu
data <- data %>%
  mutate(Date = dmy(Date)) %>%
  arrange(Date)

TÍNH TOÁN TỶ SUẤT SINH LỢI VÀ LÃI SUẤT PHI RỦI RO

# Tính toán tỷ suất sinh lợi hàng ngày (sử dụng log return)
# Log return thường được ưa chuộng trong tài chính học thuật
returns_data <- data %>%
  # Sắp xếp lại để đảm bảo tính toán lag() chính xác
  arrange(Date) %>%
  # Sử dụng across để áp dụng hàm cho nhiều cột
  mutate(across(-Date, ~log(. / lag(.)), .names = "ret_{.col}")) %>%
  # Loại bỏ hàng NA đầu tiên
  na.omit()
annual_rf <- 0.03

daily_rf <- (1 + annual_rf)^(1/252) - 1

# Tính toán tỷ suất sinh lợi VƯỢT TRỘI (Excess Returns)
# Đây là biến số thực sự được dùng trong mô hình CAPM
excess_returns_data <- returns_data %>%
  mutate(across(starts_with("ret_"), ~. - daily_rf, .names = "ex_{.col}"))
# Đổi tên cột cho dễ sử dụng
colnames(excess_returns_data) <- gsub("ex_ret_", "", colnames(excess_returns_data))
View(excess_returns_data) # Xem dữ liệu đã xử lý

CHẠY MÔ HÌNH CAPM CHO TỪNG CỔ PHIẾU VÀ KIỂM ĐỊNH

# Lấy danh sách các cổ phiếu (loại trừ cột Date và VNI)
stocks <- c("DGW", "MWG", "PNJ", "MSN", "PSD", "FRT", "PET")

# Tạo một data frame để lưu kết quả
results_df <- data.frame(
  Stock = character(),
  Alpha = numeric(),
  Beta = numeric(),
  R_Squared = numeric(),
  P_Value_Beta = numeric(),
  DW_Test_p_value = numeric(), # Kiểm định tự tương quan Durbin-Watson
  BP_Test_p_value = numeric(), # Kiểm định phương sai thay đổi Breusch-Pagan
  SW_Test_p_value = numeric(), # Kiểm định phân phối chuẩn của phần dư Shapiro-Wilk
  stringsAsFactors = FALSE
)

# Chạy vòng lặp cho từng cổ phiếu
for (stock_code in stocks) {
  
  # Tạo công thức hồi quy
  # Hồi quy TSSL vượt trội của cổ phiếu (Ri - Rf) theo TSSL vượt trội của thị trường (Rm - Rf)
  formula <- as.formula(paste(stock_code, "~ VNI"))
  
  # Chạy mô hình hồi quy tuyến tính (OLS)
  capm_model <- lm(formula, data = excess_returns_data)
  
  # Lấy tóm tắt mô hình
  summary_model <- summary(capm_model)
  
  # Trích xuất các kết quả
  alpha <- summary_model$coefficients[1, 1]
  beta <- summary_model$coefficients[2, 1]
  r_squared <- summary_model$r.squared
  p_value_beta <- summary_model$coefficients[2, 4]
  
  #--- Thực hiện các kiểm định ---
  # 1. Kiểm định tự tương quan của phần dư (Durbin-Watson test)
  # H0: Không có tự tương quan bậc nhất. (p-value > 0.05 là tốt)
  dw_test <- dwtest(capm_model)
  
  # 2. Kiểm định phương sai của phần dư không đổi (Breusch-Pagan test)
  # H0: Phương sai của phần dư không đổi (homoscedasticity). (p-value > 0.05 là tốt)
  bp_test <- bptest(capm_model)
  
  # 3. Kiểm định phân phối chuẩn của phần dư (Shapiro-Wilk test)
  # H0: Phần dư có phân phối chuẩn. (p-value > 0.05 là tốt)
  # Lưu ý: Shapiro-Wilk test chỉ hiệu quả với mẫu nhỏ (n < 5000)
  # Nếu mẫu lớn, có thể dùng biểu đồ Q-Q plot để đánh giá bằng mắt
  residuals <- resid(capm_model)
  if (length(residuals) < 5000) {
    sw_test <- shapiro.test(residuals)
    sw_p_value <- sw_test$p.value
  } else {
    sw_p_value <- NA # Ghi nhận là NA nếu mẫu quá lớn
  }

  # Thêm kết quả vào data frame
  results_df <- rbind(results_df, data.frame(
    Stock = stock_code,
    Alpha = alpha,
    Beta = beta,
    R_Squared = r_squared,
    P_Value_Beta = p_value_beta,
    DW_Test_p_value = dw_test$p.value,
    BP_Test_p_value = bp_test$p.value,
    SW_Test_p_value = sw_p_value
  ))
}

HIỂN THỊ KẾT QUẢ

# In bảng kết quả ra màn hình
print(results_df)
##     Stock      Alpha      Beta R_Squared  P_Value_Beta DW_Test_p_value
## BP    DGW -74534.971  94.01313 0.7032567  0.000000e+00               0
## BP1   MWG -32822.341  70.34274 0.7937851  0.000000e+00               0
## BP2   PNJ  -3195.506  64.49292 0.5246301 1.501319e-285               0
## BP3   MSN   4104.053  67.30060 0.3651130 2.904000e-175               0
## BP4   PSD -12271.674  19.64964 0.7191265  0.000000e+00               0
## BP5   FRT -98225.906 135.03604 0.2860354 1.680773e-130               0
## BP6   PET -31610.804  45.24755 0.7151548  0.000000e+00               0
##     BP_Test_p_value SW_Test_p_value
## BP     1.586302e-04    1.203880e-23
## BP1    1.231978e-15    3.012652e-17
## BP2    4.782682e-88    4.319352e-23
## BP3    8.363417e-67    2.630053e-25
## BP4    8.288380e-41    2.660643e-12
## BP5   1.126039e-185    5.356215e-24
## BP6    1.838002e-56    2.647197e-17

TÍNH TOÁN VÀ TRÌNH BÀY TỶ SUẤT SINH LỢI KỲ VỌNG (CAPM)

# 1. Lấy TSSL kỳ vọng của thị trường E(Rm) bằng cách tính trung bình TSSL hàng ngày của VNI
# rồi quy đổi ra năm (giả sử 252 ngày giao dịch)
mean_daily_market_return <- mean(returns_data$ret_VNI)
annual_market_return <- mean_daily_market_return * 252
# 2. Tính Phần bù rủi ro thị trường (Market Risk Premium)
market_risk_premium <- annual_market_return - annual_rf
# 3. Áp dụng công thức CAPM để tính TSSL kỳ vọng cho từng cổ phiếu
# E(Ri) = Rf + Beta * (E(Rm) - Rf)
# Chúng ta sẽ thêm một cột mới 'ERi' vào bảng kết quả đã có
results_df <- results_df %>%
  mutate(ERi = annual_rf + Beta * market_risk_premium)
# In ra xem thử E(Rm) và Market Risk Premium
cat("Tỷ suất sinh lợi kỳ vọng hàng năm của thị trường E(Rm):", percent(annual_market_return), "\n")
## Tỷ suất sinh lợi kỳ vọng hàng năm của thị trường E(Rm): 7%
cat("Phần bù rủi ro thị trường (E(Rm) - Rf):", percent(market_risk_premium), "\n\n")
## Phần bù rủi ro thị trường (E(Rm) - Rf): 4%
# --- Tạo bảng kết quả cuối cùng ---

# Tạo một data frame mới để trình bày cho đẹp
expected_return_table <- data.frame(
  "Hệ số Beta (β)" = results_df$Beta,
  "TSSL kỳ vọng (E(Ri))" = scales::percent(results_df$ERi, accuracy = 0.01)
)

# Đặt tên hàng là mã cổ phiếu
rownames(expected_return_table) <- results_df$Stock

# In bảng ra dưới định dạng đẹp mắt bằng kable()
kable(expected_return_table, 
      digits = 4, # Số chữ số thập phân cho cột Beta
      caption = "Bảng 4.4: Tỷ suất sinh lời kỳ vọng theo mô hình CAPM")
Bảng 4.4: Tỷ suất sinh lời kỳ vọng theo mô hình CAPM
Hệ.số.Beta..β. TSSL.kỳ.vọng..E.Ri..
DGW 94.0131 414.16%
MWG 70.3427 310.64%
PNJ 64.4929 285.06%
MSN 67.3006 297.34%
PSD 19.6496 88.94%
FRT 135.0360 593.57%
PET 45.2476 200.89%