tickers <- c("SPY","QQQ","EEM","IWM","EFA","TLT","IYR","GLD")
getSymbols(tickers, from = "2010-01-01", to = "2025-04-30")
## [1] "SPY" "QQQ" "EEM" "IWM" "EFA" "TLT" "IYR" "GLD"
Extract adjusted prices:
prices <- map(tickers, ~ Ad(get(.x))) %>%
reduce(merge)
colnames(prices) <- tickers
monthly_prices <- to.monthly(prices, indexAt = "lastof", OHLC = FALSE)
monthly_returns <- data.frame(
Date = index(monthly_prices),
coredata(monthly_prices)
) %>%
arrange(Date) %>%
mutate(across(-Date, ~ . / lag(.) - 1)) %>%
drop_na()
head(monthly_returns)
## Date SPY QQQ EEM IWM EFA
## 1 2010-02-28 0.03119480 0.04603899 0.017763847 0.04475121 0.002667385
## 2 2010-03-31 0.06088004 0.07710885 0.081108933 0.08230709 0.063854086
## 3 2010-04-30 0.01546980 0.02242545 -0.001661812 0.05678469 -0.028045778
## 4 2010-05-31 -0.07945465 -0.07392399 -0.093936195 -0.07536629 -0.111927677
## 5 2010-06-30 -0.05174074 -0.05975649 -0.013986011 -0.07743434 -0.020619474
## 6 2010-07-31 0.06830026 0.07258228 0.109324702 0.06730928 0.116103906
## TLT IYR GLD
## 1 -0.003423707 0.05457010 0.032748219
## 2 -0.020574803 0.09748474 -0.004386396
## 3 0.033219065 0.06388149 0.058834363
## 4 0.051083188 -0.05683540 0.030513147
## 5 0.057978961 -0.04670129 0.023553189
## 6 -0.009463873 0.09404813 -0.050871157
url <- "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip"
temp <- tempfile()
download.file(url, temp)
ff_raw <- read_csv(unz(temp, "F-F_Research_Data_Factors.csv"), skip = 3)
## New names:
## • `` -> `...1`
## Warning: One or more parsing issues, call `problems()` on your data frame for details,
## e.g.:
## dat <- vroom(...)
## problems(dat)
## Rows: 1298 Columns: 5
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): ...1
## dbl (4): Mkt-RF, SMB, HML, RF
##
## ℹ 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.
Clean + align to END OF MONTH:
ff <- ff_raw %>%
rename(Date = ...1) %>%
filter(!is.na(Date)) %>%
filter(Date >= 201001 & Date <= 202504) %>%
mutate(across(-Date, as.numeric)) %>%
mutate(across(-Date, ~ . / 100)) %>%
mutate(Date = ymd(paste0(Date, "01"))) %>%
mutate(Date = ceiling_date(Date, "month") - days(1)) # 🔥 CRITICAL
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `Date = ymd(paste0(Date, "01"))`.
## Caused by warning:
## ! 13 failed to parse.
head(ff)
## # A tibble: 6 × 5
## Date `Mkt-RF` SMB HML RF
## <date> <dbl> <dbl> <dbl> <dbl>
## 1 2010-01-31 -0.0335 0.0043 0.0033 0
## 2 2010-02-28 0.0339 0.0118 0.0318 0
## 3 2010-03-31 0.063 0.0146 0.0219 0.0001
## 4 2010-04-30 0.0199 0.0484 0.0296 0.0001
## 5 2010-05-31 -0.079 0.0013 -0.0248 0.0001
## 6 2010-06-30 -0.0556 -0.0179 -0.0473 0.0001
data_all <- left_join(monthly_returns, ff, by = "Date") %>%
drop_na()
head(data_all)
## Date SPY QQQ EEM IWM EFA
## 1 2010-02-28 0.03119480 0.04603899 0.017763847 0.04475121 0.002667385
## 2 2010-03-31 0.06088004 0.07710885 0.081108933 0.08230709 0.063854086
## 3 2010-04-30 0.01546980 0.02242545 -0.001661812 0.05678469 -0.028045778
## 4 2010-05-31 -0.07945465 -0.07392399 -0.093936195 -0.07536629 -0.111927677
## 5 2010-06-30 -0.05174074 -0.05975649 -0.013986011 -0.07743434 -0.020619474
## 6 2010-07-31 0.06830026 0.07258228 0.109324702 0.06730928 0.116103906
## TLT IYR GLD Mkt-RF SMB HML RF
## 1 -0.003423707 0.05457010 0.032748219 0.0339 0.0118 0.0318 0e+00
## 2 -0.020574803 0.09748474 -0.004386396 0.0630 0.0146 0.0219 1e-04
## 3 0.033219065 0.06388149 0.058834363 0.0199 0.0484 0.0296 1e-04
## 4 0.051083188 -0.05683540 0.030513147 -0.0790 0.0013 -0.0248 1e-04
## 5 0.057978961 -0.04670129 0.023553189 -0.0556 -0.0179 -0.0473 1e-04
## 6 -0.009463873 0.09404813 -0.050871157 0.0692 0.0022 -0.0050 1e-04
window_data <- data_all %>%
filter(Date >= as.Date("2020-03-31") & Date <= as.Date("2025-02-28"))
nrow(window_data) # should be ~60
## [1] 62
R <- as.matrix(window_data[, tickers])
Sigma <- cov(R)
one <- rep(1, ncol(R))
w_mvp <- solve(Sigma) %*% one / as.numeric(t(one) %*% solve(Sigma) %*% one)
w_mvp
## [,1]
## SPY 1.50933050
## QQQ -0.72082244
## EEM 0.08842939
## IWM 0.01256008
## EFA -0.20232253
## TLT 0.52190289
## IYR -0.52336094
## GLD 0.31428304
betas <- map_dfr(tickers, function(tk) {
df <- window_data %>%
select(all_of(tk), `Mkt-RF`, SMB, HML) %>%
drop_na()
model <- lm(df[[1]] ~ ., data = df[, -1])
tibble(asset = tk, t(coef(model)[-1]))
})
betas
## # A tibble: 8 × 2
## asset `t(coef(model)[-1])`[,1] [,2] [,3]
## <chr> <dbl> <dbl> <dbl>
## 1 SPY 0.890 -0.151 -0.118
## 2 QQQ 1.01 -0.0883 -0.497
## 3 EEM 0.654 0.0176 0.0532
## 4 IWM 0.963 0.734 0.0644
## 5 EFA 0.778 -0.179 0.0604
## 6 TLT 0.316 -0.0511 -0.286
## 7 IYR 0.883 0.0433 0.0461
## 8 GLD 0.250 -0.288 0.0365
Sigma_f <- cov(window_data %>% select(`Mkt-RF`, SMB, HML))
B <- as.matrix(betas[, -1])
Sigma_ff <- B %*% Sigma_f %*% t(B)
# Regularization (prevents singular matrix)
Sigma_ff <- Sigma_ff + diag(1e-6, nrow(Sigma_ff))
one <- rep(1, ncol(Sigma_ff))
w_mvp_ff <- solve(Sigma_ff) %*% one / as.numeric(t(one) %*% solve(Sigma_ff) %*% one)
w_mvp_ff
## [,1]
## [1,] -0.2301029
## [2,] -0.3050903
## [3,] 0.2070702
## [4,] 0.1900879
## [5,] -0.1103451
## [6,] 0.7426298
## [7,] -0.1263123
## [8,] 0.6320626
march <- data_all %>% filter(Date == as.Date("2025-03-31"))
r_march <- as.numeric(march[, tickers])
ret_capm <- sum(w_mvp * r_march)
ret_ff <- sum(w_mvp_ff * r_march)
ret_capm
## [1] 0.005993276
ret_ff
## [1] 0.0787977
window_april <- data_all %>%
filter(Date >= as.Date("2020-04-30") & Date <= as.Date("2025-03-31"))
R2 <- as.matrix(window_april[, tickers])
Sigma2 <- cov(R2)
one <- rep(1, ncol(R2))
w_mvp2 <- solve(Sigma2) %*% one / as.numeric(t(one) %*% solve(Sigma2) %*% one)
april <- data_all %>% filter(Date == as.Date("2025-04-30"))
r_april <- as.numeric(april[, tickers])
ret_april <- sum(w_mvp2 * r_april)
ret_april
## [1] -0.00165042
[cite_start]This section provides the solutions to the assigned problems from Investments (12th ed.)[cite: 16, 23].
Question: Calculating the risk premium and the real rate of return. Solution: 1. Real Rate of Return: Using the Fisher equation: \[r \approx R - i\] or the exact formula: \[1 + r = \frac{1 + R}{1 + i}\] Where \(R\) is the nominal rate and \(i\) is inflation. 2. Risk Premium: This is the difference between the expected HPR and the risk-free rate (\(E(r) - r_f\)).
Solution: * 21: This typically asks for the composition of the risky portfolio and the client’s overall portfolio. Calculate the proportion \(y\) using: \[y = \frac{E(r_p) - r_f}{A \sigma_p^2}\] * 22: Calculate the new expected return and standard deviation based on the shift in the Capital Allocation Line (CAL).
Solution: * 11: Requires finding the weights in a two-asset portfolio that minimize variance (the Minimum Variance Portfolio). * 12: Calculate the expected return and standard deviation of a portfolio with a given correlation coefficient (\(\rho\)). Note that diversification benefits are maximized when \(\rho = -1\).
Question: Comparing the Single-Index Model (SIM) to the Markowitz Model. Solution: * The number of estimates required for the Markowitz model is \(n(n+3)/2\). * The SIM simplifies this significantly by only requiring \(3n + 2\) estimates. Calculate the difference for the number of assets given in the problem.
Solution: Usually involves identifying systematic vs. firm-specific risk. * Systematic Risk: \(\beta_i^2 \sigma_m^2\) * Firm-specific (Residual) Risk: \(\sigma^2(e_i)\)