## [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.79636 40.29078 30.35150 51.36657 35.12843
## 2010-01-05 85.02087 40.29078 30.57182 51.18993 35.15939
## 2010-01-06 85.08070 40.04776 30.63577 51.14178 35.30801
## 2010-01-07 85.43986 40.07380 30.45811 51.51909 35.17179
## 2010-01-08 85.72417 40.40361 30.69972 51.80009 35.45043
## 2010-01-11 85.84390 40.23872 30.63577 51.59135 35.74147
## TLT.Adjusted IYR.Adjusted GLD.Adjusted
## 2010-01-04 56.13517 26.76811 109.80
## 2010-01-05 56.49772 26.83237 109.70
## 2010-01-06 55.74142 26.82070 111.51
## 2010-01-07 55.83513 27.06026 110.82
## 2010-01-08 55.81013 26.87914 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.35191 37.14011 27.20337 48.25951 32.49673
## 2010-02-28 82.85847 38.84997 27.68661 50.41920 32.58344
## 2010-03-31 87.90289 41.84566 29.93222 54.56904 34.66402
## 2010-04-30 89.26273 42.78407 29.88248 57.66772 33.69184
## 2010-05-31 82.17039 39.62130 27.07545 53.32151 29.92078
## 2010-06-30 77.91885 37.25366 26.69677 49.19262 29.30384
## TLT.Adjusted IYR.Adjusted GLD.Adjusted
## 2010-01-31 57.69777 25.37740 105.96
## 2010-02-28 57.50021 26.76226 109.43
## 2010-03-31 56.31721 29.37117 108.95
## 2010-04-30 58.18795 31.24745 115.36
## 2010-05-31 61.16044 29.47150 118.88
## 2010-06-30 64.70638 28.09512 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.03119470 0.04603825 0.017763911 0.04475153 0.002668091
## 2010-03-31 0.06087994 0.07710918 0.081108418 0.08230669 0.063853962
## 2010-04-30 0.01546980 0.02242545 -0.001661813 0.05678456 -0.028045668
## 2010-05-31 -0.07945456 -0.07392400 -0.093935691 -0.07536637 -0.111928117
## 2010-06-30 -0.05174064 -0.05975669 -0.013986286 -0.07743394 -0.020619163
## 2010-07-31 0.06830035 0.07258210 0.109324392 0.06730919 0.116103848
## TLT.Adjusted IYR.Adjusted GLD.Adjusted
## 2010-02-28 -0.003423972 0.05457056 0.032748219
## 2010-03-31 -0.020573813 0.09748471 -0.004386396
## 2010-04-30 0.033217753 0.06388142 0.058834363
## 2010-05-31 0.051084329 -0.05683497 0.030513147
## 2010-06-30 0.057977743 -0.04670191 0.023553189
## 2010-07-31 -0.009463761 0.09404820 -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.03119470 0.04603825 0.017763911 0.04475153
## 2 Mar 2010 2010-03-31 0.06087994 0.07710918 0.081108418 0.08230669
## 3 Apr 2010 2010-04-30 0.01546980 0.02242545 -0.001661813 0.05678456
## 4 May 2010 2010-05-31 -0.07945456 -0.07392400 -0.093935691 -0.07536637
## 5 Jun 2010 2010-06-30 -0.05174064 -0.05975669 -0.013986286 -0.07743394
## 6 Jul 2010 2010-07-31 0.06830035 0.07258210 0.109324392 0.06730919
## EFA.Adjusted TLT.Adjusted IYR.Adjusted GLD.Adjusted Date.y Mkt_RF
## 1 0.002668091 -0.003423972 0.05457056 0.032748219 2010-02-01 0.0339
## 2 0.063853962 -0.020573813 0.09748471 -0.004386396 2010-03-01 0.0630
## 3 -0.028045668 0.033217753 0.06388142 0.058834363 2010-04-01 0.0199
## 4 -0.111928117 0.051084329 -0.05683497 0.030513147 2010-05-01 -0.0790
## 5 -0.020619163 0.057977743 -0.04670191 0.023553189 2010-06-01 -0.0556
## 6 0.116103848 -0.009463761 0.09404820 -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.4542548 -0.7051061 0.0884220 0.0214490 -0.1714324 0.5170385
## IYR.Adjusted GLD.Adjusted
## -0.5196880 0.3150623
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.690030e-05 1.002089e-05 -1.724429e-05 -3.844372e-07
## QQQ.Adjusted 1.002089e-05 2.225664e-04 6.693960e-05 -6.305764e-05
## EEM.Adjusted -1.724429e-05 6.693960e-05 1.308150e-03 -1.718755e-05
## IWM.Adjusted -3.844372e-07 -6.305764e-05 -1.718755e-05 7.355500e-05
## EFA.Adjusted 3.648549e-06 -2.087540e-05 5.188973e-04 3.911922e-05
## TLT.Adjusted 1.353410e-05 7.685642e-05 4.302078e-04 2.808800e-05
## IYR.Adjusted 4.675855e-05 -7.859360e-05 2.007083e-04 1.263427e-05
## GLD.Adjusted 1.153417e-05 7.126361e-06 6.079001e-04 2.057507e-05
## EFA.Adjusted TLT.Adjusted IYR.Adjusted GLD.Adjusted
## SPY.Adjusted 3.648549e-06 1.353410e-05 4.675855e-05 1.153417e-05
## QQQ.Adjusted -2.087540e-05 7.685642e-05 -7.859360e-05 7.126361e-06
## EEM.Adjusted 5.188973e-04 4.302078e-04 2.007083e-04 6.079001e-04
## IWM.Adjusted 3.911922e-05 2.808800e-05 1.263427e-05 2.057507e-05
## EFA.Adjusted 5.877421e-04 3.437696e-04 2.306136e-04 2.635111e-04
## TLT.Adjusted 3.437696e-04 1.444412e-03 5.504107e-04 4.636577e-04
## IYR.Adjusted 2.306136e-04 5.504107e-04 9.222534e-04 2.883792e-04
## GLD.Adjusted 2.635111e-04 4.636577e-04 2.883792e-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.71792372 0.06571815 0.02920513 0.22941611 -0.01148062 -0.00267917
## IYR.Adjusted GLD.Adjusted
## -0.02194947 -0.00615384
## [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.007638897
## 2 FF3 MVP -0.063635410
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% | — | — |