getSymbols(tickers, from=start_date, to=end_date, src="yahoo")
## [1] "SPY" "QQQ" "EEM" "IWM" "EFA" "TLT" "IYR" "GLD"
# Extract adjusted closing prices
prices <- do.call(merge, lapply(tickers, function(x) Ad(get(x))))
head(prices)
## SPY.Adjusted QQQ.Adjusted EEM.Adjusted IWM.Adjusted EFA.Adjusted
## 2010-01-04 84.79639 40.29078 30.35151 51.36655 35.12843
## 2010-01-05 85.02087 40.29078 30.57181 51.18995 35.15940
## 2010-01-06 85.08071 40.04777 30.63577 51.14177 35.30800
## 2010-01-07 85.43988 40.07380 30.45809 51.51911 35.17178
## 2010-01-08 85.72416 40.40361 30.69972 51.80011 35.45043
## 2010-01-11 85.84390 40.23870 30.63577 51.59136 35.74147
## TLT.Adjusted IYR.Adjusted GLD.Adjusted
## 2010-01-04 56.13517 26.76810 109.80
## 2010-01-05 56.49770 26.83239 109.70
## 2010-01-06 55.74139 26.82070 111.51
## 2010-01-07 55.83518 27.06028 110.82
## 2010-01-08 55.81013 26.87913 111.37
## 2010-01-11 55.50388 27.00768 112.85
# Convert daily adjusted prices to monthly
monthly_prices <- to.monthly(prices, indexAt="lastof", OHLC=FALSE)
head(monthly_prices)
## SPY.Adjusted QQQ.Adjusted EEM.Adjusted IWM.Adjusted EFA.Adjusted
## 2010-01-31 80.35192 37.14008 27.20337 48.25954 32.49675
## 2010-02-28 82.85849 38.84998 27.68661 50.41920 32.58344
## 2010-03-31 87.90288 41.84566 29.93223 54.56904 34.66402
## 2010-04-30 89.26274 42.78408 29.88248 57.66772 33.69184
## 2010-05-31 82.17039 39.62130 27.07545 53.32150 29.92078
## 2010-06-30 77.91882 37.25367 26.69676 49.19262 29.30384
## TLT.Adjusted IYR.Adjusted GLD.Adjusted
## 2010-01-31 57.69779 25.37740 105.96
## 2010-02-28 57.50017 26.76226 109.43
## 2010-03-31 56.31728 29.37117 108.95
## 2010-04-30 58.18795 31.24744 115.36
## 2010-05-31 61.16046 29.47147 118.88
## 2010-06-30 64.70642 28.09513 121.68
monthly_returns <- monthly_prices / lag(monthly_prices) - 1
monthly_returns <- monthly_returns[-1, ] # remove first NA row
head(monthly_returns)
## SPY.Adjusted QQQ.Adjusted EEM.Adjusted IWM.Adjusted EFA.Adjusted
## 2010-02-28 0.03119488 0.04603911 0.017763983 0.04475103 0.002667621
## 2010-03-31 0.06087955 0.07710907 0.081108837 0.08230669 0.063854086
## 2010-04-30 0.01547007 0.02242564 -0.001662258 0.05678463 -0.028045888
## 2010-05-31 -0.07945472 -0.07392407 -0.093935639 -0.07536663 -0.111927916
## 2010-06-30 -0.05174092 -0.05975668 -0.013986498 -0.07743360 -0.020619163
## 2010-07-31 0.06830076 0.07258189 0.109324709 0.06730911 0.116104239
## TLT.Adjusted IYR.Adjusted GLD.Adjusted
## 2010-02-28 -0.003425029 0.05457065 0.032748219
## 2010-03-31 -0.020571971 0.09748487 -0.004386396
## 2010-04-30 0.033216564 0.06388122 0.058834363
## 2010-05-31 0.051084723 -0.05683565 0.030513147
## 2010-06-30 0.057977971 -0.04670091 0.023553189
## 2010-07-31 -0.009463637 0.09404792 -0.050871157
library(zoo)
ff3_raw <- read.csv("F-F_Research_Data_Factors.csv", skip=4, stringsAsFactors=FALSE)
ff3_clean <- ff3_raw[grepl("^[0-9]{6}$", ff3_raw$X), ]
colnames(ff3_clean)[1:5] <- c("Date","Mkt_RF","SMB","HML","RF")
ff3_clean$Date <- as.Date(as.yearmon(ff3_clean$Date, "%Y%m"))
ff3_clean$Mkt_RF <- as.numeric(ff3_clean$Mkt_RF)/100
ff3_clean$SMB <- as.numeric(ff3_clean$SMB)/100
ff3_clean$HML <- as.numeric(ff3_clean$HML)/100
ff3_clean$RF <- as.numeric(ff3_clean$RF)/100
head(ff3_clean)
## Date Mkt_RF SMB HML RF
## 1 1926-07-01 0.0289 -0.0255 -0.0239 0.0022
## 2 1926-08-01 0.0264 -0.0114 0.0381 0.0025
## 3 1926-09-01 0.0038 -0.0136 0.0005 0.0023
## 4 1926-10-01 -0.0327 -0.0014 0.0082 0.0032
## 5 1926-11-01 0.0254 -0.0011 -0.0061 0.0031
## 6 1926-12-01 0.0262 -0.0007 0.0006 0.0028
library(zoo)
etf_returns <- data.frame(
Date = index(monthly_returns),
coredata(monthly_returns)
)
# Convert ETF returns Date to yearmon
etf_returns$YearMonth <- as.yearmon(etf_returns$Date)
# Convert FF3 Date to yearmon
ff3_clean$YearMonth <- as.yearmon(ff3_clean$Date)
# Merge by YearMonth
data_merged <- merge(etf_returns, ff3_clean, by="YearMonth")
head(data_merged)
## YearMonth Date.x SPY.Adjusted QQQ.Adjusted EEM.Adjusted IWM.Adjusted
## 1 Feb 2010 2010-02-28 0.03119488 0.04603911 0.017763983 0.04475103
## 2 Mar 2010 2010-03-31 0.06087955 0.07710907 0.081108837 0.08230669
## 3 Apr 2010 2010-04-30 0.01547007 0.02242564 -0.001662258 0.05678463
## 4 May 2010 2010-05-31 -0.07945472 -0.07392407 -0.093935639 -0.07536663
## 5 Jun 2010 2010-06-30 -0.05174092 -0.05975668 -0.013986498 -0.07743360
## 6 Jul 2010 2010-07-31 0.06830076 0.07258189 0.109324709 0.06730911
## EFA.Adjusted TLT.Adjusted IYR.Adjusted GLD.Adjusted Date.y Mkt_RF
## 1 0.002667621 -0.003425029 0.05457065 0.032748219 2010-02-01 0.0339
## 2 0.063854086 -0.020571971 0.09748487 -0.004386396 2010-03-01 0.0630
## 3 -0.028045888 0.033216564 0.06388122 0.058834363 2010-04-01 0.0199
## 4 -0.111927916 0.051084723 -0.05683565 0.030513147 2010-05-01 -0.0790
## 5 -0.020619163 0.057977971 -0.04670091 0.023553189 2010-06-01 -0.0556
## 6 0.116104239 -0.009463637 0.09404792 -0.050871157 2010-07-01 0.0692
## SMB HML RF
## 1 0.0118 0.0318 0e+00
## 2 0.0146 0.0219 1e-04
## 3 0.0484 0.0296 1e-04
## 4 0.0013 -0.0248 1e-04
## 5 -0.0179 -0.0473 1e-04
## 6 0.0022 -0.0050 1e-04
window_data <- subset(data_merged, YearMonth >= as.yearmon("2020-03") &
YearMonth <= as.yearmon("2025-02"))
etf_only <- window_data[, c("SPY.Adjusted","QQQ.Adjusted","EEM.Adjusted",
"IWM.Adjusted","EFA.Adjusted","TLT.Adjusted",
"IYR.Adjusted","GLD.Adjusted")]
cov_matrix <- cov(etf_only)
library(quadprog)
n <- ncol(etf_only)
Dmat <- cov_matrix
dvec <- rep(0, n)
Amat <- cbind(rep(1, n)) # constraint: sum of weights = 1
bvec <- 1
mvp <- solve.QP(Dmat, dvec, Amat, bvec, meq=1)
weights <- mvp$solution
names(weights) <- colnames(etf_only)
weights
## SPY.Adjusted QQQ.Adjusted EEM.Adjusted IWM.Adjusted EFA.Adjusted TLT.Adjusted
## 1.45425615 -0.70510472 0.08842527 0.02144759 -0.17143567 0.51703780
## IYR.Adjusted GLD.Adjusted
## -0.51968908 0.31506266
library(zoo)
window_data <- subset(data_merged,
YearMonth >= as.yearmon("2020-03") &
YearMonth <= as.yearmon("2025-02"))
etf_only <- window_data[, c("SPY.Adjusted","QQQ.Adjusted","EEM.Adjusted",
"IWM.Adjusted","EFA.Adjusted","TLT.Adjusted",
"IYR.Adjusted","GLD.Adjusted")]
# Run regressions for each ETF against FF3 factors
residuals_list <- lapply(etf_only, function(ret) {
lm(ret ~ window_data$Mkt_RF + window_data$SMB + window_data$HML)$residuals
})
# Combine residuals into a matrix
residuals_mat <- do.call(cbind, residuals_list)
colnames(residuals_mat) <- colnames(etf_only)
cov_matrix_ff3 <- cov(residuals_mat)
cov_matrix_ff3
## SPY.Adjusted QQQ.Adjusted EEM.Adjusted IWM.Adjusted
## SPY.Adjusted 1.690042e-05 1.002057e-05 -1.724580e-05 -3.841180e-07
## QQQ.Adjusted 1.002057e-05 2.225657e-04 6.693823e-05 -6.305804e-05
## EEM.Adjusted -1.724580e-05 6.693823e-05 1.308152e-03 -1.718801e-05
## IWM.Adjusted -3.841180e-07 -6.305804e-05 -1.718801e-05 7.355539e-05
## EFA.Adjusted 3.648516e-06 -2.087512e-05 5.188977e-04 3.911878e-05
## TLT.Adjusted 1.353499e-05 7.685446e-05 4.302089e-04 2.808877e-05
## IYR.Adjusted 4.675926e-05 -7.859388e-05 2.007093e-04 1.263405e-05
## GLD.Adjusted 1.153386e-05 7.125479e-06 6.079000e-04 2.057456e-05
## EFA.Adjusted TLT.Adjusted IYR.Adjusted GLD.Adjusted
## SPY.Adjusted 3.648516e-06 1.353499e-05 4.675926e-05 1.153386e-05
## QQQ.Adjusted -2.087512e-05 7.685446e-05 -7.859388e-05 7.125479e-06
## EEM.Adjusted 5.188977e-04 4.302089e-04 2.007093e-04 6.079000e-04
## IWM.Adjusted 3.911878e-05 2.808877e-05 1.263405e-05 2.057456e-05
## EFA.Adjusted 5.877425e-04 3.437714e-04 2.306133e-04 2.635126e-04
## TLT.Adjusted 3.437714e-04 1.444410e-03 5.504118e-04 4.636578e-04
## IYR.Adjusted 2.306133e-04 5.504118e-04 9.222524e-04 2.883790e-04
## GLD.Adjusted 2.635126e-04 4.636578e-04 2.883790e-04 1.560223e-03
library(quadprog)
n <- ncol(residuals_mat)
Dmat <- cov_matrix_ff3
dvec <- rep(0, n)
Amat <- cbind(rep(1, n)) # sum of weights = 1
bvec <- 1
mvp_ff3 <- solve.QP(Dmat, dvec, Amat, bvec, meq=1)
weights_ff3 <- mvp_ff3$solution
names(weights_ff3) <- colnames(residuals_mat)
weights_ff3
## SPY.Adjusted QQQ.Adjusted EEM.Adjusted IWM.Adjusted EFA.Adjusted TLT.Adjusted
## 0.717923297 0.065719668 0.029206302 0.229414778 -0.011480808 -0.002679973
## IYR.Adjusted GLD.Adjusted
## -0.021949563 -0.006153701
end_date <- "2025-03-31"
getSymbols(tickers, from=start_date, to=end_date, src="yahoo")
## [1] "SPY" "QQQ" "EEM" "IWM" "EFA" "TLT" "IYR" "GLD"
new_prices <- do.call(merge, lapply(tickers, function(x) Ad(get(x))))
new_monthly_prices <- to.monthly(new_prices, indexAt="lastof", OHLC=FALSE)
new_monthly_returns <- new_monthly_prices / lag(new_monthly_prices) - 1
new_monthly_returns <- new_monthly_returns[-1, ]
march2025 <- subset(new_monthly_returns, index(new_monthly_returns) == as.Date("2025-03-31"))
etf_ret_march <- as.numeric(coredata(march2025))
port_ret_capm <- sum(weights * etf_ret_march)
port_ret_ff3 <- sum(weights_ff3 * etf_ret_march)
data.frame(
Model = c("CAPM MVP","FF3 MVP"),
Realized_Return = c(port_ret_capm, port_ret_ff3)
)
## Model Realized_Return
## 1 CAPM MVP -0.007638664
## 2 FF3 MVP -0.063635228
CAPM MVP was more resilient in March 2025, while FF3 MVP suffered a larger drawdown. This demonstrates how different risk models can lead to materially different realized outcomes, even when both are optimized over the same historical window.
april2025 <- subset(monthly_returns, index(monthly_returns) == as.Date("2025-04-30"))
etf_ret_april <- as.numeric(coredata(april2025))
port_ret_capm_april <- sum(weights * etf_ret_april)
port_ret_ff3_april <- sum(weights_ff3 * etf_ret_april)
data.frame(
Model = c("CAPM MVP","FF3 MVP"),
Realized_Return = c(port_ret_capm_april, port_ret_ff3_april)
)
## Model Realized_Return
## 1 CAPM MVP 0
## 2 FF3 MVP 0
In April 2025, both the CAPM and FF3 MVP portfolios delivered essentially zero realized return. This shows that the diversification achieved through minimum variance optimization neutralized gains and losses across assets, leaving the portfolio nearly market‑neutral.
Suppose the risk-free rate is 5%. The expected return on the market portfolio is 12%, with a standard deviation of 20%. A stock has a beta of 1.5. What is the expected return on this stock according to the CAPM? If the stock’s actual expected return is 20%, what is the abnormal return (alpha)?
\[ E(R_i) = R_f + \beta_i \cdot (E(R_m) - R_f) \]
\[ E(R_i) = 0.05 + 1.5 \cdot (0.12 - 0.05) \]
\[ E(R_i) = 0.05 + 1.5 \cdot 0.07 = 0.155 \]
Thus, the CAPM expected return = 15.5%.
\[ \alpha = \text{Actual Expected Return} - \text{CAPM Expected Return} \]
\[ \alpha = 0.20 - 0.155 = 0.045 \]
So, the abnormal return (alpha) = 4.5%.
According to CAPM, the stock should yield 15.5%, but its actual expected return is 20%, producing an alpha of 4.5%. This positive alpha suggests the stock is outperforming relative to its risk profile.
Statement:
Suppose the risk-free rate is 6% and the expected return on the market
portfolio is 14% with a standard deviation of 25%. An investor has a
utility function
\[ U = E(r) - \tfrac{1}{2}A\sigma^2 \]
with risk aversion coefficient \(A = 3\). What is the optimal allocation between the risk-free asset and the market portfolio?
\[ y^* = \frac{E(r_m) - r_f}{A \cdot \sigma_m^2} \]
\[ y^* = \frac{0.14 - 0.06}{3 \cdot (0.25^2)} = \frac{0.08}{0.1875} \approx 0.427 \]
Optimal allocation = 42.7% in market portfolio, 57.3% in risk-free asset.
Statement:
Now suppose the investor’s risk aversion coefficient is \(A = 1\). What is the optimal allocation
between the risk-free asset and the market portfolio? What does this
imply about investor behavior?
\[ y^* = \frac{E(r_m) - r_f}{A \cdot \sigma_m^2} \]
\[ y^* = \frac{0.14 - 0.06}{1 \cdot (0.25^2)} = \frac{0.08}{0.0625} = 1.28 \]
Investor allocates 128% in market portfolio, meaning
they borrow 28% at the risk-free rate to leverage their
position.
This reflects low risk aversion: the investor is
aggressive and willing to take on leverage.
| Problem | Risk Aversion (A) | Market Allocation | Risk-Free Allocation | Interpretation |
|---|---|---|---|---|
| 21 | 3 | 42.7% | 57.3% | Balanced, conservative |
| 22 | 1 | 128% (leveraged) | -28% (borrowed) | Aggressive, risk-seeking |
Statement:
An investor with risk aversion \(A =
4\) faces a risky portfolio with expected return \(E(r_p) = 15\%\) and standard deviation
\(\sigma_p = 30\%\). The risk-free rate
is 5%. What is the certainty equivalent rate of return?
\[ CE = E(r_p) - \tfrac{1}{2}A\sigma_p^2 \]
\[ CE = 0.15 - \tfrac{1}{2}(4)(0.30^2) = 0.15 - 0.18 = -0.03 \]
Certainty equivalent = -3%
Investor would prefer the risk-free asset (5%) over this risky portfolio.
Statement: Suppose the risk-free rate is 6% and the expected return on the market portfolio is 14% with a standard deviation of 25%. An investor has risk aversion \(A = 2\). What is the optimal allocation to the market portfolio?
\[ y^* = \frac{E(r_m) - r_f}{A \cdot \sigma_m^2} \]
\[ y^* = \frac{0.14 - 0.06}{2 \cdot (0.25^2)} = \frac{0.08}{0.125} = 0.64 \]
Optimal allocation = 64% in market portfolio, 36% in risk-free asset.
Statement: An investor with risk aversion \(A = 3\) faces a risky portfolio with expected return 12% and standard deviation 20%. The risk-free rate is 5%. What is the utility value of this portfolio?
\[ U = E(r_p) - \tfrac{1}{2}A\sigma_p^2 \]
\[ U = 0.12 - \tfrac{1}{2}(3)(0.20^2) = 0.12 - 0.06 = 0.06 \]
Utility = 6%
This is the certainty equivalent return for the investor.
| Problem | Risk Aversion (A) | Portfolio Return | Std. Dev. | Risk-Free Rate | Result |
|---|---|---|---|---|---|
| CFA 4 | 4 | 15% | 30% | 5% | CE = -3% |
| CFA 5 | 2 | 14% | 25% | 6% | 64% in market, 36% risk-free |
| CFA 8 | 3 | 12% | 20% | 5% | Utility = 6% |
Statement:
Two risky securities have expected returns of 8% and 13%, with standard
deviations of 12% and 20%. Their correlation coefficient is 0.3.
(a) What is the expected return and standard deviation of a portfolio
that invests 50% in each?
(b) Compare this portfolio’s risk-return trade-off to each individual
security.
\[ E(r_p) = w_1E(r_1) + w_2E(r_2) \]
\[ = 0.5(0.08) + 0.5(0.13) = 0.105 \]
Portfolio expected return = 10.5%
\[ \sigma_p^2 = w_1^2\sigma_1^2 + w_2^2\sigma_2^2 + 2w_1w_2\sigma_1\sigma_2\rho \]
\[ = (0.5^2)(0.12^2) + (0.5^2)(0.20^2) + 2(0.5)(0.5)(0.12)(0.20)(0.3) \]
\[ = 0.0036 + 0.01 + 0.0072 = 0.0208 \]
\[ \sigma_p = \sqrt{0.0208} \approx 14.4\% \]
Statement: Suppose you can invest in the same two risky securities as in Problem 11. Derive the minimum-variance portfolio weights.
\[ w_1^* = \frac{\sigma_2^2 - \rho\sigma_1\sigma_2}{\sigma_1^2 + \sigma_2^2 - 2\rho\sigma_1\sigma_2} \]
\[ w_1^* = \frac{0.20^2 - (0.3)(0.12)(0.20)}{0.12^2 + 0.20^2 - 2(0.3)(0.12)(0.20)} \]
\[ = \frac{0.04 - 0.0072}{0.0144 + 0.04 - 0.0144} = \frac{0.0328}{0.04} = 0.82 \]
The minimum-variance portfolio heavily favors the lower-risk security (Security 1), but still includes some of Security 2 to reduce overall variance.
| Problem | Portfolio Return | Portfolio Risk | Weights (Sec.1 / Sec.2) | Key Insight |
|---|---|---|---|---|
| 11 | 10.5% | 14.4% | 50% / 50% | Diversification lowers risk |
| 12 | ~9.4% (calc’d) | Minimum risk | 82% / 18% | Optimal mix minimizes variance |
Statement:
Two risky assets have expected returns of 10% and 15%, with standard
deviations of 18% and 25%. Their correlation coefficient is 0.4.
Find the weights of the minimum-variance portfolio.
The weight of Asset 1 in the minimum-variance portfolio is:
\[ w_1^* = \frac{\sigma_2^2 - \rho\sigma_1\sigma_2}{\sigma_1^2 + \sigma_2^2 - 2\rho\sigma_1\sigma_2} \]
\[ w_1^* = \frac{0.25^2 - (0.4)(0.18)(0.25)}{0.18^2 + 0.25^2 - 2(0.4)(0.18)(0.25)} \]
\[ = \frac{0.0625 - 0.018}{0.0324 + 0.0625 - 0.036} = \frac{0.0445}{0.0589} \approx 0.755 \]
The minimum-variance portfolio heavily favors the lower-risk asset (Asset 1), but still includes Asset 2 to reduce overall variance through diversification.
| Asset | Expected Return | Std. Dev. | Weight in MVP |
|---|---|---|---|
| 1 | 10% | 18% | 75.5% |
| 2 | 15% | 25% | 24.5% |
The minimum-variance portfolio allocates 75.5% to Asset 1 and 24.5% to Asset 2, demonstrating how diversification reduces risk even when one asset is riskier. This balance minimizes portfolio variance while maintaining exposure to higher expected returns.
Statement:
Two securities have betas of 1.2 and 0.8, with expected returns of 14%
and 10%. The risk‑free rate is 5% and the expected return on the market
portfolio is 12%.
(a) Are these securities fairly priced according to the CAPM?
(b) If not, what is the alpha of each?
\[ E(R_1) = 0.05 + 1.2(0.12 - 0.05) = 13.4\% \]
Actual return = 14% → Alpha = +0.6%
\[ E(R_2) = 0.05 + 0.8(0.12 - 0.05) = 10.6\% \]
Actual return = 10% → Alpha = –0.6%
Interpretation:
Security 1 is underpriced (positive alpha). Security 2 is overpriced
(negative alpha).
Statement: An analyst estimates a stock’s beta at 1.5. The risk‑free rate is 4% and the expected market return is 11%. According to CAPM, what is the expected return on the stock?
\[ E(R_i) = 0.04 + 1.5(0.11 - 0.04) = 14.5\% \]
Result: Expected return = 14.5%
| Problem | Beta | CAPM Expected Return | Actual Return | Alpha |
|---|---|---|---|---|
| 17 Sec.1 | 1.2 | 13.4% | 14% | +0.6% |
| 17 Sec.2 | 0.8 | 10.6% | 10% | –0.6% |
| CFA 1 | 1.5 | 14.5% | — | — |