tickers <- c("SPY", "QQQ", "EEM", "IWM", "EFA", "TLT", "IYR", "GLD")
getSymbols(tickers, from = "2010-01-01", to = "2026-04-01", src = "yahoo")
## [1] "SPY" "QQQ" "EEM" "IWM" "EFA" "TLT" "IYR" "GLD"
prices <- do.call(merge, lapply(tickers, function(x) Ad(get(x))))
colnames(prices) <- tickers
monthly_prices <- to.monthly(prices, indexAt = "lastof", OHLC = FALSE)
etf_returns <- Return.calculate(monthly_prices, method = "discrete")
etf_returns <- na.omit(etf_returns)
tail(etf_returns)
## SPY QQQ EEM IWM EFA
## 2025-10-31 0.0238374141 0.047803919 0.03558049 0.017647532 0.011995297
## 2025-11-30 0.0019499439 -0.015610490 -0.01772148 0.010234304 0.007408238
## 2025-12-31 0.0007975196 -0.006699225 0.02159301 -0.007093521 0.027104738
## 2026-01-31 0.0147377328 0.012306485 0.08024126 0.054801705 0.049047164
## 2026-02-28 -0.0086419517 -0.023445425 0.05888331 0.006778441 0.046059157
## 2026-03-31 -0.0493795461 -0.048382515 -0.09252158 -0.049611183 -0.078288102
## TLT IYR GLD
## 2025-10-31 0.0138109924 -0.02492791 0.03558667
## 2025-11-30 0.0027232217 0.02366371 0.05367818
## 2025-12-31 -0.0265860756 -0.02219823 0.02173351
## 2026-01-31 -0.0003443135 0.02470977 0.12273224
## 2026-02-28 0.0463375319 0.05269722 0.08720078
## 2026-03-31 -0.0423004803 -0.06373015 -0.11051161
ff_lines <- readLines("F-F_Research_Data_Factors.CSV")
data_lines <- ff_lines[grep("^[0-9]{6}", trimws(ff_lines))]
ff_split <- do.call(rbind, strsplit(data_lines, ","))
ff_monthly <- as.data.frame(ff_split, stringsAsFactors = FALSE)
ff_monthly <- as.data.frame(lapply(ff_monthly, trimws))
colnames(ff_monthly) <- c("Date", "Mkt.RF", "SMB", "HML", "RF")
ff_dates <- zoo::as.yearmon(ff_monthly$Date, "%Y%m")
ff_matrix <- data.frame(
Mkt.RF = as.numeric(ff_monthly$Mkt.RF) / 100,
SMB = as.numeric(ff_monthly$SMB) / 100,
HML = as.numeric(ff_monthly$HML) / 100
)
ff_xts <- xts(ff_matrix, order.by = ff_dates)
index(etf_returns) <- zoo::as.yearmon(index(etf_returns))
combined_data <- merge(etf_returns, ff_xts, join = "inner")
combined_data <- na.omit(combined_data)
if(nrow(combined_data) > 0) {
cat("Success! Merged data range:", as.character(start(combined_data)),
"to", as.character(end(combined_data)))
} else {
cat("Still no overlap. First FF date:", as.character(start(ff_xts)),
"\nFirst ETF date:", as.character(start(etf_returns)))
}
## Success! Merged data range: Feb 2010 to Feb 2026
window_train <- combined_data["2020-03/2025-02", tickers]
sigma_capm <- cov(window_train)
inv_sigma_capm <- solve(sigma_capm)
ones <- rep(1, ncol(window_train))
w_capm <- (inv_sigma_capm %*% ones) / as.numeric(t(ones) %*% inv_sigma_capm %*% ones)
cat("CAPM MVP Weights calculated successfully.\n")
## CAPM MVP Weights calculated successfully.
print(round(t(w_capm), 4))
## SPY QQQ EEM IWM EFA TLT IYR GLD
## [1,] 1.4662 -0.7038 0.088 0.0292 -0.1817 0.5129 -0.5294 0.3187
factors <- combined_data["2020-03/2025-02", c("Mkt.RF", "SMB", "HML")]
fits <- apply(window_train, 2, function(x) lm(x ~ factors$Mkt.RF + factors$SMB + factors$HML))
B <- t(sapply(fits, function(x) coef(x)[-1]))
D <- diag(sapply(fits, function(x) var(residuals(x))))
sigma_ff <- B %*% cov(factors) %*% t(B) + D
inv_sigma_ff <- solve(sigma_ff)
w_ff <- (inv_sigma_ff %*% ones) / as.numeric(t(ones) %*% inv_sigma_ff %*% ones)
cat("FF 3-Factor MVP Weights calculated successfully.\n")
## FF 3-Factor MVP Weights calculated successfully.
print(round(t(w_ff), 4))
## SPY QQQ EEM IWM EFA TLT IYR GLD
## [1,] 0.1387 -0.2324 0.1986 -0.0479 0.1798 0.3776 -0.0136 0.3991
mar_2025_rets <- as.numeric(combined_data["2025-03", tickers])
realized_mar_capm <- sum(w_capm * mar_2025_rets)
realized_mar_ff <- sum(w_ff * mar_2025_rets)
results_mar <- data.frame(
Model = c("CAPM (Sample)", "FF 3-Factor"),
Realized_Return_Mar_2025 = c(realized_mar_capm, realized_mar_ff)
)
print(results_mar)
## Model Realized_Return_Mar_2025
## 1 CAPM (Sample) 0.006662892
## 2 FF 3-Factor 0.049233103
window_apr_train <- combined_data["2020-04/2025-03", tickers]
sigma_apr <- cov(window_apr_train)
inv_sigma_apr <- solve(sigma_apr)
ones <- rep(1, ncol(window_apr_train))
w_apr <- (inv_sigma_apr %*% ones) / as.numeric(t(ones) %*% inv_sigma_apr %*% ones)
apr_2025_rets <- as.numeric(combined_data["2025-04", tickers])
realized_apr <- sum(w_apr * apr_2025_rets)
cat("Realized MVP Portfolio Return for April 2025:", round(realized_apr, 6))
## Realized MVP Portfolio Return for April 2025: -0.00805
The table below summarizes the performance of the strategies during the test months of 2025:
| Period | Estimation Model | Realized Return |
|---|---|---|
| March 2025 | CAPM (Sample Covariance) | 0.6663% |
| March 2025 | Fama-French 3-Factor | 4.9233% |
| April 2025 | Rolling Window (60-mo) | -0.805% |
ff_6_raw <- read.csv("6_Portfolios_2x3.csv", skip = 15, header = TRUE, stringsAsFactors = FALSE)
stop_row <- which(ff_6_raw[,1] == "")[1] - 1
ff_6_clean <- ff_6_raw[1:stop_row, ]
colnames(ff_6_clean) <- c("Date", "S_Lo", "S_Med", "S_Hi", "B_Lo", "B_Med", "B_Hi")
ff_6_clean$Date <- trimws(ff_6_clean$Date)
ff_data <- ff_6_clean[ff_6_clean$Date >= "193001" & ff_6_clean$Date <= "201812", ]
ff_numeric <- as.data.frame(lapply(ff_data[,-1], function(x) as.numeric(as.character(x))))
mid_point <- floor(nrow(ff_numeric) / 2)
first_half <- ff_numeric[1:mid_point, ]
second_half <- ff_numeric[(mid_point + 1):nrow(ff_numeric), ]
calc_moments <- function(df) {
res <- data.frame(
Mean = colMeans(df),
SD = apply(df, 2, sd),
Skew = apply(df, 2, moments::skewness),
Kurt = apply(df, 2, moments::kurtosis)
)
return(round(res, 4))
}
stats_h1 <- calc_moments(first_half)
stats_h2 <- calc_moments(second_half)
cat("--- First Half: 1930 to Mid-Point ---\n")
## --- First Half: 1930 to Mid-Point ---
print(stats_h1)
## Mean SD Skew Kurt
## S_Lo 0.9713 8.2253 1.1800 12.0716
## S_Med 1.1695 8.4229 1.5797 15.7404
## S_Hi 1.4844 10.2059 2.2875 20.0760
## B_Lo 0.7648 5.7095 0.1783 9.8941
## B_Med 0.8118 6.7341 1.7116 20.5352
## B_Hi 1.1874 8.9106 1.7694 17.4682
cat("\n--- Second Half: Mid-Point to 2018 ---\n")
##
## --- Second Half: Mid-Point to 2018 ---
print(stats_h2)
## Mean SD Skew Kurt
## S_Lo 0.9959 6.6884 -0.4086 5.1587
## S_Med 1.3548 5.2817 -0.5330 6.4246
## S_Hi 1.4246 5.4985 -0.4642 7.3061
## B_Lo 0.9781 4.6955 -0.3337 4.9925
## B_Med 1.0578 4.3391 -0.4729 5.6534
## B_Hi 1.1446 4.8871 -0.5172 5.8054
Problem Parameters:
Results:
Given Data:
Analysis:
The CML equation provides the expected return for a given level of risk: \(E(r_P) = r_f + \text{Sharpe Ratio} \times \sigma_P\)
Results:
Given: \(y = 0.60\), \(r_f = 6\%\), Risk Premium = 10% (\(\implies E(r_P) = 16\%\)), \(\sigma_P = 14\%\).
Analysis:
a. Holding Gold: Investors would hold gold even if it has lower returns and higher volatility than stocks, provided its correlation with the portfolio is low (less than 1). This provides diversification benefits that reduce the overall portfolio standard deviation.
b. Correlation = 1: If the correlation is 1, gold is “dominated” by stocks. Since stocks provide a higher return for lower risk, no rational investor would hold gold in this scenario as there are no diversification gains.
c. Equilibrium: Part (b) does not represent an equilibrium. In a functional market, the price of an inferior asset would drop until its expected return rises enough to compensate for its risk level.
Problem Parameters:
Results:
sd_a <- 5
sd_b <- 10
ret_a <- 10
ret_b <- 15
w_a <- sd_b / (sd_a + sd_b)
w_b <- 1 - w_a
rf_calc <- (w_a * ret_a) + (w_b * ret_b)
cat("Weight in Stock A:", round(w_a, 4), "\n")
## Weight in Stock A: 0.6667
cat("Weight in Stock B:", round(w_b, 4), "\n")
## Weight in Stock B: 0.3333
cat("Risk-Free Return (rf):", round(rf_calc, 2), "%")
## Risk-Free Return (rf): 11.67 %
Problem Parameters and Input Variables:
The following code calculates the impact of adding a risky asset (ABC Stock) versus a risk-free asset (Government Securities) to Abigail’s $1,000,000 portfolio.
Part A: Keeping the ABC Stock
Part B: Replacing ABC with Government Securities
Part C: Systematic Risk
The systematic risk of the new portfolio will be lower than that of her original portfolio. Replacing a risky asset with a risk-free asset reduces the overall portfolio beta and sensitivity to market movements.
Part D: Diversification and Correlation
The husband’s comment is incorrect. It matters significantly whether she keeps ABC or acquires XYZ. Even if the expected returns and SD are identical, a different correlation coefficient would change the total portfolio risk (standard deviation). Lower correlation provides better diversification.
Part E: Risk Measurement
Weakness of SD: Standard deviation is a symmetric measure that treats upside potential and downside loss equally. Abigail is specifically afraid of losing money, not general volatility.
Alternate Measure: Value at Risk (VaR) or Safety-First Ratio would be more appropriate because they focus strictly on the probability and magnitude of potential losses.
Part A: Alpha and Residual Variance
Stock A Alpha: 1.6% | Residual Var: 3364
Stock B Alpha: -4.4% | Residual Var: 5041
Stock C Alpha: 3.4% | Residual Var: 3600
Stock D Alpha: -4% | Residual Var: 3025
Part B: Optimal Active Portfolio
The optimal risky portfolio is constructed by tilting the passive index toward stocks with positive alpha. Based on the Treynor-Black model, the active portfolio weights are prioritized by the ratio of Alpha to Residual Variance.
Part C & D: Sharpe Ratio Improvement
Passive Strategy Sharpe Ratio: 0.3478
Optimal Portfolio Sharpe Ratio: 0.3662
Improvement: The active position improved the Sharpe ratio by 0.0183.
Part E: Makeup of the Complete Portfolio (A = 2.8)
Allocation to Risky Portfolio (\(y^*\)): The investor should allocate 0.54% of their total wealth to the risky portfolio.
Allocation within the Risky Portfolio:
Active Portfolio (Stocks A-D): -4.86%
Passive Portfolio (Market Index): 104.86%
Final Overall Asset Allocation: * Active Component: -0.03%
Passive Component: 0.57%
Risk-Free Asset (T-bills): 99.46%
Analysis of Regression Results:
Alpha Comparison: Stock XYZ has a high positive alpha (7.3%), suggesting it outperformed the market index on a risk-adjusted basis. Stock ABC has a negative alpha (-3.2%), indicating under performance.
Beta and Systematic Risk: Stock ABC (Beta 0.60) is less sensitive to market movements than Stock XYZ (Beta 0.97). ABC would be considered a “defensive” stock.
R-Squared (\(R^2\)): ABC has a higher \(R^2\) (0.35) than XYZ (0.17). This means 35% of ABC’s returns are explained by the market, whereas XYZ’s returns are mostly driven by firm-specific (idiosyncratic) factors.
Future Implications: The brokerage data shows XYZ’s beta increasing (up to 1.45), suggesting XYZ is becoming significantly riskier and more sensitive to market cycles than the 5-year historical average indicated.