Load data

library(quantmod)
## Warning: package 'quantmod' was built under R version 4.3.3
## Loading required package: xts
## Warning: package 'xts' was built under R version 4.3.3
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 4.3.3
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## Loading required package: TTR
## Warning: package 'TTR' was built under R version 4.3.3
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
library(xts)
getSymbols("FPT.VN", src = "yahoo", from = "2015-01-01", to = "2025-01-01")
## Warning: FPT.VN contains missing values. Some functions will not work if
## objects contain missing values in the middle of the series. Consider using
## na.omit(), na.approx(), na.fill(), etc to remove or replace them.
## [1] "FPT.VN"
getSymbols("SSI.VN", src = "yahoo", from = "2015-01-01", to = "2025-01-01")
## Warning: SSI.VN contains missing values. Some functions will not work if
## objects contain missing values in the middle of the series. Consider using
## na.omit(), na.approx(), na.fill(), etc to remove or replace them.
## [1] "SSI.VN"
getSymbols("HPG.VN", src = "yahoo", from = "2015-01-01", to = "2025-01-01")
## Warning: HPG.VN contains missing values. Some functions will not work if
## objects contain missing values in the middle of the series. Consider using
## na.omit(), na.approx(), na.fill(), etc to remove or replace them.
## [1] "HPG.VN"
getSymbols("VCB.VN", src = "yahoo", from = "2015-01-01", to = "2025-01-01")
## Warning: VCB.VN contains missing values. Some functions will not work if
## objects contain missing values in the middle of the series. Consider using
## na.omit(), na.approx(), na.fill(), etc to remove or replace them.
## [1] "VCB.VN"
getSymbols("BID.VN", src = "yahoo", from = "2015-01-01", to = "2025-01-01")
## Warning: BID.VN contains missing values. Some functions will not work if
## objects contain missing values in the middle of the series. Consider using
## na.omit(), na.approx(), na.fill(), etc to remove or replace them.
## [1] "BID.VN"
vcb <- to.weekly(VCB.VN)
## Warning in to.period(x, "weeks", name = name, ...): missing values removed from
## data
hpg <- to.weekly(HPG.VN)
## Warning in to.period(x, "weeks", name = name, ...): missing values removed from
## data
fpt <- to.weekly(FPT.VN)
## Warning in to.period(x, "weeks", name = name, ...): missing values removed from
## data
ssi <- to.weekly(SSI.VN)
## Warning in to.period(x, "weeks", name = name, ...): missing values removed from
## data
bid <- to.weekly(BID.VN)
## Warning in to.period(x, "weeks", name = name, ...): missing values removed from
## data
data <- data.frame(
  Date = index(vcb),
  VCB = vcb$VCB.VN.Close,
  hpg = hpg$HPG.VN.Close,
  fpt = fpt$FPT.VN.Close,
  ssi = ssi$SSI.VN.Close,
  bid = bid$BID.VN.Close
)
data <- xts(data[,-1],order.by = data$Date)
library(PerformanceAnalytics)
## Warning: package 'PerformanceAnalytics' was built under R version 4.3.3
## 
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
## 
##     legend
data_rt <- CalculateReturns(data, method = "log")[-1,]
head(data_rt)
##            VCB.VN.Close HPG.VN.Close FPT.VN.Close SSI.VN.Close BID.VN.Close
## 2015-01-16 -0.016393785  -0.01869216  0.004132261  0.007299340  0.110862569
## 2015-01-23  0.021799196  -0.02871005 -0.002063996  0.000000000  0.071459007
## 2015-01-30 -0.038466223  -0.07455617 -0.018770117 -0.044617120  0.005730662
## 2015-02-06  0.008368196  -0.03838427 -0.004219440 -0.015325959 -0.005730662
## 2015-02-13  0.013793342   0.06725225  0.014690801  0.045290814  0.022728201
## 2015-02-27  0.050745241  -0.04362061  0.016529303  0.007352925  0.043963120

Tính kỳ vọng và ma trận hiệp phương sai

covar <- cov(data_rt)
er <- sapply(data_rt, mean)
covar # Ma trận hiệp psai
##              VCB.VN.Close HPG.VN.Close FPT.VN.Close SSI.VN.Close BID.VN.Close
## VCB.VN.Close 0.0014497686 0.0007293674 0.0004625945 0.0009268142 0.0011204490
## HPG.VN.Close 0.0007293674 0.0022295422 0.0006278781 0.0013289049 0.0007761040
## FPT.VN.Close 0.0004625945 0.0006278781 0.0010179498 0.0008495247 0.0006474889
## SSI.VN.Close 0.0009268142 0.0013289049 0.0008495247 0.0025611105 0.0013348958
## BID.VN.Close 0.0011204490 0.0007761040 0.0006474889 0.0013348958 0.0022870418
er # Kỳ vọng
## VCB.VN.Close HPG.VN.Close FPT.VN.Close SSI.VN.Close BID.VN.Close 
##  0.003123868  0.003256159  0.004923521  0.001642527  0.002883771

Giải bài toán với lợi nhuận kỳ vọng là 0.03%

library(quadprog)
# Hàm giải bài toán Frontier Portfolio
frontier_portfolio <- function(expected_returns, cov_matrix, target_return) {
  n <- length(expected_returns)
  
  # Ma trận D cho hàm mục tiêu (1/2)w'Vw
  Dmat <- cov_matrix
  
  # Vector d cho hàm mục tiêu (ở đây bằng 0)
  dvec <- rep(0, n)
  
  # Ràng buộc:
  # 1. Tổng trọng số bằng 1
  # 2. Lợi nhuận kỳ vọng bằng target_return
  # 3. Tất cả trọng số >= 0 (nếu muốn không cho phép bán khống)
  Amat <- cbind(
    rep(1, n),          # Ràng buộc tổng trọng số
    expected_returns,   # Ràng buộc lợi nhuận
    diag(n)             # Ràng buộc không âm (tùy chọn)
  )
  
  bvec <- c(
    1,                  # Tổng trọng số bằng 1
    target_return,      # Lợi nhuận mục tiêu
    rep(0, n)           # Trọng số >= 0 (tùy chọn)
  )
  
  # Giải bài toán
  solution <- solve.QP(
    Dmat = Dmat,
    dvec = dvec,
    Amat = Amat,
    bvec = bvec,
    meq = 2             # 2 ràng buộc đầu là đẳng thức
  )
  
  # Trả về trọng số tối ưu
  weights <- solution$solution
  names(weights) <- names(expected_returns)
  
  return(weights)
}

# Tính toán danh mục với lợi nhuận mục tiêu 0.3%
target_return <- 0.003
optimal_weights <- frontier_portfolio(er, covar, target_return)
# Kết quả
print("Trọng số tối ưu:")
## [1] "Trọng số tối ưu:"
print(optimal_weights)
## VCB.VN.Close HPG.VN.Close FPT.VN.Close SSI.VN.Close BID.VN.Close 
##   0.43291596   0.11444026   0.14292827   0.25930996   0.05040554
# Kiểm tra ràng buộc
print(paste("Tổng trọng số:", sum(optimal_weights)))
## [1] "Tổng trọng số: 1"
print(paste("Lợi nhuận kỳ vọng:", sum(optimal_weights * er)))
## [1] "Lợi nhuận kỳ vọng: 0.003"
print(paste("Phương sai danh mục:", t(optimal_weights) %*% covar%*% optimal_weights))
## [1] "Phương sai danh mục: 0.00110179506905096"