Setup: Load Data
library(tidyverse)
library(lubridate)
library(knitr)
# Read the ETF price data
etf <- read.csv("myetf4.csv", stringsAsFactors = FALSE)
colnames(etf) <- c("Date", "X0050", "X0056", "X006205", "X00646")
etf$Date <- as.Date(etf$Date, format = "%Y/%m/%d")
# Filter insample: 2015/12/14 to 2018/12/28
insample <- etf %>%
filter(Date >= as.Date("2015-12-14") & Date <= as.Date("2018-12-28")) %>%
arrange(Date)
cat("Insample period:", format(min(insample$Date)), "to", format(max(insample$Date)), "\n")
## Insample period: 2015-12-14 to 2018-12-28
cat("Number of observations:", nrow(insample), "\n")
## Number of observations: 751
Q1. GMVP Using Daily Returns
Compute Daily Returns
prices_daily <- insample[, c("X0050", "X0056", "X006205", "X00646")]
# Log daily returns
ret_daily <- as.matrix(log(prices_daily[-1, ] / prices_daily[-nrow(prices_daily), ]))
colnames(ret_daily) <- c("0050", "0056", "006205", "00646")
cat("Daily return matrix dimensions:", dim(ret_daily), "\n")
## Daily return matrix dimensions: 750 4
cat("\nSample mean daily returns:\n")
##
## Sample mean daily returns:
print(round(colMeans(ret_daily) * 100, 4))
## 0050 0056 006205 00646
## 0.0424 0.0362 -0.0277 0.0226
GMVP: Daily Returns
The Global Minimum Variance Portfolio (GMVP)
minimizes portfolio variance:
\[\min_{w} \; w^\top \Sigma w \quad
\text{subject to} \quad \mathbf{1}^\top w = 1\]
The closed-form solution is:
\[w^* = \frac{\Sigma^{-1}
\mathbf{1}}{\mathbf{1}^\top \Sigma^{-1} \mathbf{1}}\]
# Covariance matrix of daily returns
Sigma_d <- cov(ret_daily)
ones <- rep(1, 4)
Sigma_d_inv <- solve(Sigma_d)
# GMVP weights
w_gmvp_d <- (Sigma_d_inv %*% ones) / as.numeric(t(ones) %*% Sigma_d_inv %*% ones)
w_gmvp_d <- as.vector(w_gmvp_d)
names(w_gmvp_d) <- colnames(ret_daily)
cat("=== GMVP Weights (Daily Returns) ===\n")
## === GMVP Weights (Daily Returns) ===
kable(data.frame(
ETF = names(w_gmvp_d),
Weight = round(w_gmvp_d, 6),
Weight_pct = paste0(round(w_gmvp_d * 100, 4), "%")
), col.names = c("ETF", "Weight", "Weight (%)"), align = "lrr")
| 0050 |
0050 |
-0.224131 |
-22.4131% |
| 0056 |
0056 |
0.729873 |
72.9873% |
| 006205 |
006205 |
0.108076 |
10.8076% |
| 00646 |
00646 |
0.386183 |
38.6183% |
Q2. GMVP Using Monthly Returns
Compute Monthly Returns
# Keep last trading day of each month
monthly <- insample %>%
mutate(YM = format(Date, "%Y-%m")) %>%
group_by(YM) %>%
slice_tail(n = 1) %>%
ungroup() %>%
arrange(YM)
prices_m <- monthly[, c("X0050", "X0056", "X006205", "X00646")]
# Monthly log returns
ret_monthly <- as.matrix(log(prices_m[-1, ] / prices_m[-nrow(prices_m), ]))
colnames(ret_monthly) <- c("0050", "0056", "006205", "00646")
cat("Monthly return matrix dimensions:", dim(ret_monthly), "\n")
## Monthly return matrix dimensions: 36 4
cat("\nSample mean monthly returns:\n")
##
## Sample mean monthly returns:
print(round(colMeans(ret_monthly) * 100, 4))
## 0050 0056 006205 00646
## 0.8206 0.6625 -0.6612 0.4075
GMVP: Monthly Returns
Sigma_m <- cov(ret_monthly)
Sigma_m_inv <- solve(Sigma_m)
w_gmvp_m <- (Sigma_m_inv %*% ones) / as.numeric(t(ones) %*% Sigma_m_inv %*% ones)
w_gmvp_m <- as.vector(w_gmvp_m)
names(w_gmvp_m) <- colnames(ret_monthly)
cat("=== GMVP Weights (Monthly Returns) ===\n")
## === GMVP Weights (Monthly Returns) ===
kable(data.frame(
ETF = names(w_gmvp_m),
Weight = round(w_gmvp_m, 6),
Weight_pct = paste0(round(w_gmvp_m * 100, 4), "%")
), col.names = c("ETF", "Weight", "Weight (%)"), align = "lrr")
| 0050 |
0050 |
-0.028541 |
-2.8541% |
| 0056 |
0056 |
0.520687 |
52.0687% |
| 006205 |
006205 |
-0.001783 |
-0.1783% |
| 00646 |
00646 |
0.509637 |
50.9637% |
Comparison: Daily vs Monthly GMVP Weights
kable(data.frame(
ETF = c("0050", "0056", "006205", "00646"),
Daily_w = round(w_gmvp_d, 6),
Monthly_w = round(w_gmvp_m, 6)
), col.names = c("ETF", "Weight (Daily)", "Weight (Monthly)"), align = "lrr")
| 0050 |
0050 |
-0.224131 |
-0.028541 |
| 0056 |
0056 |
0.729873 |
0.520687 |
| 006205 |
006205 |
0.108076 |
-0.001783 |
| 00646 |
00646 |
0.386183 |
0.509637 |
Q3. Tangency Portfolio (Monthly Returns, Rf = 0)
The Tangency Portfolio maximizes the Sharpe Ratio.
With \(R_f = 0\):
\[w_{TP} = \frac{\Sigma^{-1}
\mu}{\mathbf{1}^\top \Sigma^{-1} \mu}\]
where \(\mu\) is the vector of mean
returns (since \(R_f = 0\), excess
returns equal raw returns).
mu_m <- colMeans(ret_monthly)
num_tp <- Sigma_m_inv %*% mu_m
w_tp <- num_tp / as.numeric(t(ones) %*% num_tp)
w_tp <- as.vector(w_tp)
names(w_tp) <- colnames(ret_monthly)
cat("=== Tangency Portfolio Weights (Monthly, Rf = 0) ===\n")
## === Tangency Portfolio Weights (Monthly, Rf = 0) ===
kable(data.frame(
ETF = names(w_tp),
Weight = round(w_tp, 6),
Weight_pct = paste0(round(w_tp * 100, 4), "%")
), col.names = c("ETF", "Weight", "Weight (%)"), align = "lrr")
| 0050 |
0050 |
1.278969 |
127.8969% |
| 0056 |
0056 |
-0.087371 |
-8.7371% |
| 006205 |
006205 |
-0.896758 |
-89.6758% |
| 00646 |
00646 |
0.705159 |
70.5159% |
Summary: All Three Portfolios
kable(data.frame(
Portfolio = c("GMVP (Daily)", "GMVP (Monthly)", "Tangency (Monthly)"),
w_0050 = round(c(w_gmvp_d["0050"], w_gmvp_m["0050"], w_tp["0050"]), 4),
w_0056 = round(c(w_gmvp_d["0056"], w_gmvp_m["0056"], w_tp["0056"]), 4),
w_006205 = round(c(w_gmvp_d["006205"], w_gmvp_m["006205"], w_tp["006205"]), 4),
w_00646 = round(c(w_gmvp_d["00646"], w_gmvp_m["00646"], w_tp["00646"]), 4),
Return_pct = c(paste0(round(gmvp_d_ret * 100, 4), "%"),
paste0(round(gmvp_m_ret * 100, 4), "%"),
paste0(round(tp_ret * 100, 4), "%")),
StdDev_pct = c(paste0(round(gmvp_d_sd * 100, 4), "%"),
paste0(round(gmvp_m_sd * 100, 4), "%"),
paste0(round(tp_sd * 100, 4), "%")),
Sharpe = c(round(gmvp_d_ret / gmvp_d_sd, 4),
round(gmvp_m_ret / gmvp_m_sd, 4),
round(tp_sr, 4))
),
col.names = c("Portfolio", "w(0050)", "w(0056)", "w(006205)", "w(00646)",
"Return", "Std Dev", "Sharpe"),
align = "lrrrrrrr",
caption = "Summary of GMVP and Tangency Portfolio Results")
Summary of GMVP and Tangency Portfolio Results
| GMVP (Daily) |
-0.2241 |
0.7299 |
0.1081 |
0.3862 |
0.0226% |
0.5922% |
0.0382 |
| GMVP (Monthly) |
-0.0285 |
0.5207 |
-0.0018 |
0.5096 |
0.5304% |
2.5069% |
0.2116 |
| Tangency (Monthly) |
1.2790 |
-0.0874 |
-0.8968 |
0.7052 |
1.8719% |
4.7094% |
0.3975 |