# ── Packages ──────────────────────────────────────────────
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 4.0.2 ✔ tibble 3.2.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.1
## ✔ purrr 1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(lubridate)
# ── Load Data ─────────────────────────────────────────────
prices <- read_csv("myetf4.csv") %>%
rename(date = Index,
etf0050 = tw0050,
etf0056 = tw0056,
etf006205 = tw006205,
etf00646 = tw00646) %>%
mutate(date = as.Date(date))
## Rows: 751 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): Index
## dbl (4): tw0050, tw0056, tw006205, tw00646
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# ═══════════════════════════════════════════════════════════
# Q1: GMVP — DAILY RETURNS
# ═══════════════════════════════════════════════════════════
daily_ret <- prices %>%
filter(date >= as.Date("2015-12-14"),
date <= as.Date("2018-12-28")) %>%
arrange(date) %>%
mutate(
r0050 = log(etf0050 / lag(etf0050)),
r0056 = log(etf0056 / lag(etf0056)),
r006205 = log(etf006205 / lag(etf006205)),
r00646 = log(etf00646 / lag(etf00646))
) %>%
drop_na()
R_daily <- as.matrix(daily_ret[, c("r0050","r0056","r006205","r00646")])
cov_d <- cov(R_daily)
mu_d <- colMeans(R_daily)
ones <- rep(1, 4)
# Analytical GMVP: w = Sigma^-1 * 1 / (1' * Sigma^-1 * 1)
w_gmvp_d <- solve(cov_d) %*% ones
w_gmvp_d <- w_gmvp_d / sum(w_gmvp_d)
names(w_gmvp_d) <- c("0050","0056","006205","00646")
ret_gmvp_d <- as.numeric(t(w_gmvp_d) %*% mu_d)
sd_gmvp_d <- sqrt(as.numeric(t(w_gmvp_d) %*% cov_d %*% w_gmvp_d))
cat("=== Q1: GMVP — Daily Returns ===\n")
## === Q1: GMVP — Daily Returns ===
cat("Weights:\n"); print(round(w_gmvp_d, 6))
## Weights:
## [,1]
## r0050 -0.224131
## r0056 0.729873
## r006205 0.108076
## r00646 0.386183
## attr(,"names")
## [1] "0050" "0056" "006205" "00646"
cat("Daily Return :", round(ret_gmvp_d, 6), "\n")
## Daily Return : 0.000226
cat("Daily Std Dev :", round(sd_gmvp_d, 6), "\n")
## Daily Std Dev : 0.005922
# ═══════════════════════════════════════════════════════════
# Q2: GMVP — MONTHLY RETURNS
# ═══════════════════════════════════════════════════════════
monthly_ret <- prices %>%
filter(date >= as.Date("2015-12-14"),
date <= as.Date("2018-12-28")) %>%
mutate(ym = floor_date(date, "month")) %>%
group_by(ym) %>%
slice_tail(n = 1) %>% # last trading day of each month
ungroup() %>%
arrange(ym) %>%
mutate(
r0050 = log(etf0050 / lag(etf0050)),
r0056 = log(etf0056 / lag(etf0056)),
r006205 = log(etf006205 / lag(etf006205)),
r00646 = log(etf00646 / lag(etf00646))
) %>%
drop_na()
R_monthly <- as.matrix(monthly_ret[, c("r0050","r0056","r006205","r00646")])
cov_m <- cov(R_monthly)
mu_m <- colMeans(R_monthly)
w_gmvp_m <- solve(cov_m) %*% ones
w_gmvp_m <- w_gmvp_m / sum(w_gmvp_m)
names(w_gmvp_m) <- c("0050","0056","006205","00646")
ret_gmvp_m <- as.numeric(t(w_gmvp_m) %*% mu_m)
sd_gmvp_m <- sqrt(as.numeric(t(w_gmvp_m) %*% cov_m %*% w_gmvp_m))
cat("\n=== Q2: GMVP — Monthly Returns ===\n")
##
## === Q2: GMVP — Monthly Returns ===
cat("Weights:\n"); print(round(w_gmvp_m, 6))
## Weights:
## [,1]
## r0050 -0.028541
## r0056 0.520687
## r006205 -0.001783
## r00646 0.509637
## attr(,"names")
## [1] "0050" "0056" "006205" "00646"
cat("Monthly Return :", round(ret_gmvp_m, 6), "\n")
## Monthly Return : 0.005304
cat("Monthly Std Dev :", round(sd_gmvp_m, 6), "\n")
## Monthly Std Dev : 0.025069
# ═══════════════════════════════════════════════════════════
# Q3: TANGENCY PORTFOLIO — Monthly Returns, Rf = 0
# ═══════════════════════════════════════════════════════════
Rf <- 0
w_tp <- solve(cov_m) %*% (mu_m - Rf)
w_tp <- w_tp / sum(w_tp)
names(w_tp) <- c("0050","0056","006205","00646")
ret_tp <- as.numeric(t(w_tp) %*% mu_m)
sd_tp <- sqrt(as.numeric(t(w_tp) %*% cov_m %*% w_tp))
sr_tp <- (ret_tp - Rf) / sd_tp
cat("\n=== Q3: Tangency Portfolio — Monthly, Rf=0 ===\n")
##
## === Q3: Tangency Portfolio — Monthly, Rf=0 ===
cat("Weights:\n"); print(round(w_tp, 6))
## Weights:
## [,1]
## r0050 1.278969
## r0056 -0.087371
## r006205 -0.896758
## r00646 0.705159
## attr(,"names")
## [1] "0050" "0056" "006205" "00646"
cat("Monthly Return :", round(ret_tp, 6), "\n")
## Monthly Return : 0.018719
cat("Monthly Std Dev :", round(sd_tp, 6), "\n")
## Monthly Std Dev : 0.047094
cat("Sharpe Ratio :", round(sr_tp, 6), "\n")
## Sharpe Ratio : 0.397475
# ── Summary Table ─────────────────────────────────────────
cat("\n=== Summary: All Portfolios ===\n")
##
## === Summary: All Portfolios ===
summary_tbl <- data.frame(
Portfolio = c("GMVP (Daily)","GMVP (Monthly)","Tangency (Monthly)"),
w_0050 = round(c(w_gmvp_d[1], w_gmvp_m[1], w_tp[1]), 4),
w_0056 = round(c(w_gmvp_d[2], w_gmvp_m[2], w_tp[2]), 4),
w_006205 = round(c(w_gmvp_d[3], w_gmvp_m[3], w_tp[3]), 4),
w_00646 = round(c(w_gmvp_d[4], w_gmvp_m[4], w_tp[4]), 4),
Return = round(c(ret_gmvp_d, ret_gmvp_m, ret_tp), 6),
Std_Dev = round(c(sd_gmvp_d, sd_gmvp_m, sd_tp), 6)
)
print(summary_tbl)
## Portfolio w_0050 w_0056 w_006205 w_00646 Return Std_Dev
## 1 GMVP (Daily) -0.2241 0.7299 0.1081 0.3862 0.000226 0.005922
## 2 GMVP (Monthly) -0.0285 0.5207 -0.0018 0.5096 0.005304 0.025069
## 3 Tangency (Monthly) 1.2790 -0.0874 -0.8968 0.7052 0.018719 0.047094