Coverage: Ch.7 CFA 1–4, 10 · Ch.8 CFA 1–5 · Ch.9 CFA 8–10 · Ch.10 Problems 13–16 (Bodie, Kane & Marcus, Investments)
(a) Will limiting the portfolio to 20 stocks likely increase or decrease risk?
Increase the risk. Total portfolio risk is the sum of systematic (market) risk, which cannot be diversified away, and firm-specific (nonsystematic) risk, which can. Cutting the portfolio from about 40 holdings to 20 reduces diversification, so a larger fraction of each stock’s firm-specific risk remains in the portfolio. With fewer names absorbing idiosyncratic shocks, the portfolio standard deviation rises even though average systematic risk is essentially unchanged.
(b) Can Hennessy go from 40 to 20 issues without significantly affecting risk?
Yes — if the 20 retained stocks are chosen for low mutual correlation. Most of the benefit of diversification is captured by roughly 20 well-chosen securities; beyond that, the marginal reduction in risk is small. If Hennessy keeps stocks that are spread across industries and have low correlations with one another (and drops the names that contributed most to firm-specific risk), the reduced portfolio can retain nearly the same total risk as the 40-stock portfolio. The key is which 20 stocks are kept, not merely the count.
The relationship between the number of stocks and portfolio risk is non-linear: diversification benefits are large at first and then flatten out. Going from 40 → 20 sacrifices only a small amount of diversification, so the gain from concentrating in Hennessy’s best ideas can outweigh the modest rise in firm-specific risk. Going from 20 → 10, however, pushes the portfolio into the steep region of the curve, where each stock removed adds a disproportionately large amount of nonsystematic risk. Because Wilstead evaluates Hennessy’s portfolio on a standalone basis, that extra idiosyncratic risk is not diversified away by other managers. So while a 20-stock concentration may be advantageous, a 10-stock concentration is much less likely to be, because the incremental risk grows faster than the incremental benefit from superior stock selection.
Hennessy manages only $30 million of a $280 million total fund (the other five managers run $250 million across 150+ issues). At the total-fund level, the firm-specific risk that concentration adds to the Hennessy sub-portfolio is largely diversified away against the rest of the fund — what matters is Hennessy’s correlation with, and weight in, the whole fund, not its standalone standard deviation. Because Hennessy is a small slice of a well-diversified whole, the marginal contribution of a 10- or 20-stock Hennessy portfolio to total-fund risk is modest. From this broader perspective the committee can be more comfortable allowing concentration, since the fund’s overall diversification cushions the added idiosyncratic risk, and it lets Hennessy exploit its stock-selection skill more fully.
| Portfolio | E(R) % | σ % |
|---|---|---|
| W | 15 | 36 |
| X | 12 | 15 |
| Z | 5 | 7 |
| Y | 9 | 21 |
Answer: (d) Portfolio Y. A portfolio is efficient only if no other portfolio offers higher return for the same (or less) risk. Portfolio X (12%, 15%) has a higher expected return and a lower standard deviation than Portfolio Y (9%, 21%) — so X strictly dominates Y. A dominated portfolio cannot sit on the Markowitz efficient frontier. (W, X and Z are mutually non-dominating and can lie on the frontier.)
Inputs: σ_A = 40%, σ_B = 20%, σ_C = 40%; ρ_AB = 0.90, ρ_BC = 0.10. Equal weights (0.5 / 0.5).
\[\sigma_P^2 = w_1^2\sigma_1^2 + w_2^2\sigma_2^2 + 2 w_1 w_2 \rho_{12}\sigma_1\sigma_2\]
Portfolio A&B: \[\sigma^2 = 0.25(40^2) + 0.25(20^2) + 2(0.5)(0.5)(0.90)(40)(20) = 400 + 100 + 360 = 860 \;\Rightarrow\; \sigma = 29.3\%\]
Portfolio B&C: \[\sigma^2 = 0.25(20^2) + 0.25(40^2) + 2(0.5)(0.5)(0.10)(20)(40) = 100 + 400 + 40 = 540 \;\Rightarrow\; \sigma = 23.2\%\]
Recommend the B&C portfolio. Both candidate portfolios pair a 20%-σ stock with a 40%-σ stock, so the individual risks are identical across the two choices. The deciding factor is correlation: B&C has a much lower correlation (0.10 vs. 0.90), giving a far stronger diversification effect. The result is a lower portfolio standard deviation (23.2% vs. 29.3%). With no return information provided, the lower-risk B&C portfolio is the better choice.
| Statistic | ABC | XYZ |
|---|---|---|
| Alpha (α) | −3.20% | 7.30% |
| Beta (β) | 0.60 | 0.97 |
| R² | 0.35 | 0.17 |
| Residual σ | 13.02% | 21.45% |
Risk–return read-out (single-index model):
Implications for the future (with the brokerage betas):
| Brokerage | β ABC | β XYZ |
|---|---|---|
| Regression | 0.60 | 0.97 |
| House A | 0.62 | 1.45 |
| House B | 0.71 | 1.25 |
ABC’s three beta estimates (0.60, 0.62, 0.71) are tightly clustered, so its beta — and therefore its forecast systematic risk — is reliable. XYZ’s estimates (0.97, 1.45, 1.25) are widely dispersed, consistent with its low R² and high residual risk, so its beta is far less reliable. An analyst should place more confidence in ABC’s risk forecast, treat XYZ’s beta as uncertain, and should not extrapolate either stock’s historical alpha into the future.
Correlation with the market ρ = 0.70, so R² = ρ² = 0.49 (systematic share).
Nonsystematic (specific) share = 1 − R² = 1 − 0.49 = 0.51 → 51%.
ρ with the world index = 1.0, E(R_M) = 11%, E(R_fund) = 9%, r_f = 3%. Using E(R) = r_f + β[E(R_M) − r_f]:
\[9\% = 3\% + \beta(11\% - 3\%) \;\Rightarrow\; \beta = \frac{6\%}{8\%} = 0.75\]
Implied beta = 0.75.
(d) Systematic risk.
(b) Beta measures only systematic risk, while standard deviation measures total risk (systematic + nonsystematic).
Assumption. The original problem includes T-bills with a risk-free rate r_f = 6%. The table reproduced below adds that row, which is needed to locate Portfolio R relative to the SML and CML.
| Portfolio | Avg. Return | σ | β |
|---|---|---|---|
| R | 11% | 10% | 0.5 |
| S&P 500 (M) | 14% | 12% | 1.0 |
| T-bills (r_f) | 6% | 0% | 0 |
SML required return for β = 0.5: \[E(R) = r_f + \beta(R_M - r_f) = 6\% + 0.5(14\% - 6\%) = 10\%\]
Actual return = 11% > 10%.
(c) Above the SML. R earns 11% versus a 10% required return for its beta — a positive alpha of +1%.
CML expected return at σ = 10%: \[E(R) = r_f + \frac{R_M - r_f}{\sigma_M}\,\sigma = 6\% + \frac{14\% - 6\%}{12\%}(10\%) = 6\% + 6.67\% = 12.67\%\]
Actual return = 11% < 12.67%.
(b) Below the CML. Although R plots above the SML (good reward for systematic risk), it lies below the CML, because it is not fully diversified — it carries firm-specific risk, so on a total-risk basis it underperforms an efficient combination of the market and T-bills.
Both portfolios have β = 1.0; A has high specific risk, B has low specific risk.
No. Under the CAPM, expected return is determined solely by systematic risk (beta). Since both portfolios have β = 1.0, the model predicts the same expected return. Firm-specific risk is diversifiable and therefore not priced — investors are not compensated for bearing it — so A’s higher specific risk does not justify a higher expected return.
Factor risk premia: real GDP = 8%, inflation = 2%.
| Fund | b(GDP) | b(Inflation) |
|---|---|---|
| High Growth | 1.25 | 1.50 |
| Large Cap | 0.75 | 1.25 |
| Utility | 1.00 | 2.00 |
\[E(R) = r_f + b_{GDP}\lambda_{GDP} + b_{infl}\lambda_{infl} = 4\% + 1.25(8\%) + 1.50(2\%) = 4\% + 10\% + 3\% = \mathbf{17\%}\]
APT equilibrium risk premium: \[0.75(8\%) + 1.25(2\%) = 6\% + 2.5\% = 8.5\% \;\Rightarrow\; E(R) = 4\% + 8.5\% = 12.5\%\]
Kwon’s fundamental estimate = “8.5% above the risk-free rate” = 12.5%.
No arbitrage opportunity exists. The APT-implied return (8.5% above r_f) exactly equals Kwon’s fundamental estimate (8.5% above r_f), so the fund is fairly priced.
Build a portfolio of the three funds with weights (w₁ High Growth, w₂ Large Cap, w₃ Utility) such that: weights sum to 1, GDP exposure = 1, inflation exposure = 0.
\[ \begin{cases} w_1 + w_2 + w_3 = 1 \\ 1.25 w_1 + 0.75 w_2 + 1.00 w_3 = 1 \\ 1.50 w_1 + 1.25 w_2 + 2.00 w_3 = 0 \end{cases} \]
Subtracting eq.1 from eq.2 ⇒ 0.25 w₁ − 0.25 w₂ = 0 ⇒ w₁ = w₂. Then 2w₁ + w₃ = 1 ⇒ w₃ = 1 − 2w₁. Substituting into eq.3: 2.75 w₁ + 2(1 − 2w₁) = 0 ⇒ −1.25 w₁ + 2 = 0 ⇒ w₁ = 1.6, so w₃ = 1 − 3.2.
(a) −2.2. The GDP Fund holds a weight of −2.2 in the Utility Fund (a short position), with w₁ = w₂ = 1.6.
(a) McCracken is correct and Stiles is wrong. The GDP Fund has unit sensitivity to real GDP and zero sensitivity to inflation — it is therefore exposed to GDP/business-cycle risk, not a stable income vehicle. It is not suitable for retirees seeking steady income (Stiles is wrong). It would perform well if growth-boosting supply-side policies succeed and real GDP rises (McCracken is correct).
ETFs analysed: SPY, QQQ, EEM, IWM, EFA, TLT, IYR, GLD (daily data from Yahoo, 2010 → today). Workflow: simple returns → monthly tibble → merge with Fama–French 3 factors → CAPM & FF GMV portfolios → rolling-window backtest.
# install.packages(c("tidyquant","tidyverse","timetk","lubridate",
# "quadprog","PerformanceAnalytics","frenchdata","scales"))
library(tidyquant)
library(tidyverse)
library(lubridate)
library(timetk)
library(quadprog)
library(PerformanceAnalytics)
library(frenchdata)
library(scales)
tickers <- c("SPY","QQQ","EEM","IWM","EFA","TLT","IYR","GLD")prices <- tq_get(tickers,
get = "stock.prices",
from = "2010-01-01",
to = Sys.Date())
# Adjusted daily closing prices in wide form
prices_wide <- prices %>%
select(symbol, date, adjusted) %>%
pivot_wider(names_from = symbol, values_from = adjusted) %>%
select(date, all_of(tickers)) %>%
arrange(date)
head(prices_wide)# Weekly simple (arithmetic) returns
weekly_ret <- prices %>%
group_by(symbol) %>%
tq_transmute(select = adjusted, mutate_fun = periodReturn,
period = "weekly", type = "arithmetic", col_rename = "ret") %>%
ungroup()
# Monthly simple (arithmetic) returns
monthly_ret <- prices %>%
group_by(symbol) %>%
tq_transmute(select = adjusted, mutate_fun = periodReturn,
period = "monthly", type = "arithmetic", col_rename = "ret") %>%
ungroup()
head(monthly_ret)monthly_wide <- monthly_ret %>%
pivot_wider(names_from = symbol, values_from = ret) %>%
arrange(date) %>%
# use first-of-month so it lines up with the Fama-French dates
mutate(date = floor_date(date, "month")) %>%
select(date, all_of(tickers))
head(monthly_wide)ff_raw <- download_french_data("Fama/French 3 Factors") # monthly = subset 1
ff_monthly <- ff_raw$subsets$data[[1]] %>%
as_tibble() %>%
transmute(
date = as.Date(paste0(date, "01"), format = "%Y%m%d"), # 201001 -> 2010-01-01
Mkt_RF = `Mkt-RF` / 100,
SMB = SMB / 100,
HML = HML / 100,
RF = RF / 100
)
# As xts (requested) and as tibble for the merge
ff_xts <- ff_monthly %>% tk_xts(silent = TRUE)
head(ff_monthly)These build the factor-implied covariance matrix and the global minimum-variance (GMV) weights used in Q6–Q8.
# CAPM single-index covariance: Sigma = beta beta' * var(Mkt) + diag(resid var)
estimate_capm_cov <- function(win, assets = tickers) {
X <- as.matrix(win[, assets])
exc <- sweep(X, 1, win$RF, "-") # excess returns
mkt <- win$Mkt_RF
betas <- numeric(length(assets)); rv <- numeric(length(assets))
for (i in seq_along(assets)) {
fit <- lm(exc[, i] ~ mkt)
betas[i] <- coef(fit)[2]
rv[i] <- mean(residuals(fit)^2) # residual (firm-specific) variance
}
(betas %o% betas) * var(mkt) + diag(rv)
}
# Fama-French 3-factor covariance: Sigma = B Omega B' + diag(resid var)
estimate_ff_cov <- function(win, assets = tickers) {
X <- as.matrix(win[, assets])
exc <- sweep(X, 1, win$RF, "-")
Fm <- as.matrix(win[, c("Mkt_RF","SMB","HML")])
B <- matrix(0, length(assets), 3); rv <- numeric(length(assets))
for (i in seq_along(assets)) {
fit <- lm(exc[, i] ~ Fm)
B[i, ] <- coef(fit)[2:4]
rv[i] <- mean(residuals(fit)^2)
}
B %*% cov(Fm) %*% t(B) + diag(rv)
}
# Closed-form GMV weights: w = Sigma^-1 1 / (1' Sigma^-1 1)
gmv_weights <- function(Sigma) {
ones <- rep(1, ncol(Sigma))
z <- solve(Sigma, ones)
as.numeric(z / sum(z))
}win_2015 <- merged %>% filter(date >= as.Date("2010-02-01"),
date <= as.Date("2015-01-01"))
stopifnot(nrow(win_2015) == 60) # 60-month estimation window
Sigma_capm <- estimate_capm_cov(win_2015)
w_capm <- gmv_weights(Sigma_capm)
names(w_capm) <- tickers
# Realized 8-asset returns in 2015/02
r_feb15 <- merged %>% filter(date == as.Date("2015-02-01")) %>%
select(all_of(tickers)) %>% as.numeric()
ret_capm_feb <- sum(w_capm * r_feb15)
round(w_capm, 4)## SPY QQQ EEM IWM EFA TLT IYR GLD
## 0.7744 -0.0131 -0.0362 -0.2035 -0.0358 0.4141 0.0374 0.0628
## CAPM GMV realized return, 2015/02 = -0.3486%
Sigma_ff <- estimate_ff_cov(win_2015)
w_ff <- gmv_weights(Sigma_ff)
names(w_ff) <- tickers
ret_ff_feb <- sum(w_ff * r_feb15)
round(w_ff, 4)## SPY QQQ EEM IWM EFA TLT IYR GLD
## 0.8821 -0.1441 -0.0433 -0.1135 -0.1044 0.4172 0.0369 0.0691
## FF 3-factor GMV realized return, 2015/02 = -0.6748%
# Side-by-side weights and realized Feb-2015 returns
tibble(
Asset = tickers,
CAPM_w = round(w_capm, 4),
FF3_w = round(w_ff, 4),
Ret_Feb = round(r_feb15, 4)
) %>%
bind_rows(tibble(Asset = "PORTFOLIO RETURN (Feb-2015)",
CAPM_w = round(ret_capm_feb, 4),
FF3_w = round(ret_ff_feb, 4),
Ret_Feb = NA_real_))For each month t, the prior 60 months (t−60 … t−1) are used to estimate the covariance matrix and the GMV weights, which are then applied to the realized returns in month t. SPY (buy-and-hold) is included as a benchmark.
invest_dates <- merged %>%
filter(date >= as.Date("2015-02-01"), date <= as.Date("2026-05-01")) %>%
pull(date)
res <- tibble(date = invest_dates,
CAPM = NA_real_, FF3 = NA_real_, SPY = NA_real_)
for (k in seq_along(invest_dates)) {
t <- invest_dates[k]
win <- merged %>% filter(date < t) %>% tail(60)
if (nrow(win) < 60) next
w_c <- gmv_weights(estimate_capm_cov(win))
w_f <- gmv_weights(estimate_ff_cov(win))
r_t <- merged %>% filter(date == t) %>%
select(all_of(tickers)) %>% as.numeric()
res$CAPM[k] <- sum(w_c * r_t)
res$FF3[k] <- sum(w_f * r_t)
res$SPY[k] <- r_t[which(tickers == "SPY")]
}
res <- res %>% drop_na()
head(res)cum <- res %>%
mutate(CAPM = cumprod(1 + CAPM),
FF3 = cumprod(1 + FF3),
SPY = cumprod(1 + SPY)) %>%
pivot_longer(c(CAPM, FF3, SPY), names_to = "Strategy", values_to = "Growth")
ggplot(cum, aes(date, Growth, colour = Strategy)) +
geom_line(linewidth = 0.9) +
scale_colour_manual(values = c(CAPM = "#2c7fb8", FF3 = "#d95f02", SPY = "#7f7f7f")) +
scale_y_continuous(labels = label_number(accuracy = 0.1)) +
labs(title = "Cumulative Growth of $1 — GMV Portfolios vs. SPY",
subtitle = "Rolling 60-month estimation window, Feb-2015 to May-2026",
x = NULL, y = "Growth of $1", colour = NULL) +
theme_minimal(base_size = 13) +
theme(legend.position = "top",
plot.title = element_text(face = "bold"))ret_xts <- xts(res[, c("CAPM","FF3","SPY")], order.by = res$date)
# Annualized return / volatility / Sharpe (Rf = 0 for comparison)
round(table.AnnualizedReturns(ret_xts, scale = 12, Rf = 0), 4)charts.PerformanceSummary(ret_xts,
main = "GMV Portfolios (CAPM vs FF3) and SPY",
colorset = c("#2c7fb8", "#d95f02", "#7f7f7f"))
Comments