Part I — Questions from the Textbook (60%)

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)


Chapter 7

CFA Problem 1 — Hennessy portfolio (40 → 20 stocks)

(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.

CFA Problem 2 — Reducing to 10 issues

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.

CFA Problem 3 — Viewing the change at the total-fund level

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.

CFA Problem 4 — Which portfolio cannot lie on the efficient frontier?

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.)

CFA Problem 10 — Choosing between portfolio (A,B) and (B,C)

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.


Chapter 8

CFA Problem 1 — Interpreting the regression output

Statistic ABC XYZ
Alpha (α) −3.20% 7.30%
Beta (β) 0.60 0.97
0.35 0.17
Residual σ 13.02% 21.45%

Risk–return read-out (single-index model):

  • Alpha. Over the sample, ABC underperformed its beta-implied return (α = −3.20%), while XYZ outperformed (α = +7.30%). Historical alphas, however, are noisy and tend to revert toward zero, so they are weak predictors of future performance.
  • Beta (systematic risk). ABC is defensive (β = 0.60); XYZ is near-market (β = 0.97). In a diversified portfolio, beta is the relevant risk measure.
  • R². ABC: 35% of total variance is explained by the market (so 65% is firm-specific). XYZ: only 17% is market-driven (83% firm-specific). A lower R² also means a less precise beta estimate (larger standard error).
  • Residual σ (firm-specific risk). XYZ’s 21.45% is much higher than ABC’s 13.02%. In a well-diversified portfolio this idiosyncratic risk is largely diversified away, so it matters less for a fully invested investor than it would for someone holding the stock alone.

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.

CFA Problem 2 — Baker Fund nonsystematic risk

Correlation with the market ρ = 0.70, so R² = ρ² = 0.49 (systematic share).

Nonsystematic (specific) share = 1 − R² = 1 − 0.49 = 0.51 → 51%.

CFA Problem 3 — Implied beta of Charlottesville International

ρ 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.

CFA Problem 4 — “Beta” is most closely associated with:

(d) Systematic risk.

CFA Problem 5 — Beta vs. standard deviation

(b) Beta measures only systematic risk, while standard deviation measures total risk (systematic + nonsystematic).


Chapter 9

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

CFA Problem 8 — Portfolio R relative to the SML

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%.

CFA Problem 9 — Portfolio R relative to the CML

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.

CFA Problem 10 — Should A earn more than B under CAPM?

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.


Chapter 10 — Orb Trust (Two-Factor APT)

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

Problem 13 — APT expected return of the High Growth Fund (r_f = 4%)

\[E(R) = r_f + b_{GDP}\lambda_{GDP} + b_{infl}\lambda_{infl} = 4\% + 1.25(8\%) + 1.50(2\%) = 4\% + 10\% + 3\% = \mathbf{17\%}\]

Problem 14 — Arbitrage in the Large Cap Fund?

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.

Problem 15 — Weight of the Utility Fund in the “GDP Fund”

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.

Problem 16 — For whom is the GDP Fund appropriate?

(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).


Part II — Questions Using R Code (40%)

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")

1. Import data

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)
tail(prices_wide)

2. Weekly and monthly simple returns

# 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)

3. Monthly returns in tibble (wide) format

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)

4. Fama–French 3 factors (as decimals, not percent)

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)

5. Merge monthly returns with the FF factors

merged <- monthly_wide %>%
  inner_join(ff_monthly, by = "date") %>%
  arrange(date)

head(merged)

Factor-model helper functions

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))
}

6. CAPM GMV portfolio — weights at 2015/01, realized return 2015/02

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
cat(sprintf("CAPM GMV realized return, 2015/02 = %.4f%%\n", 100 * ret_capm_feb))
## CAPM GMV realized return, 2015/02 = -0.3486%

7. Fama–French GMV portfolio — weights at 2015/01, realized return 2015/02

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
cat(sprintf("FF 3-factor GMV realized return, 2015/02 = %.4f%%\n", 100 * ret_ff_feb))
## 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_))

8. Rolling-window backtest (2015/02 → 2026/05)

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)

Cumulative returns

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"))

Performance summary

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

  • Both GMV portfolios use factor-model covariance estimates rather than the raw sample covariance, which gives more stable weights — especially helpful with only 60 monthly observations for 8 assets.
  • The CAPM single-index model imposes a single common factor, so its covariance matrix is the most parsimonious. The Fama–French 3-factor model adds the SMB and HML factors, allowing the estimated covariances to capture size- and value-related comovement that the market factor alone misses.
  • As minimum-variance strategies, both portfolios are expected to show lower volatility than SPY, typically at the cost of a different (often lower) total return — the annualized table above quantifies that risk–return trade-off over the full sample.