# Load Required Libraries
library(xts)
library(quantmod)
library(zoo)
library(quadprog)
library(DEoptim)
library(copula)
# Create the Data Files
start.date <- "2014-04-01"
end.date <- "2019-04-01"
tickers <- c("NVDA","MSFT","JPM","BAC","AMT","PLD")
for (tk in tickers) {
getSymbols(tk, from = start.date, to = end.date, src = "yahoo", auto.assign = TRUE)
}
data.NVDA <- NVDA; names(data.NVDA) <- paste0("NVDA.", c("Open","High","Low","Close","Volume","Adjusted"))
data.MSFT <- MSFT; names(data.MSFT) <- paste0("MSFT.", c("Open","High","Low","Close","Volume","Adjusted"))
data.JPM <- JPM; names(data.JPM) <- paste0("JPM.", c("Open","High","Low","Close","Volume","Adjusted"))
data.BAC <- BAC; names(data.BAC) <- paste0("BAC.", c("Open","High","Low","Close","Volume","Adjusted"))
data.AMT <- AMT; names(data.AMT) <- paste0("AMT.", c("Open","High","Low","Close","Volume","Adjusted"))
data.PLD <- PLD; names(data.PLD) <- paste0("PLD.", c("Open","High","Low","Close","Volume","Adjusted"))
# Subset to Fifth Year
hw4.start <- "2018-04-01"
hw4.end <- "2019-03-31"
NVDA.hw4 <- window(data.NVDA, start = hw4.start, end = hw4.end)
MSFT.hw4 <- window(data.MSFT, start = hw4.start, end = hw4.end)
JPM.hw4 <- window(data.JPM, start = hw4.start, end = hw4.end)
BAC.hw4 <- window(data.BAC, start = hw4.start, end = hw4.end)
AMT.hw4 <- window(data.AMT, start = hw4.start, end = hw4.end)
PLD.hw4 <- window(data.PLD, start = hw4.start, end = hw4.end)
# Merge adjusted prices and compute daily log returns
prices <- merge(NVDA.hw4$NVDA.Adjusted,
MSFT.hw4$MSFT.Adjusted,
JPM.hw4$JPM.Adjusted,
BAC.hw4$BAC.Adjusted,
AMT.hw4$AMT.Adjusted,
PLD.hw4$PLD.Adjusted,
join = "inner")
prices <- na.omit(prices)
colnames(prices) <- tickers
ret <- diff(log(prices))
ret <- na.omit(ret)
ret.mat <- as.matrix(ret)
mu.vec <- colMeans(ret.mat)
vcov.mat <- cov(ret.mat)
n <- ncol(ret.mat)
cat("Dimensions of return matrix:", dim(ret.mat), "\n")
## Dimensions of return matrix: 250 6
cat("Date range:", as.character(index(ret)[1]),
"to", as.character(index(ret)[nrow(ret)]), "\n")
## Date range: 2018-04-03 to 2019-03-29
# Part A - Section 1a: Variance-Covariance Matrix
cat("=== Variance-Covariance Matrix ===\n")
## === Variance-Covariance Matrix ===
print(round(vcov.mat, 8))
## NVDA MSFT JPM BAC AMT PLD
## NVDA 0.00107213 0.00030116 0.00014893 0.00017694 0.00002460 0.00006576
## MSFT 0.00030116 0.00026564 0.00010345 0.00012199 0.00004033 0.00007461
## JPM 0.00014893 0.00010345 0.00016103 0.00016965 0.00001410 0.00003221
## BAC 0.00017694 0.00012199 0.00016965 0.00022979 0.00000731 0.00003456
## AMT 0.00002460 0.00004033 0.00001410 0.00000731 0.00012975 0.00008136
## PLD 0.00006576 0.00007461 0.00003221 0.00003456 0.00008136 0.00015975
cat("\n=== Annualized Std Deviations ===\n")
##
## === Annualized Std Deviations ===
ann.sd <- sqrt(diag(vcov.mat) * 252) * 100
print(round(ann.sd, 2))
## NVDA MSFT JPM BAC AMT PLD
## 51.98 25.87 20.14 24.06 18.08 20.06
cat("\n=== Correlation Matrix ===\n")
##
## === Correlation Matrix ===
print(round(cor(ret.mat), 4))
## NVDA MSFT JPM BAC AMT PLD
## NVDA 1.0000 0.5643 0.3584 0.3565 0.0660 0.1589
## MSFT 0.5643 1.0000 0.5002 0.4938 0.2172 0.3622
## JPM 0.3584 0.5002 1.0000 0.8819 0.0975 0.2008
## BAC 0.3565 0.4938 0.8819 1.0000 0.0423 0.1804
## AMT 0.0660 0.2172 0.0975 0.0423 1.0000 0.5651
## PLD 0.1589 0.3622 0.2008 0.1804 0.5651 1.0000
# Part A - Section 1b: Minimum Variance Portfolio
Dmat <- 2 * vcov.mat
dvec <- rep(0, n)
Amat <- cbind(rep(1, n), diag(n))
bvec <- c(1, rep(0, n))
mvp.sol <- solve.QP(Dmat, dvec, Amat, bvec, meq = 1)
mvp.w <- mvp.sol$solution
mvp.w <- mvp.w / sum(mvp.w)
names(mvp.w) <- tickers
mvp.ret <- sum(mvp.w * mu.vec) * 252
mvp.sd <- sqrt(t(mvp.w) %*% vcov.mat %*% mvp.w) * sqrt(252)
cat("=== Minimum Variance Portfolio ===\n")
## === Minimum Variance Portfolio ===
cat("Weights:\n"); print(round(mvp.w, 4))
## Weights:
## NVDA MSFT JPM BAC AMT PLD
## 0.0000 0.0232 0.3826 0.0000 0.4283 0.1658
cat("Annualized Return:", round(mvp.ret * 100, 2), "%\n")
## Annualized Return: 16.93 %
cat("Annualized Std Dev:", round(as.numeric(mvp.sd) * 100, 2), "%\n")
## Annualized Std Dev: 13.78 %
# Part A - Section 1c: Tangency Portfolio
rf.daily <- 0.02 / 252
excess <- mu.vec - rf.daily
vcov.inv <- solve(vcov.mat)
z <- vcov.inv %*% excess
tan.w <- pmax(z, 0)
tan.w <- tan.w / sum(tan.w)
names(tan.w) <- tickers
tan.ret <- sum(tan.w * mu.vec) * 252
tan.sd <- sqrt(t(tan.w) %*% vcov.mat %*% tan.w) * sqrt(252)
tan.sr <- (tan.ret - 0.02) / as.numeric(tan.sd)
cat("=== Tangency Portfolio ===\n")
## === Tangency Portfolio ===
cat("Weights:\n"); print(round(tan.w, 4))
## Weights:
## [,1]
## NVDA 0.0000
## MSFT 0.4441
## JPM 0.0000
## BAC 0.0219
## AMT 0.5340
## PLD 0.0000
## attr(,"names")
## [1] "NVDA" "MSFT" "JPM" "BAC" "AMT" "PLD"
cat("Annualized Return:", round(tan.ret * 100, 2), "%\n")
## Annualized Return: 31.63 %
cat("Annualized Std Dev:", round(as.numeric(tan.sd) * 100, 2), "%\n")
## Annualized Std Dev: 16.74 %
cat("Sharpe Ratio:", round(tan.sr, 4), "\n")
## Sharpe Ratio: 1.77
# Part A - Section 1d: Efficient Frontier
n.points <- 100
ret.range <- seq(min(mu.vec) * 252, max(mu.vec) * 252, length.out = n.points)
front.sd <- rep(NA, n.points)
for (i in seq_along(ret.range)) {
target <- ret.range[i] / 252
Amat.f <- cbind(rep(1, n), mu.vec, diag(n))
bvec.f <- c(1, target, rep(0, n))
tryCatch({
sol <- solve.QP(Dmat, dvec, Amat.f, bvec.f, meq = 2)
w.f <- sol$solution
front.sd[i] <- sqrt(t(w.f) %*% vcov.mat %*% w.f) * sqrt(252)
}, error = function(e) NULL)
}
ind.ret <- mu.vec * 252
ind.sd <- sqrt(diag(vcov.mat)) * sqrt(252)
plot(front.sd * 100, ret.range * 100,
type = "l", col = "steelblue", lwd = 2.5,
xlim = c(0, max(ind.sd * 100) * 1.15),
ylim = c(min(c(ret.range, ind.ret)) * 100 - 5,
max(c(ret.range, ind.ret)) * 100 + 5),
xlab = "Annualized Std Dev (%)",
ylab = "Annualized Return (%)",
main = "Mean-Variance Efficient Frontier\nFifth Year (April 2018 - March 2019)",
cex.lab = 1.0)
points(ind.sd * 100, ind.ret * 100, pch = 19, col = "gray40", cex = 1.3)
text(ind.sd * 100, ind.ret * 100, labels = tickers,
pos = 4, cex = 0.75, col = "gray30")
points(as.numeric(mvp.sd) * 100, mvp.ret * 100,
pch = 18, col = "darkgreen", cex = 2.5)
text(as.numeric(mvp.sd) * 100, mvp.ret * 100,
labels = "MVP", pos = 4, cex = 0.85, col = "darkgreen", font = 2)
points(as.numeric(tan.sd) * 100, tan.ret * 100,
pch = 17, col = "firebrick", cex = 2.5)
text(as.numeric(tan.sd) * 100, tan.ret * 100,
labels = "TP", pos = 4, cex = 0.85, col = "firebrick", font = 2)
cml.sd <- seq(0, max(ind.sd * 100) * 1.15, length.out = 100)
cml.ret <- 2 + tan.sr * cml.sd
lines(cml.sd, cml.ret, lty = 2, col = "firebrick", lwd = 1.5)
legend("topleft",
legend = c("Efficient Frontier", "Capital Market Line",
"Individual Stocks", "Min Variance Portfolio",
"Tangency Portfolio"),
col = c("steelblue","firebrick","gray40","darkgreen","firebrick"),
lty = c(1, 2, NA, NA, NA),
pch = c(NA, NA, 19, 18, 17),
pt.cex = c(NA, NA, 1.3, 2, 2),
lwd = c(2.5, 1.5, NA, NA, NA),
bty = "n",
cex = 0.8)

# Part B - Section 1: Global Minimum Variance Portfolio
gmv.sol <- solve.QP(Dmat, dvec, Amat, bvec, meq = 1)
gmv.w <- gmv.sol$solution
gmv.w <- gmv.w / sum(gmv.w)
names(gmv.w) <- tickers
gmv.ret <- sum(gmv.w * mu.vec) * 252
gmv.sd <- sqrt(t(gmv.w) %*% vcov.mat %*% gmv.w) * sqrt(252)
cat("=== Global Minimum Variance Portfolio ===\n")
## === Global Minimum Variance Portfolio ===
cat("Weights:\n"); print(round(gmv.w, 4))
## Weights:
## NVDA MSFT JPM BAC AMT PLD
## 0.0000 0.0232 0.3826 0.0000 0.4283 0.1658
cat("Annualized Return:", round(gmv.ret * 100, 2), "%\n")
## Annualized Return: 16.93 %
cat("Annualized Std Dev:", round(as.numeric(gmv.sd) * 100, 2), "%\n")
## Annualized Std Dev: 13.78 %
# Part B - Section 2: Most Diversified Portfolio
sigma.vec <- sqrt(diag(vcov.mat))
neg.dr <- function(w) {
w <- abs(w) / sum(abs(w))
port.vol <- sqrt(t(w) %*% vcov.mat %*% w)
dr <- sum(w * sigma.vec) / port.vol
return(-as.numeric(dr))
}
lower <- rep(0, n)
upper <- rep(1, n)
set.seed(123)
mdp.sol <- DEoptim(neg.dr, lower, upper,
control = DEoptim.control(itermax = 5000,
NP = 200,
trace = FALSE,
CR = 0.9,
F = 0.8))
mdp.w <- mdp.sol$optim$bestmem
mdp.w <- mdp.w / sum(mdp.w)
names(mdp.w) <- tickers
if (max(mdp.w) > 0.95) warning("MDP may be degenerate")
mdp.ret <- sum(mdp.w * mu.vec) * 252
mdp.sd <- sqrt(t(mdp.w) %*% vcov.mat %*% mdp.w) * sqrt(252)
cat("=== Most Diversified Portfolio ===\n")
## === Most Diversified Portfolio ===
cat("Weights:\n"); print(round(mdp.w, 4))
## Weights:
## NVDA MSFT JPM BAC AMT PLD
## 0.1241 0.0037 0.0691 0.2272 0.3941 0.1818
cat("Diversification Ratio:", round(-neg.dr(mdp.w), 4), "\n")
## Diversification Ratio: 1.5549
cat("Annualized Return:", round(mdp.ret * 100, 2), "%\n")
## Annualized Return: 13.17 %
cat("Annualized Std Dev:", round(as.numeric(mdp.sd) * 100, 2), "%\n")
## Annualized Std Dev: 15.55 %
# Part B - Section 3: Equal Risk Contribution Portfolio
erc.obj <- function(w) {
w <- abs(w) / sum(abs(w))
port.var <- as.numeric(t(w) %*% vcov.mat %*% w)
mrc <- as.numeric(vcov.mat %*% w)
rc <- w * mrc / port.var
return(sum((rc - mean(rc))^2))
}
set.seed(456)
erc.sol <- DEoptim(erc.obj, lower, upper,
control = DEoptim.control(itermax = 5000,
NP = 200,
trace = FALSE,
CR = 0.9,
F = 0.8))
erc.w <- erc.sol$optim$bestmem
erc.w <- erc.w / sum(erc.w)
names(erc.w) <- tickers
if (max(erc.w) > 0.95) warning("ERC may be degenerate")
erc.ret <- sum(erc.w * mu.vec) * 252
erc.sd <- sqrt(t(erc.w) %*% vcov.mat %*% erc.w) * sqrt(252)
port.var <- as.numeric(t(erc.w) %*% vcov.mat %*% erc.w)
mrc <- as.numeric(vcov.mat %*% erc.w)
rc <- erc.w * mrc / port.var
cat("=== Equal Risk Contribution Portfolio ===\n")
## === Equal Risk Contribution Portfolio ===
cat("Weights:\n"); print(round(erc.w, 4))
## Weights:
## NVDA MSFT JPM BAC AMT PLD
## 0.0800 0.1308 0.1763 0.1525 0.2602 0.2002
cat("Risk Contributions (should be ~equal):\n"); print(round(rc, 4))
## Risk Contributions (should be ~equal):
## NVDA MSFT JPM BAC AMT PLD
## 0.1667 0.1667 0.1667 0.1667 0.1667 0.1667
cat("Annualized Return:", round(erc.ret * 100, 2), "%\n")
## Annualized Return: 13.67 %
cat("Annualized Std Dev:", round(as.numeric(erc.sd) * 100, 2), "%\n")
## Annualized Std Dev: 15.48 %
# Part B - Section 4: Minimum Tail-Dependent Portfolio
ret.mat.scaled <- apply(ret.mat, 2, rank) / (nrow(ret.mat) + 1)
tdc.matrix <- matrix(0, n, n)
threshold <- 0.10
for (i in 1:n) {
for (j in 1:n) {
if (i != j) {
u1 <- ret.mat.scaled[, i]
u2 <- ret.mat.scaled[, j]
joint.tail <- sum(u1 <= threshold & u2 <= threshold)
margin.tail <- sum(u2 <= threshold)
tdc.matrix[i, j] <- joint.tail / margin.tail
}
}
}
rownames(tdc.matrix) <- tickers
colnames(tdc.matrix) <- tickers
cat("=== Pairwise Lower Tail Dependence Coefficients ===\n")
## === Pairwise Lower Tail Dependence Coefficients ===
print(round(tdc.matrix, 4))
## NVDA MSFT JPM BAC AMT PLD
## NVDA 0.00 0.48 0.36 0.36 0.20 0.32
## MSFT 0.48 0.00 0.44 0.40 0.28 0.36
## JPM 0.36 0.44 0.00 0.80 0.24 0.28
## BAC 0.36 0.40 0.80 0.00 0.24 0.24
## AMT 0.20 0.28 0.24 0.24 0.00 0.44
## PLD 0.32 0.36 0.28 0.24 0.44 0.00
mtd.obj <- function(w) {
w <- abs(w) / sum(abs(w))
tdc.val <- as.numeric(t(w) %*% tdc.matrix %*% w)
herfindahl <- sum(w^2)
return(tdc.val + 0.5 * herfindahl)
}
upper.mtd <- rep(0.6, n)
set.seed(789)
mtd.sol <- DEoptim(mtd.obj, lower, upper.mtd,
control = DEoptim.control(itermax = 5000,
NP = 200,
trace = FALSE,
CR = 0.9,
F = 0.8))
mtd.w <- mtd.sol$optim$bestmem
mtd.w <- mtd.w / sum(mtd.w)
names(mtd.w) <- tickers
if (max(mtd.w) > 0.95) warning("MTD may be degenerate")
mtd.ret <- sum(mtd.w * mu.vec) * 252
mtd.sd <- sqrt(t(mtd.w) %*% vcov.mat %*% mtd.w) * sqrt(252)
cat("=== Minimum Tail-Dependent Portfolio ===\n")
## === Minimum Tail-Dependent Portfolio ===
cat("Weights:\n"); print(round(mtd.w, 4))
## Weights:
## NVDA MSFT JPM BAC AMT PLD
## 0.3451 0.0000 0.2212 0.0000 0.4336 0.0000
cat("Weighted Avg Tail Dependence:", round(mtd.obj(mtd.w), 4), "\n")
## Weighted Avg Tail Dependence: 0.3389
cat("Annualized Return:", round(mtd.ret * 100, 2), "%\n")
## Annualized Return: 6.75 %
cat("Annualized Std Dev:", round(as.numeric(mtd.sd) * 100, 2), "%\n")
## Annualized Std Dev: 22.04 %
# Part B - Graphical Comparison: Weight Bar Chart
all.weights <- rbind(gmv.w, mdp.w, erc.w, mtd.w)
rownames(all.weights) <- c("GMV","MDP","ERC","MTD")
port.names <- c("GMV","MDP","ERC","MTD")
port.colors <- c("steelblue","firebrick","darkgreen","darkorange")
stock.colors <- c("#4E79A7","#F28E2B","#E15759","#76B7B2","#59A14F","#EDC948")
par(mfrow = c(1,1), mar = c(5, 5, 4, 10), xpd = TRUE)
barplot(t(all.weights),
beside = TRUE,
col = stock.colors,
names.arg = port.names,
xlab = "Portfolio",
ylab = "Weight",
main = "Portfolio Weights by Stock\nFifth Year (April 2018 - March 2019)",
ylim = c(0, max(all.weights) * 1.3),
cex.names = 1.0,
cex.axis = 0.9,
border = "white")
legend("topright",
inset = c(-0.32, 0),
legend = tickers,
fill = stock.colors,
border = "white",
bty = "n",
cex = 0.85,
title = "Stock",
title.adj = 0)

par(xpd = FALSE)
# Part B - Graphical Comparison: Risk-Return Scatter
port.ret.vec <- c(gmv.ret, mdp.ret, erc.ret, mtd.ret) * 100
port.sd.vec <- c(as.numeric(gmv.sd), as.numeric(mdp.sd),
as.numeric(erc.sd), as.numeric(mtd.sd)) * 100
port.shapes <- c(16, 17, 15, 18)
ord <- order(port.sd.vec)
par(mar = c(5, 5, 4, 2))
plot(port.sd.vec, port.ret.vec,
type = "n",
xlim = c(min(port.sd.vec) - 3, max(port.sd.vec) + 3),
ylim = c(min(port.ret.vec) - 4, max(port.ret.vec) + 4),
xlab = "Annualized Std Dev (%)",
ylab = "Annualized Return (%)",
main = "Risk-Return Comparison\nFifth Year (April 2018 - March 2019)",
cex.lab = 1.0, cex.axis = 0.9, las = 1)
abline(h = seq(0, 25, by = 5), lty = 3, col = "gray88")
abline(v = seq(10, 30, by = 5), lty = 3, col = "gray88")
lines(port.sd.vec[ord], port.ret.vec[ord],
col = "gray60", lty = 2, lwd = 1.5)
points(port.sd.vec, port.ret.vec,
pch = port.shapes, col = port.colors, cex = 3)
offsets.x <- c(-0.5, 1.8, 1.8, 0.5)
offsets.y <- c( 1.5, 1.2, -1.8, 1.5)
for (i in 1:4) {
text(port.sd.vec[i] + offsets.x[i],
port.ret.vec[i] + offsets.y[i],
labels = port.names[i],
col = port.colors[i], cex = 0.95, font = 2)
}
legend("topright",
legend = paste0(port.names, " (",
round(port.ret.vec, 1), "% / ",
round(port.sd.vec, 1), "%)"),
col = port.colors,
pch = port.shapes,
pt.cex = 1.5,
bty = "n",
cex = 0.82,
title = "Portfolio (Return / Std Dev)",
title.adj = 0)

# Part B - Graphical Comparison: Pie Charts
par(mfrow = c(2, 2), mar = c(1, 1, 3, 1))
for (i in 1:4) {
w <- all.weights[i, ]
lbl <- ifelse(w > 0.02,
paste0(tickers, "\n", round(w * 100, 1), "%"), "")
pie(w, labels = lbl, col = stock.colors,
main = paste(port.names[i], "Portfolio"),
cex = 0.85, border = "white")
}

par(mfrow = c(1, 1), mar = c(5, 4, 4, 2))