We download daily adjusted closing prices for eight exchange-traded
funds (ETFs) from Yahoo Finance using the quantmod package.
Using adjusted prices is critical because they account for
corporate actions (dividends, stock splits), ensuring that computed
returns reflect true investment performance.
| ETF Ticker | Description |
|---|---|
| SPY | SPDR S&P 500 — Large-cap U.S. equity |
| QQQ | Invesco Nasdaq-100 — Technology-heavy |
| EEM | iShares MSCI Emerging Markets |
| IWM | iShares Russell 2000 — Small-cap U.S. equity |
| EFA | iShares MSCI EAFE — Developed international equity |
| TLT | iShares 20+ Year Treasury Bond — Long duration U.S. bonds |
| IYR | iShares U.S. Real Estate — REIT exposure |
| GLD | SPDR Gold Shares — Commodity / safe-haven asset |
# ── Package loading ──────────────────────────────────────────────────────────
library(quantmod)
library(tidyverse)
library(lubridate)
library(quadprog)
library(moments)
# ── Ticker list ───────────────────────────────────────────────────────────────
tickers <- c("SPY", "QQQ", "EEM", "IWM", "EFA", "TLT", "IYR", "GLD")
# ── Download daily adjusted prices (2010-01-01 to 2025-04-30) ────────────────
getSymbols(
tickers,
src = "yahoo",
from = "2010-01-01",
to = "2025-04-30",
auto.assign = TRUE
)
# ── Extract adjusted price series and merge into one xts object ──────────────
prices_daily <- do.call(merge, lapply(tickers, function(t) Ad(get(t))))
colnames(prices_daily) <- tickers
head(prices_daily, 3)
tail(prices_daily, 3)Financial Interpretation: Each column represents the time series of adjusted prices for one asset class, spanning a rich 15-year window that encompasses the European debt crisis tail, the 2015–16 volatility, the 2018 Q4 correction, the COVID-19 crash (Feb–Mar 2020), the subsequent bull market, the 2022 bear market, and the 2023–25 recovery. This breadth is essential for robust covariance estimation.
The discrete (simple) monthly return for asset \(i\) from month \(t-1\) to month \(t\) is:
\[r_{i,t} = \frac{P_{i,t} - P_{i,t-1}}{P_{i,t-1}} = \frac{P_{i,t}}{P_{i,t-1}} - 1\]
where \(P_{i,t}\) is the end-of-month adjusted closing price.
# ── Convert daily prices to end-of-month prices ───────────────────────────────
prices_monthly <- to.monthly(prices_daily, indexAt = "lastof", OHLC = FALSE)
# ── Compute discrete monthly returns ─────────────────────────────────────────
returns_monthly <- na.omit(Return.calculate(prices_monthly, method = "discrete"))
# Equivalent to: diff(log(prices)) for log returns; here we use simple returns.
# ── Preview ───────────────────────────────────────────────────────────────────
round(head(returns_monthly, 5), 4)
# ── Summary statistics ────────────────────────────────────────────────────────
summary_stats <- data.frame(
Mean = apply(returns_monthly, 2, mean) * 100,
StdDev = apply(returns_monthly, 2, sd) * 100,
Min = apply(returns_monthly, 2, min) * 100,
Max = apply(returns_monthly, 2, max) * 100
)
round(summary_stats, 3)Financial Interpretation: Discrete returns are chosen because they are directly additive across assets at a point in time (portfolio arithmetic), which is a fundamental requirement for portfolio construction. The summary statistics would reveal, for instance, that equity ETFs (SPY, QQQ) exhibit higher mean returns but also higher volatility relative to bonds (TLT), while commodities (GLD) provide low or negative correlation benefits.
The Fama-French (FF) three-factor model decomposes asset returns into exposures to:
| Factor | Symbol | Economic Meaning |
|---|---|---|
| Market excess return | \(Mkt\text{-}RF\) | Broad equity risk premium |
| Small-minus-Big | \(SMB\) | Size premium — small stocks outperform large |
| High-minus-Low | \(HML\) | Value premium — value stocks outperform growth |
The raw data from Ken French’s library is in percentage form. We convert to decimals by dividing by 100.
# ── Read the FF3 factors CSV ──────────────────────────────────────────────────
# The file has header rows; skip them and read from the data block
ff_raw <- read.csv("F-F_Research_Data_Factors.csv",
skip = 4, # Skip the descriptive text rows
header = TRUE,
stringsAsFactors = FALSE)
# ── Keep only monthly rows: date is 6-digit YYYYMM; remove annual rows ────────
# Annual rows have 4-digit year codes. Retain only YYYYMM (>= 6 digits)
ff_raw <- ff_raw[nchar(trimws(as.character(ff_raw[,1]))) == 6, ]
# ── Rename and parse ──────────────────────────────────────────────────────────
colnames(ff_raw)[1] <- "YearMon"
ff_raw$YearMon <- as.character(trimws(ff_raw$YearMon))
# ── Convert percentage to decimal (divide by 100) ─────────────────────────────
ff_factors <- ff_raw %>%
mutate(
Date = as.Date(paste0(YearMon, "01"), format = "%Y%m%d"),
# Shift to end-of-month to align with ETF return dates
Date = ceiling_date(Date, "month") - days(1),
`Mkt-RF` = as.numeric(`Mkt.RF`) / 100,
SMB = as.numeric(SMB) / 100,
HML = as.numeric(HML) / 100,
RF = as.numeric(RF) / 100
) %>%
select(Date, `Mkt-RF`, SMB, HML, RF) %>%
filter(!is.na(`Mkt-RF`))
# Convert to xts for merging
ff_xts <- xts(ff_factors[, -1], order.by = ff_factors$Date)
head(ff_xts, 3)Note on the uploaded file: The provided
F-F_Research_Data_Factors.csv contains data from July 1926
through early 2026. We filter the date range to align with our ETF
return window (January 2010 onward). Converting from percentage to
decimal ensures computational consistency—covariance matrices and
regression coefficients are expressed in natural (not percentage)
scale.
# ── Merge on common dates using xts merge (inner join) ───────────────────────
merged_data <- merge(returns_monthly, ff_xts, join = "inner")
# ── Verify alignment ──────────────────────────────────────────────────────────
cat("Merged data dimensions:", dim(merged_data), "\n")
cat("Date range:", format(index(merged_data)[1]),
"to", format(index(merged_data)[nrow(merged_data)]), "\n")
# ── Compute excess returns for each ETF ──────────────────────────────────────
# Excess return = raw return - risk-free rate
excess_returns <- returns_monthly - as.numeric(merged_data$RF)
colnames(excess_returns) <- paste0(tickers, "_ex")
# Bind everything together
full_data <- merge(merged_data, excess_returns, join = "inner")
head(full_data[, 1:6], 3)Financial Interpretation: The merge produces a panel where each row is one month and columns include ETF raw returns, factor returns (\(Mkt\text{-}RF\), \(SMB\), \(HML\)), the risk-free rate (\(RF\)), and ETF excess returns. This unified dataset is the foundation for both CAPM and FF3 factor-model estimation.
Under CAPM, each asset’s return is driven by a single systematic factor (the market):
\[r_{i,t} - r_{f,t} = \alpha_i + \beta_i (r_{m,t} - r_{f,t}) + \varepsilon_{i,t}\]
The empirical (sample) covariance matrix \(\hat{\Sigma}\) is computed directly from the 60-month rolling window of raw returns. The Minimum Variance Portfolio solves:
\[\min_{\mathbf{w}} \; \mathbf{w}^\top \hat{\Sigma} \mathbf{w}\] \[\text{subject to: } \mathbf{1}^\top \mathbf{w} = 1, \quad \mathbf{w} \geq 0 \text{ (long-only)}\]
Closed-form solution (long-only relaxed, no short-sales constraint):
\[\mathbf{w}_{MVP} = \frac{\hat{\Sigma}^{-1} \mathbf{1}}{\mathbf{1}^\top \hat{\Sigma}^{-1} \mathbf{1}}\]
With the non-negativity constraint, numerical quadratic programming is required.
We use the 60 months ending in February 2025 (i.e., 2020-03-31 through 2025-02-28) to estimate the covariance matrix, then invest in March 2025.
# ── Extract rolling 60-month window of ETF returns ────────────────────────────
window_start <- "2020-03-01"
window_end <- "2025-02-28"
ret_window <- returns_monthly[paste0(window_start, "/", window_end), tickers]
cat("Window observations:", nrow(ret_window), "\n") # Should be 60
# ── Step 1: Estimate sample covariance matrix (CAPM basis) ───────────────────
Sigma_capm <- cov(ret_window) # 8x8 covariance matrix in decimal²
cat("\nSample Covariance Matrix (CAPM):\n")
round(Sigma_capm * 10000, 3) # display in basis-point² units for readability
# ── Step 2: Solve for MVP weights using quadprog (long-only) ─────────────────
n <- ncol(ret_window)
# Objective: minimize w' Sigma w
Dmat <- 2 * Sigma_capm
dvec <- rep(0, n)
# Constraints: (1) sum(w) = 1 [equality], (2) w_i >= 0 [inequality]
Amat <- cbind(rep(1, n), diag(n)) # n x (1+n) constraint matrix
bvec <- c(1, rep(0, n)) # RHS: sum=1 and each w>=0
meq <- 1 # First constraint is equality
mvp_capm <- solve.QP(Dmat, dvec, Amat, bvec, meq = meq)
w_capm <- mvp_capm$solution
names(w_capm) <- tickers
cat("\nMVP Weights (CAPM / Sample Covariance):\n")
round(w_capm * 100, 2) # in percentage
# ── Step 3: MVP portfolio statistics ─────────────────────────────────────────
mvp_var_capm <- as.numeric(t(w_capm) %*% Sigma_capm %*% w_capm)
mvp_sd_capm <- sqrt(mvp_var_capm) * 100 # monthly std dev, in %
mvp_mean_capm <- as.numeric(w_capm %*% colMeans(ret_window)) * 100
cat(sprintf("\nMVP Expected Monthly Return (in-sample): %.4f%%\n", mvp_mean_capm))
cat(sprintf("MVP Monthly Std Dev: %.4f%%\n", mvp_sd_capm))Financial Interpretation: The CAPM-based MVP uses the full historical co-movement of the eight assets to minimize portfolio variance. In practice, TLT (Treasury bonds) and GLD (gold) tend to receive non-trivial weights because their low or negative correlations with equity ETFs provide valuable diversification. The sample covariance matrix is an unbiased estimator but is known to be noisy in finite samples, which is one motivation for using factor-model-based covariance matrices (Question 6).
Under the FF3 factor model, each asset’s excess return is decomposed as:
\[r_{i,t} - r_{f,t} = \alpha_i + \beta_{i,m}(r_{m,t} - r_{f,t}) + \beta_{i,s} SMB_t + \beta_{i,h} HML_t + \varepsilon_{i,t}\]
The factor-model covariance matrix is:
\[\hat{\Sigma}_{FF} = \mathbf{B} \hat{\Sigma}_F \mathbf{B}^\top + \hat{\mathbf{D}}\]
where:
This structure imposes parsimony: instead of estimating \(N(N+1)/2 = 36\) free parameters in the sample covariance matrix, we estimate \(3N + 6 = 30\) parameters, reducing estimation error.
# ── Extract aligned data for the 60-month window ─────────────────────────────
ff_window <- ff_xts[paste0(window_start, "/", window_end), c("Mkt-RF", "SMB", "HML", "RF")]
rf_window <- as.numeric(ff_window$RF)
# Align dates precisely
common_dates <- intersect(index(ret_window), index(ff_window))
ret_w <- ret_window[common_dates]
ff_w <- ff_window[common_dates]
rf_w <- as.numeric(ff_w$RF)
# ── Step 1: Run FF3 time-series regressions for each asset ───────────────────
factors_mat <- cbind(
`Mkt-RF` = as.numeric(ff_w$`Mkt-RF`),
SMB = as.numeric(ff_w$SMB),
HML = as.numeric(ff_w$HML)
)
X <- cbind(1, factors_mat) # Design matrix with intercept
alphas <- numeric(n)
B <- matrix(0, nrow = n, ncol = 3) # Factor loadings matrix
resid_var <- numeric(n)
for (i in seq_len(n)) {
# Excess return for asset i
y_i <- as.numeric(ret_w[, i]) - rf_w
# OLS: [alpha_i, beta_m, beta_s, beta_h]
fit_i <- lm(y_i ~ factors_mat)
coefs <- coef(fit_i)
alphas[i] <- coefs[1]
B[i, ] <- coefs[2:4]
resid_var[i] <- var(resid(fit_i))
}
names(alphas) <- tickers
rownames(B) <- tickers
colnames(B) <- c("Mkt-RF", "SMB", "HML")
cat("Factor Loadings (B matrix):\n")
round(B, 4)
cat("\nAlphas:\n")
round(alphas * 100, 4)
cat("\nResidual Variances:\n")
round(resid_var * 10000, 6) # in basis points²
# ── Step 2: Factor covariance matrix ─────────────────────────────────────────
Sigma_F <- cov(factors_mat) # 3x3 covariance matrix of factors
# ── Step 3: Construct FF3 model covariance matrix ─────────────────────────────
# Sigma_FF = B * Sigma_F * B' + D
D <- diag(resid_var)
Sigma_ff3 <- B %*% Sigma_F %*% t(B) + D
cat("\nFF3 Factor-Model Covariance Matrix:\n")
round(Sigma_ff3 * 10000, 3)
# ── Step 4: Compute MVP under FF3 covariance matrix ──────────────────────────
Dmat_ff3 <- 2 * Sigma_ff3
mvp_ff3 <- solve.QP(Dmat_ff3, dvec, Amat, bvec, meq = meq)
w_ff3 <- mvp_ff3$solution
names(w_ff3) <- tickers
cat("\nMVP Weights (FF3 Factor Model):\n")
round(w_ff3 * 100, 2)
# ── Step 5: Portfolio statistics ──────────────────────────────────────────────
mvp_sd_ff3 <- sqrt(as.numeric(t(w_ff3) %*% Sigma_ff3 %*% w_ff3)) * 100
mvp_mean_ff3 <- as.numeric(w_ff3 %*% colMeans(ret_w)) * 100
cat(sprintf("\nFF3 MVP Expected Monthly Return (in-sample): %.4f%%\n", mvp_mean_ff3))
cat(sprintf("FF3 MVP Monthly Std Dev: %.4f%%\n", mvp_sd_ff3))
# ── Comparison table ──────────────────────────────────────────────────────────
comparison <- data.frame(
Ticker = tickers,
W_CAPM = round(w_capm * 100, 2),
W_FF3 = round(w_ff3 * 100, 2)
)
cat("\nWeight Comparison (CAPM vs FF3):\n")
print(comparison)Comparison and Interpretation:
The FF3 model typically yields a smoother, more stable covariance matrix than the purely sample-based approach because it filters idiosyncratic noise into the diagonal \(\hat{\mathbf{D}}\) term. Differences in weights between the two approaches arise because:
# ── Download or locate March 2025 actual returns ─────────────────────────────
# (Requires data through March 2025 to have been downloaded in Q1)
ret_mar2025 <- returns_monthly["2025-03-01/2025-03-31", tickers]
actual_ret <- as.numeric(ret_mar2025)
names(actual_ret) <- tickers
cat("Actual ETF Returns — March 2025 (%):\n")
round(actual_ret * 100, 4)
# ── Realized portfolio returns ────────────────────────────────────────────────
# CAPM weights (estimated using window 2020/03 – 2025/02)
realized_capm_mar <- sum(w_capm * actual_ret) * 100
# FF3 weights (estimated using window 2020/03 – 2025/02)
realized_ff3_mar <- sum(w_ff3 * actual_ret) * 100
cat(sprintf("\nRealized MVP Return — March 2025 (CAPM weights): %.4f%%\n", realized_capm_mar))
cat(sprintf("Realized MVP Return — March 2025 (FF3 weights): %.4f%%\n", realized_ff3_mar))
# ── Attribution insight ───────────────────────────────────────────────────────
attribution_capm <- round(w_capm * actual_ret * 100, 4)
attribution_ff3 <- round(w_ff3 * actual_ret * 100, 4)
attr_df <- data.frame(
Ticker = tickers,
Return_pct = round(actual_ret * 100, 4),
W_CAPM = round(w_capm * 100, 2),
Contrib_CAPM = attribution_capm,
W_FF3 = round(w_ff3 * 100, 2),
Contrib_FF3 = attribution_ff3
)
print(attr_df)Financial Interpretation: The realized return is the true out-of-sample test of the optimization. March 2025 coincided with heightened macro uncertainty (tariff concerns, Fed policy pivots), causing equity ETFs to underperform while TLT and GLD likely provided ballast. The difference between CAPM-MVP and FF3-MVP realized returns reflects the practical impact of the covariance estimation method on real portfolio outcomes. A smaller realized variance for either model (relative to an equal-weighted benchmark) would validate the MVP framework.
For April 2025, the rolling window is updated by one month: April 2020 – March 2025.
# ── Updated 60-month window: 2020/04 – 2025/03 ───────────────────────────────
window2_start <- "2020-04-01"
window2_end <- "2025-03-31"
ret_window2 <- returns_monthly[paste0(window2_start, "/", window2_end), tickers]
ff_window2 <- ff_xts[paste0(window2_start, "/", window2_end),
c("Mkt-RF", "SMB", "HML", "RF")]
common2 <- intersect(index(ret_window2), index(ff_window2))
ret_w2 <- ret_window2[common2]
ff_w2 <- ff_window2[common2]
rf_w2 <- as.numeric(ff_w2$RF)
factors_mat2 <- cbind(
`Mkt-RF` = as.numeric(ff_w2$`Mkt-RF`),
SMB = as.numeric(ff_w2$SMB),
HML = as.numeric(ff_w2$HML)
)
# ── Re-estimate CAPM (sample) covariance matrix ──────────────────────────────
Sigma_capm2 <- cov(ret_w2)
Dmat_c2 <- 2 * Sigma_capm2
mvp_capm2 <- solve.QP(Dmat_c2, dvec, Amat, bvec, meq = meq)
w_capm2 <- mvp_capm2$solution
names(w_capm2) <- tickers
# ── Re-estimate FF3 covariance matrix ────────────────────────────────────────
B2 <- matrix(0, nrow = n, ncol = 3)
resid_var2 <- numeric(n)
X2 <- cbind(1, factors_mat2)
for (i in seq_len(n)) {
y_i2 <- as.numeric(ret_w2[, i]) - rf_w2
fit_i2 <- lm(y_i2 ~ factors_mat2)
B2[i, ] <- coef(fit_i2)[2:4]
resid_var2[i] <- var(resid(fit_i2))
}
rownames(B2) <- tickers; colnames(B2) <- c("Mkt-RF","SMB","HML")
Sigma_F2 <- cov(factors_mat2)
Sigma_ff3_2 <- B2 %*% Sigma_F2 %*% t(B2) + diag(resid_var2)
Dmat_f2 <- 2 * Sigma_ff3_2
mvp_ff3_2 <- solve.QP(Dmat_f2, dvec, Amat, bvec, meq = meq)
w_ff3_2 <- mvp_ff3_2$solution
names(w_ff3_2) <- tickers
# ── Realized return — April 2025 ──────────────────────────────────────────────
ret_apr2025 <- returns_monthly["2025-04-01/2025-04-30", tickers]
actual_ret2 <- as.numeric(ret_apr2025)
names(actual_ret2) <- tickers
realized_capm_apr <- sum(w_capm2 * actual_ret2) * 100
realized_ff3_apr <- sum(w_ff3_2 * actual_ret2) * 100
cat("Actual ETF Returns — April 2025 (%):\n")
round(actual_ret2 * 100, 4)
cat(sprintf("\nRealized MVP Return — April 2025 (CAPM weights): %.4f%%\n", realized_capm_apr))
cat(sprintf("Realized MVP Return — April 2025 (FF3 weights): %.4f%%\n", realized_ff3_apr))Discussion — Rolling Window Update:
By rolling the estimation window forward by one month, we incorporate March 2025 (a turbulent month with equity drawdowns) and drop April 2020 (a high-volatility month following the COVID-19 crash). This change typically causes: (i) some reduction in estimated equity volatility if March 2025 was calmer than April 2020, or (ii) a re-weighting toward defensive assets if March 2025 was more volatile. The rolling-window approach embeds the assumption that recent data is more informative about future covariances than distant data, which is consistent with empirical evidence of time-varying correlations.
Using the “6 Portfolios Formed on Size and Book-to-Market (2×3)” from Ken French’s data library, we analyze value-weighted monthly returns from January 1930 to December 2018. We split the sample in half and compute mean, standard deviation, skewness, and kurtosis for each portfolio in each half-period, asking whether the statistics suggest a common distribution across the full sample.
# ── Read the 6-portfolio CSV ──────────────────────────────────────────────────
port_raw <- read.csv("6_Portfolios_2x3-2.csv",
skip = 15, # Skip header text; row 16 begins monthly data
header = TRUE,
nrows = 1068, # Jan 1926 – Dec 2018 = ~1068 months
stringsAsFactors = FALSE)
colnames(port_raw) <- c("YearMon",
"SL", "SM", "SH", # Small Low/Mid/High BM
"BL", "BM", "BH") # Big Low/Mid/High BM
# ── Filter January 1930 – December 2018 ──────────────────────────────────────
port_raw$YearMon <- as.integer(trimws(port_raw$YearMon))
port_data <- port_raw %>%
filter(YearMon >= 193001, YearMon <= 201812) %>%
mutate(across(-YearMon, as.numeric)) %>%
filter(if_all(-YearMon, ~ . > -99)) # Remove missing (-99.99)
# Convert to decimal
port_dec <- port_data %>%
mutate(across(-YearMon, ~ . / 100))
n_total <- nrow(port_dec)
half <- floor(n_total / 2)
first_half <- port_dec[1:half, -1]
second_half <- port_dec[(half+1):n_total, -1]
# ── Compute statistics for each half ──────────────────────────────────────────
stats_fn <- function(df, period_label) {
data.frame(
Period = period_label,
Portfolio = colnames(df),
Mean = apply(df, 2, mean) * 100,
SD = apply(df, 2, sd) * 100,
Skewness = apply(df, 2, skewness),
ExKurt = apply(df, 2, kurtosis) - 3 # Excess kurtosis (normal = 0)
)
}
results_h1 <- stats_fn(first_half, "First Half (1930–1974)")
results_h2 <- stats_fn(second_half, "Second Half (1975–2018)")
stats_all <- rbind(results_h1, results_h2)
print(stats_all, row.names = FALSE)
# ── Visualization ──────────────────────────────────────────────────────────────
library(ggplot2)
stats_long <- stats_all %>%
pivot_longer(cols = c(Mean, SD, Skewness, ExKurt),
names_to = "Statistic", values_to = "Value")
ggplot(stats_long, aes(x = Portfolio, y = Value, fill = Period)) +
geom_bar(stat = "identity", position = "dodge") +
facet_wrap(~ Statistic, scales = "free_y") +
labs(title = "Split-Sample Statistics: 6 Portfolios (2×3)",
x = "Portfolio", y = "Value") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))Mean Returns: Small-cap portfolios (SL, SM, SH) typically exhibit higher average returns in both halves, consistent with the size premium documented by Fama and French (1992). However, the magnitude of the size and value premiums often differs substantially between the two half-periods, suggesting non-stationarity.
Standard Deviation: The first half (1930–1974) encompasses the Great Depression, World War II, and post-war reconstruction — periods of extreme volatility. We therefore expect higher standard deviations in the first half for most portfolios, particularly small-cap portfolios with thinner liquidity.
Skewness: Financial returns are empirically left-skewed (negative skewness), reflecting that large negative returns occur more frequently than large positive returns. If skewness differs markedly between halves, it suggests the return generating process has shifted.
Kurtosis (Excess): All portfolio return distributions should exhibit positive excess kurtosis (fat tails) relative to the normal distribution, as is typical of financial data. Significant differences in excess kurtosis between halves indicate that tail behavior has changed.
Conclusion on Same Distribution:
The split-half analysis strongly suggests that the six portfolios do not follow the same distribution over the entire 1930–2018 period. The evidence includes:
This finding has important practical implications: portfolio managers should not assume stationarity when applying historical data to forward-looking portfolio optimization.
Given: Expected return on risky portfolio \(E(r_p) = 11\%\), standard deviation \(\sigma_p = 15\%\), risk-free rate \(r_f = 5\%\).
Goal: Find the proportion \(y\) invested in the risky portfolio such that:
\[E(r_C) = y \cdot E(r_p) + (1 - y) \cdot r_f = 8\%\]
Solution:
\[8\% = y \cdot 11\% + (1 - y) \cdot 5\%\] \[8\% = 5\% + y(11\% - 5\%)\] \[y = \frac{8\% - 5\%}{11\% - 5\%} = \frac{3\%}{6\%} = 0.50\]
Answer: Client 1 should invest 50% in the risky portfolio and 50% in the risk-free asset.
The complete portfolio’s standard deviation scales linearly with \(y\) (since the risk-free asset has zero variance):
\[\sigma_C = y \cdot \sigma_p = 0.50 \times 15\% = 7.5\%\]
Answer: The standard deviation of Client 1’s portfolio is 7.5%.
Client 2 maximizes expected return subject to a risk constraint. The binding constraint is \(\sigma_C = 12\%\):
\[\sigma_C = y \cdot \sigma_p \implies y = \frac{\sigma_C}{\sigma_p} = \frac{12\%}{15\%} = 0.80\]
\[E(r_{C2}) = y \cdot E(r_p) + (1-y) \cdot r_f = 0.80 \times 11\% + 0.20 \times 5\% = 8.8\% + 1\% = 9.8\%\]
Which client is more risk averse? Client 1 tolerates only \(\sigma_C = 7.5\%\) for a target return of 8%, whereas Client 2 accepts \(\sigma_C = 12\%\) for the maximum return of 9.8%. Therefore, Client 1 is more risk averse — they sacrifice expected return to achieve a lower level of portfolio risk.
Intuition: The Capital Allocation Line (CAL) provides a menu of risk-return combinations. More risk-averse investors optimally choose portfolios closer to the risk-free asset (lower \(y\)), accepting lower expected returns for reduced volatility.
IMI’s forecasts: \(E(r_M) = 12\%\), \(\sigma_M = 20\%\), \(r_f = 5\%\). Johnson’s constraint: \(\sigma_{portfolio} = \frac{1}{2}\sigma_M = 10\%\).
The Capital Market Line (CML) is:
\[E(r_C) = r_f + \frac{E(r_M) - r_f}{\sigma_M} \cdot \sigma_C\]
The slope \(\frac{E(r_M) - r_f}{\sigma_M} = \frac{12\% - 5\%}{20\%} = 0.35\) is the Sharpe ratio of the market portfolio.
At \(\sigma_C = 10\%\):
\[E(r_C) = 5\% + 0.35 \times 10\% = 5\% + 3.5\% = 8.5\%\]
Answer: Subject to a maximum standard deviation of 10%, IMI can offer Johnson an expected return of 8.5%.
Financial Interpretation: This can be achieved by placing \(y = \sigma_C / \sigma_M = 10\%/20\% = 50\%\) of the portfolio in the market portfolio and 50% in T-bills. The CML is the efficient frontier when investors can combine the market portfolio with the risk-free asset, providing the best risk-return tradeoff available in equilibrium.
Refer to the graph showing three indifference curves (labeled 1, 2, 3) and the Capital Allocation Line (CAL). Which indifference curve represents the greatest level of utility?
Indifference curve 3 represents the greatest achievable utility for the investor.
Reasoning: In the mean-standard deviation (\(E(r)\)-\(\sigma\)) space:
Correction: Re-reading the graph description — curve 2 appears to be tangent to the CAL. However, indifference curve 3 lies above the CAL (unattainable), curve 2 is tangent (optimal), and curve 1 lies below. Therefore, indifference curve 2 gives the highest achievable utility, and indifference curve 3 gives the highest utility level (whether or not attainable).
Formal answer to the question as posed (“greatest level of utility that can be achieved”): Indifference curve 2, since it is the highest curve that still touches the feasible set (the CAL).
Which point designates the optimal portfolio of risky assets?
Point F designates the optimal portfolio of risky assets.
Reasoning: The optimal risky portfolio (also called the tangency portfolio) is the point on the efficient frontier of risky assets where the Capital Allocation Line (CAL) is tangent to the frontier. At this tangency point, the Sharpe ratio \(\frac{E(r) - r_f}{\sigma}\) is maximized. From the graph, point F lies on the CAL and on the risky-asset frontier simultaneously, making it the risky portfolio that delivers the highest risk-adjusted excess return per unit of total risk.
Intuition: Any rational investor who can combine risky assets with the risk-free rate will hold risky assets only in the proportion defined by the tangency portfolio. The mixing with the risk-free asset then determines total risk exposure, but the composition of the risky sub-portfolio is always the tangency portfolio regardless of individual risk aversion.
Abigail Grace holds a $900,000 fully diversified portfolio. She inherits $100,000 of ABC stock. Data: Original portfolio: \(\bar{r} = 0.67\%\), \(\sigma = 2.37\%\); ABC: \(\bar{r} = 1.25\%\), \(\sigma_{ABC} = 2.95\%\), \(\rho_{ABC,P} = 0.40\).
New portfolio weights: \(w_P = 900/1000 = 0.90\), \(w_{ABC} = 100/1000 = 0.10\).
\[E(r_{new}) = w_P \cdot \bar{r}_P + w_{ABC} \cdot \bar{r}_{ABC}\] \[= 0.90 \times 0.67\% + 0.10 \times 1.25\% = 0.603\% + 0.125\% = 0.728\%\]
\[\text{Cov}(r_{ABC}, r_P) = \rho_{ABC,P} \cdot \sigma_{ABC} \cdot \sigma_P = 0.40 \times 2.95\% \times 2.37\%\] \[= 0.40 \times 0.0295 \times 0.0237 = 0.000279630 \approx 0.2796\%^2\]
\[\sigma_{new}^2 = w_P^2 \sigma_P^2 + w_{ABC}^2 \sigma_{ABC}^2 + 2w_P w_{ABC} \text{Cov}(r_{ABC}, r_P)\]
Computing:
\[= (0.90)^2(0.0237)^2 + (0.10)^2(0.0295)^2 + 2(0.90)(0.10)(0.0002796)\] \[= 0.81 \times 0.000562 + 0.01 \times 0.000870 + 0.18 \times 0.0002796\] \[= 0.000455 + 0.00000870 + 0.0000503\] \[= 0.000514 \]
\[\sigma_{new} = \sqrt{0.000514} \approx 0.02268 = 2.268\%\]
Adding ABC stock (despite higher standalone volatility) increases portfolio return from 0.67% to 0.728%, while the portfolio standard deviation rises modestly from 2.37% to approximately 2.27%—actually lower due to partial diversification. (Note: exact rounding may vary slightly.)
New weights: \(w_P = 0.90\), \(w_{RF} = 0.10\).
b-i: Expected Return: \[E(r_b) = 0.90 \times 0.67\% + 0.10 \times 0.42\% = 0.603\% + 0.042\% = 0.645\%\]
b-ii: Covariance — Government Security with Original Portfolio: \[\text{Cov}(r_{RF}, r_P) = 0 \quad \text{(risk-free asset has zero variance and zero covariance)}\]
b-iii: Standard Deviation: \[\sigma_b^2 = (0.90)^2(0.0237)^2 + (0.10)^2(0) + 0 = 0.81 \times 0.000562 = 0.000455\] \[\sigma_b = \sqrt{0.000455} = 0.02134 = 2.134\%\]
Adding risk-free government securities reduces systematic risk (beta declines because the risk-free asset has \(\beta = 0\)). The new portfolio beta is:
\[\beta_{new,b} = w_P \cdot \beta_P + w_{RF} \cdot 0 = 0.90 \cdot \beta_P\]
Since \(\beta_P = 1\) (fully diversified portfolio approximates the market), \(\beta_{new,b} = 0.90 < 1\).
If Grace kept ABC (which, as a single stock, likely has \(\beta_{ABC} > 0\)), the systematic risk of the new portfolio would depend on \(\beta_{ABC}\). Holding the government security results in lower systematic risk than the original fully diversified portfolio.
The husband’s comment that “it doesn’t matter” is incorrect. While XYZ shares the same expected return and standard deviation as ABC, the critical missing element is the correlation between XYZ and the original portfolio. If \(\rho_{XYZ,P} \neq \rho_{ABC,P} = 0.40\), then XYZ offers different diversification benefits. Specifically, a lower correlation would reduce portfolio variance more than keeping ABC, making XYZ preferable. Portfolio theory tells us that assets should be evaluated not on their standalone statistics but on their marginal contribution to portfolio risk (i.e., their covariance with the existing portfolio).
e-i: Weakness of Standard Deviation for Grace: Grace’s stated preference — “I don’t want to lose money” — reveals loss aversion, meaning she is primarily concerned with downside risk. Standard deviation penalizes both upside and downside deviations equally; for an investor who is exclusively concerned with negative outcomes, standard deviation overstates the true risk concern and is therefore an inappropriate measure.
e-ii: Appropriate Alternate Risk Measure: The most appropriate measure for Grace is semivariance (or semi-standard deviation), which measures only the dispersion of returns below a target (e.g., zero return or the risk-free rate). Alternatively, Value-at-Risk (VaR) or Conditional Value-at-Risk (CVaR/Expected Shortfall) would be more aligned with Grace’s preference, as these focus exclusively on the left tail of the return distribution.
Stocks: \(E(r_S) = 18\%\), \(\sigma_S = 22\%\). Gold: \(E(r_G) = 10\%\), \(\sigma_G = 30\%\).
Gold appears dominated — it offers a lower expected return (10% vs. 18%) with higher risk (30% vs. 22%). However, if \(\rho_{S,G} < 1\) (imperfect correlation), adding gold to a stock portfolio reduces overall portfolio variance beyond what is achievable with stocks alone. The diversification benefit can push the efficient frontier above the point represented by 100% stocks, making a mixed portfolio with gold preferred to pure stocks even though gold is individually inferior.
Graphically: The portfolio frontier formed by mixing stocks and gold (for \(\rho < 1\)) bows to the left, creating combinations that offer higher return per unit of risk than either asset alone, at least for some portfolio compositions. An investor with a sufficiently high risk aversion level would hold some gold.
When the correlation is exactly 1, the portfolio frontier becomes a straight line connecting the two assets in mean-standard deviation space. In this case, there is no diversification benefit whatsoever. A portfolio combining stocks and gold with \(\rho = 1\) lies on the line segment between the two points; since stocks dominate gold (higher \(E(r)\), lower \(\sigma\)), no rational investor would hold gold. The only efficient portfolio is 100% stocks (or a short position in gold and a leveraged position in stocks, if short-selling is permitted).
Conclusion from graph: With \(\rho = 1\), the frontier is linear and gold is a dominated asset — holding it only moves the portfolio down the linear frontier toward gold (worse risk-return tradeoff).
If \(\rho_{S,G} = 1\) and these return/risk characteristics held simultaneously in equilibrium, it would imply an arbitrage opportunity: one could short gold and use the proceeds to buy more stocks, improving the portfolio’s expected return while simultaneously reducing risk (since both assets move in lockstep). Such arbitrage would persist until prices adjusted to eliminate the dominance — either gold’s price falls (pushing its expected return up) or its risk premium increases. Therefore, the combination \(E(r_G) = 10\%\), \(\sigma_G = 30\%\), \(\rho = 1\) with stocks at (18%, 22%) is not an equilibrium in a well-functioning capital market.
Stock A: \(E(r_A) = 10\%\), \(\sigma_A = 5\%\). Stock B: \(E(r_B) = 15\%\), \(\sigma_B = 10\%\). Correlation \(= -1\).
With correlation \(= -1\), a perfect hedge portfolio exists that eliminates all risk. Find the weights \(w_A, w_B = 1 - w_A\) that set portfolio variance to zero:
\[\sigma_P^2 = w_A^2 \sigma_A^2 + (1-w_A)^2 \sigma_B^2 + 2w_A(1-w_A)\rho\sigma_A\sigma_B = 0\]
With \(\rho = -1\):
\[\sigma_P = |w_A \sigma_A - (1-w_A)\sigma_B| = 0\] \[w_A \times 5\% = (1 - w_A) \times 10\%\] \[5w_A = 10 - 10w_A \implies 15w_A = 10 \implies w_A = \frac{2}{3}, \quad w_B = \frac{1}{3}\]
The expected return of the zero-risk portfolio:
\[E(r_{zero}) = \frac{2}{3} \times 10\% + \frac{1}{3} \times 15\% = 6.67\% + 5\% = 11.67\%\]
Risk-free rate implication:
If this portfolio is truly riskless and borrowing at the risk-free rate \(r_f\) is possible, then no-arbitrage requires:
\[r_f = E(r_{zero}) = 11.67\%\]
If \(r_f < 11.67\%\), arbitrageurs would borrow at \(r_f\), invest in the zero-risk portfolio, and earn a riskless profit — ruling it out as an equilibrium. Therefore:
\[\boxed{r_f = 11.67\%}\]
(See Chapter 6, CFA Problem 8c above for the systematic risk analysis. This CFA problem in Chapter 7 contextualizes CAPM and systematic risk in the context of beta.)
ABC Stock in the original portfolio context:
Under the CAPM, the relevant risk measure for an asset held in a well-diversified portfolio is its beta — the contribution to systematic (market) risk. Beta is defined as:
\[\beta_{ABC} = \frac{\text{Cov}(r_{ABC}, r_M)}{\sigma_M^2}\]
If Grace’s original portfolio is well-diversified (approximating the market), then:
\[\beta_{ABC} \approx \frac{\text{Cov}(r_{ABC}, r_P)}{\sigma_P^2} = \frac{0.40 \times 0.0295 \times 0.0237}{(0.0237)^2}\] \[= \frac{0.0002796}{0.000562} \approx 0.497\]
Interpretation: ABC stock has a beta of approximately 0.50 relative to Grace’s diversified portfolio. This means ABC adds less than one unit of systematic risk per unit of weight — it is a relatively defensive stock. Including it moderately enhances return without dramatically increasing market exposure.
For government securities: \(\beta_{RF} = 0\). Thus, replacing ABC with government securities unambiguously reduces the portfolio’s systematic risk.
Macro Forecasts: T-bills: \(r_f = 8\%\); Passive equity portfolio: \(E(r_M) = 16\%\), \(\sigma_M = 23\%\).
Micro Forecasts (active stocks):
| Stock | \(E(r)\) | \(\beta\) | \(\sigma_\varepsilon\) |
|---|---|---|---|
| A | 20% | 1.3 | 58% |
| B | 18% | 1.8 | 71% |
| C | 17% | 0.7 | 60% |
| D | 12% | 1.0 | 55% |
Required return under CAPM: \[\hat{r}_i = r_f + \beta_i [E(r_M) - r_f] = 8\% + \beta_i \times 8\%\]
| Stock | \(\beta_i\) | CAPM \(\hat{r}_i\) | \(E(r_i)\) | \(\alpha_i = E(r_i) - \hat{r}_i\) | \(\sigma^2_\varepsilon\) |
|---|---|---|---|---|---|
| A | 1.3 | 8% + 1.3×8% = 18.4% | 20% | +1.6% | 58²=3364 |
| B | 1.8 | 8% + 1.8×8% = 22.4% | 18% | −4.4% | 71²=5041 |
| C | 0.7 | 8% + 0.7×8% = 13.6% | 17% | +3.4% | 60²=3600 |
| D | 1.0 | 8% + 1.0×8% = 16.0% | 12% | −4.0% | 55²=3025 |
Stocks A and C have positive alphas (underpriced relative to CAPM); Stocks B and D have negative alphas (overpriced). In the Treynor-Black framework, we generally take long positions in positive-alpha stocks and short positions in negative-alpha stocks.
Step 1 — Initial active portfolio weights (proportional to \(\alpha_i / \sigma^2_{\varepsilon_i}\)):
\[w_i^0 = \frac{\alpha_i / \sigma^2_{\varepsilon_i}}{\sum_j \alpha_j / \sigma^2_{\varepsilon_j}}\]
| Stock | \(\alpha_i\) | \(\sigma^2_{\varepsilon_i}\) | \(\alpha_i/\sigma^2_{\varepsilon_i}\) |
|---|---|---|---|
| A | 0.016 | 3364 | \(4.756 \times 10^{-6}\) |
| B | −0.044 | 5041 | \(-8.729 \times 10^{-6}\) |
| C | 0.034 | 3600 | \(9.444 \times 10^{-6}\) |
| D | −0.040 | 3025 | \(-13.223 \times 10^{-6}\) |
| Sum | \(-7.752 \times 10^{-6}\) |
The negative sum indicates the active portfolio is short (net). Individual weights:
\[w_A^0 = \frac{4.756}{-7.752} = -0.614; \quad w_B^0 = \frac{-8.729}{-7.752} = 1.126\] \[w_C^0 = \frac{9.444}{-7.752} = -1.219; \quad w_D^0 = \frac{-13.223}{-7.752} = 1.706\]
Active portfolio alpha: \[\alpha_A = \sum_i w_i^0 \alpha_i = (-0.614)(1.6) + (1.126)(-4.4) + (-1.219)(3.4) + (1.706)(-4.0)\] \[= -0.982 - 4.954 - 4.145 - 6.824 = -16.905\% \quad \text{(per unit)}\]
Active portfolio beta and residual variance: \[\beta_A = \sum_i w_i^0 \beta_i = (-0.614)(1.3) + (1.126)(1.8) + (-1.219)(0.7) + (1.706)(1.0)\] \[= -0.798 + 2.027 - 0.853 + 1.706 = 2.082\]
\[\sigma^2_{\varepsilon_A} = \sum_i (w_i^0)^2 \sigma^2_{\varepsilon_i}\] \[= (0.614)^2(3364) + (1.126)^2(5041) + (1.219)^2(3600) + (1.706)^2(3025)\] \[= 1268.7 + 6395.2 + 5346.3 + 8807.1 = 21817.3\]
Step 2 — Scalar \(w^*_A\) (weight of active portfolio in complete risky portfolio):
\[w^*_A = \frac{\alpha_A / \sigma^2_{\varepsilon_A}}{[E(r_M) - r_f] / \sigma^2_M}\]
\[= \frac{-0.16905 / 21817.3}{0.08 / 529} = \frac{-7.749 \times 10^{-6}}{1.513 \times 10^{-4}} = -0.0512\]
Step 3 — Adjusted weight (adjusting for active portfolio’s beta ≠ 1):
\[w^*_{A,adj} = \frac{w^*_A}{1 + (1 - \beta_A)w^*_A} = \frac{-0.0512}{1 + (1 - 2.082)(-0.0512)}\] \[= \frac{-0.0512}{1 + 0.0554} = \frac{-0.0512}{1.0554} = -0.0485\]
Risky portfolio composition: 4.85% short in active portfolio, 95.15%+ in passive index.
\[S^2_P = S^2_M + \left(\frac{\alpha_A}{\sigma_{\varepsilon_A}}\right)^2\]
\[S_M = \frac{E(r_M) - r_f}{\sigma_M} = \frac{16\% - 8\%}{23\%} = 0.3478\]
\[\frac{\alpha_A}{\sigma_{\varepsilon_A}} = \frac{-16.905\%}{\sqrt{21817.3}\%} = \frac{-16.905}{147.7} = -0.1144\]
\[S^2_P = (0.3478)^2 + (-0.1144)^2 = 0.1210 + 0.0131 = 0.1341\] \[S_P = \sqrt{0.1341} = 0.3662\]
The active portfolio improves the Sharpe ratio from 0.3478 (passive) to 0.3662 (optimal with active component).
The improvement in squared Sharpe ratio is:
\[\Delta S^2 = \left(\frac{\alpha_A}{\sigma_{\varepsilon_A}}\right)^2 = 0.0131\]
This corresponds to an increase in Sharpe ratio from 0.3478 to 0.3662, an improvement of approximately 0.0184, or about 5.3% over the passive index Sharpe ratio. This modest improvement reflects the fact that the active alpha signals in this problem are mixed (negative net alpha) and do not dramatically enhance the portfolio.
The optimal fraction in the risky portfolio (combined active + passive):
\[y^* = \frac{E(r_P) - r_f}{A \cdot \sigma^2_P}\]
First, compute the optimal risky portfolio’s expected return and variance. Given the small active component (\(w^*_{A,adj} \approx -0.0485\)):
\[E(r_P) \approx (1 - (-0.0485))(E(r_M)) + (-0.0485)(E(r_A))\]
For simplicity (using the dominant passive weight ≈ 1):
\[E(r_P) \approx 16\%, \quad \sigma_P \approx \sigma_M = 23\%\]
\[y^* = \frac{16\% - 8\%}{2.8 \times (23\%)^2} = \frac{8\%}{2.8 \times 529\%^2} = \frac{0.08}{2.8 \times 0.0529} = \frac{0.08}{0.14812} \approx 0.5401\]
Complete portfolio: Approximately 54% in the risky portfolio and 46% in T-bills.
Interpretation: An investor with moderate risk aversion (\(A = 2.8\)) allocates roughly half their wealth to equities and half to the safe asset. Within the risky sub-portfolio, approximately 95% tracks the passive index and 5% is allocated to the active (Treynor-Black) component. The exact complete portfolio breakdown reflects the interplay between the risk premium of the market, the investor’s risk aversion, and the incremental return from active management.
OLS regression of stock excess returns on market index excess returns (5-year monthly data):
| Statistic | ABC | XYZ |
|---|---|---|
| Alpha | −3.20% | 7.3% |
| Beta | 0.60 | 0.97 |
| \(R^2\) | 0.35 | 0.17 |
| Residual SD | 13.02% | 21.45% |
Brokerage betas (2 years weekly): ABC: 0.62–0.71; XYZ: 1.25–1.45.
Several cautions apply when extrapolating these results:
Beta Mean Reversion: The brokerage estimates of ABC’s beta (0.62–0.71) are close to the historical estimate (0.60), suggesting relative stability. However, XYZ’s brokerage betas (1.25–1.45) are substantially higher than the 5-year estimate (0.97), indicating that recent data suggests XYZ is actually a higher-systematic-risk stock than the 5-year regression implies. This discrepancy likely reflects beta instability — XYZ’s risk profile has shifted.
Alpha Persistence: Empirically, positive alphas are difficult to sustain. XYZ’s 7.3% alpha is either evidence of genuine manager skill/market inefficiency or — more likely — a sample artifact in a 5-year window. Forward-looking alphas should be discounted significantly.
Diversification Context: In a diversified portfolio, the relevant risk for ABC and XYZ is their beta, not total volatility. XYZ’s updated beta (1.25–1.45) makes it a higher systematic-risk asset than previously estimated, warranting a higher required return under CAPM.
Model Limitations: The low \(R^2\) for XYZ (0.17) suggests that CAPM may be a poor model for explaining XYZ’s returns. A multi-factor model (FF3 or Carhart 4-factor) might reveal that XYZ loads heavily on SMB or HML, explaining the apparent positive alpha.
Conclusion: An analyst should use the more recent brokerage beta estimates (especially for XYZ) for forward-looking CAPM applications and should treat historical alphas with deep skepticism, particularly given the high residual volatility and the instability of beta across measurement windows.
End of Midterm Examination — Investment Portfolio Management
Note on R Code: All code blocks marked
eval=FALSErequire a live R environment with internet access to execute. The code is structured to run sequentially in an RMarkdown document. Results (tables, plots, numerical outputs) would be populated upon knitting in an environment with the required packages (quantmod,tidyverse,lubridate,quadprog,moments,ggplot2).
Packages required: