Questions from textbook (60%)

Chapter 7: CFA Problems 1, 2, 3, 4, 10

Background (CFA 1–3): Hennessy & Associates manages a $30 million equity portfolio for the multimanager Wilstead Pension Fund. Hennessy is a bottom-up stock picker holding about 40 stocks (2%–3% of funds each). Jones proposes limiting the portfolio to no more than 20 stocks.

CFA 1

a. Will the limitation to 20 stocks likely increase or decrease the risk of the portfolio? Explain.

The restriction to 20 stocks will most likely increase the risk of the portfolio. Going from 40 to 20 issues reduces diversification, so the firm-specific (nonsystematic) component of risk rises. However, the increase should be relatively small, because most of the benefit of diversification is already achieved with roughly 20–25 well-chosen stocks: the marginal risk reduction from adding stocks beyond 20 is modest, so removing them adds back only a modest amount of risk.

b. Is there any way Hennessy could reduce the number of issues from 40 to 20 without significantly affecting risk? Explain.

Yes. Hennessy can make the reduction in a deliberately diversified way rather than simply doubling up on its favorite names. By spreading the 20 remaining stocks across different industries, sectors, and risk characteristics (i.e., choosing stocks whose returns have relatively low correlations with each other — for example through stratified sampling across sectors or a simple optimization), the portfolio keeps most of its diversification, and total risk will be only slightly higher than with 40 stocks.

CFA 2

Reduction from 40 to 20 stocks is less harmful than a further reduction from 20 to 10 because diversification benefits diminish at a decreasing rate — equivalently, concentration costs accelerate. The portfolio standard deviation as a function of the number of holdings is convex: dropping from 40 to 20 stocks adds only a small amount of nonsystematic risk, but dropping from 20 to 10 adds much more nonsystematic risk. Therefore, while the gain from concentrating money in Hennessy’s 10 very best ideas might add some extra expected return, that gain would have to overcome a far larger increase in risk than the move to 20 stocks did. Evaluated as a stand-alone portfolio (as Wilstead proposes to do), the reduction to 10 issues is therefore less likely to be advantageous than the reduction to 20.

CFA 3

The committee member’s point is that Hennessy’s portfolio should not be judged in isolation: it is only about $30 million out of an aggregate fund of roughly $280 million (the other five managers run $250 million spread over more than 150 issues). At the total fund level, Hennessy’s firm-specific risk is largely diversified away by the holdings of the other managers. Hence the increase in nonsystematic risk caused by concentrating Hennessy’s portfolio in 20 — or even 10 — stocks has only a negligible effect on the total fund’s risk. Taking this broader view, the committee can comfortably allow the more concentrated portfolio (and capture more of Hennessy’s stock-picking skill), since the relevant risk to the plan is the risk of the overall fund, not of each sleeve separately.

CFA 4

Which portfolio cannot lie on the efficient frontier?

Portfolio Expected Return (%) Standard Deviation (%)
a. W 15 36
b. X 12 15
c. Z 5 7
d. Y 9 21

Answer: (d) Portfolio Y. Portfolio Y is dominated by portfolio X, which offers a higher expected return (12% > 9%) with a lower standard deviation (15% < 21%). A dominated portfolio can never lie on the Markowitz efficient frontier. None of the other portfolios is dominated by another portfolio in the table.

CFA 10

Standard deviations: \(\sigma_A = 40\%\), \(\sigma_B = 20\%\), \(\sigma_C = 40\%\); correlations: \(\rho_{AB}=0.90\), \(\rho_{AC}=0.50\), \(\rho_{BC}=0.10\). Compare an equally weighted portfolio of A and B with an equally weighted portfolio of B and C:

\[\sigma_p^2 = w_1^2\sigma_1^2 + w_2^2\sigma_2^2 + 2w_1 w_2 \rho_{12}\sigma_1\sigma_2\]

sd_AB <- sqrt(0.5^2*40^2 + 0.5^2*20^2 + 2*0.5*0.5*0.90*40*20)
sd_BC <- sqrt(0.5^2*20^2 + 0.5^2*40^2 + 2*0.5*0.5*0.10*20*40)
c(`SD of 50/50 A&B (%)` = sd_AB, `SD of 50/50 B&C (%)` = sd_BC)
## SD of 50/50 A&B (%) SD of 50/50 B&C (%) 
##            29.32576            23.23790

Recommendation: the portfolio of B and C. Its standard deviation (23.24%) is clearly lower than that of the A&B portfolio (29.33%). Even though B and C individually have the same volatilities as B and A (20% and 40%), the much lower correlation between B and C (0.10 versus 0.90) delivers a far greater diversification benefit. Since the tables give no information about expected returns, the lower-risk combination B&C is the rational choice.

Chapter 8: CFA Problems 1, 2, 3, 4, 5

CFA 1

Regression of annualized monthly excess returns on the market excess return:

Statistic ABC XYZ
Alpha −3.20% 7.3%
Beta 0.60 0.97
\(R^2\) 0.35 0.17
Residual std. dev. 13.02% 21.45%

What the results tell the analyst about the sample period:

  • Alpha is the average return earned beyond what CAPM/the index model predicts. ABC’s alpha of −3.2% means it underperformed on a risk-adjusted basis over the five years, while XYZ’s +7.3% means it outperformed.
  • Beta measures systematic (market) risk. ABC (\(\beta=0.60\)) was defensive — its returns moved much less than the market — while XYZ (\(\beta=0.97\)) moved essentially one-for-one with the market.
  • \(R^2\) gives the fraction of total variance explained by the market: 35% for ABC but only 17% for XYZ. Hence most of each stock’s risk is firm-specific, especially for XYZ, which also has the larger residual standard deviation (21.45% vs. 13.02%).

Implications for the future:

  • Alphas are unlikely to persist. They are estimated with large sampling error (note the huge residual standard deviations) and historical alphas are poor predictors of future alphas. One should not project ABC’s underperformance or XYZ’s outperformance forward.
  • Betas are more stable and more useful for prediction, but the brokerage data raise a flag for XYZ: the two houses estimate XYZ’s beta at 1.45 and 1.25 using the most recent two years of weekly data — well above the five-year estimate of 0.97. This suggests XYZ’s systematic risk has increased and that XYZ should be treated as an aggressive, higher-than-average-risk stock going forward, whereas ABC’s beta estimates (0.60, 0.62, 0.71) consistently portray a low-beta, defensive stock.
  • In a well-diversified portfolio, the residual (firm-specific) risk of each stock is largely diversified away, so the relevant risk measure is beta. ABC would add relatively little systematic risk; XYZ would add about market-level (and possibly greater) systematic risk.

CFA 2

If \(\rho\) between Baker Fund and the market is 0.70, the proportion of total risk that is systematic is \(\rho^2 = 0.70^2 = 0.49\). Therefore the fraction of total risk that is specific (nonsystematic) is:

\[1-\rho^2 = 1 - 0.49 = 0.51 = \textbf{51\%}\]

CFA 3

Correlation with the world index is 1.0, so the fund lies exactly on the regression line and CAPM/SML pricing applies:

\[E(r) = r_f + \beta\,[E(r_M) - r_f] \;\Rightarrow\; 9\% = 3\% + \beta\,(11\% - 3\%)\]

\[\beta = \frac{9\% - 3\%}{11\% - 3\%} = \frac{6}{8} = \textbf{0.75}\]

CFA 4

The concept of beta is most closely associated with:

Answer: (d) systematic risk. Beta measures the sensitivity of a security’s return to the market return, i.e., its systematic (market, non-diversifiable) risk.

CFA 5

Beta and standard deviation differ as risk measures in that beta measures:

Answer: (b) — beta measures only systematic risk, while standard deviation measures total risk (systematic + unsystematic).

Chapter 9: CFA Problems 8, 9, 10

Data for CFA 8 and 9:

Portfolio Avg. Annual Return Standard Deviation Beta
R 11% 10% 0.5
S&P 500 14% 12% 1.0

CFA 8

When plotting portfolio R relative to the SML, portfolio R lies:

Answer: (d) Insufficient data given — you need to know the risk-free rate.

The SML is the line from the risk-free rate through the market portfolio in \((\beta, E(r))\) space: \(E(r) = r_f + \beta\,[E(r_M) - r_f]\). The table gives the market’s return (14%) and portfolio R’s beta (0.5) and return (11%), but not the risk-free rate, so the SML cannot be drawn and R’s position relative to it cannot be determined. At \(\beta = 0.5\) the required return is \(7\% + 0.5\,r_f\): if \(r_f < 8\%\) portfolio R (11%) would plot above the SML, and if \(r_f > 8\%\) it would plot below — the answer depends on the missing risk-free rate.

CFA 9

When plotting portfolio R relative to the capital market line (CML), portfolio R lies:

Answer: (d) Insufficient data given — you need to know the risk-free rate.

The CML is the line from the risk-free rate through the market portfolio in \((\sigma, E(r))\) space: \(E(r) = r_f + \dfrac{E(r_M)-r_f}{\sigma_M}\,\sigma\). Without the risk-free rate the CML cannot be drawn, so R’s position relative to it cannot be plotted. (Note that for any plausible non-negative \(r_f\), the CML at \(\sigma = 10\%\) would give \(11.67\% + 0.167\,r_f > 11\%\), i.e., R would lie below the CML — it bears some nonsystematic risk, and only perfectly diversified portfolios reach the CML — but strictly the line itself requires \(r_f\) to be specified.)

CFA 10

Portfolio A Portfolio B
Systematic risk (beta) 1.0 1.0
Specific risk for each individual security High Low

No — under the CAPM investors should expect the same return on both portfolios. The CAPM prices only systematic risk: expected return depends solely on beta, \(E(r)=r_f+\beta[E(r_M)-r_f]\). Both portfolios have \(\beta = 1.0\), so both have an expected return equal to the market’s. The high specific (firm-level) risk of portfolio A is diversifiable — the market does not compensate investors for risk they can eliminate for free by diversifying — so it commands no additional risk premium.

Chapter 10: Problems 13–16 (Orb Trust / APT)

Setup: Two-factor APT model with factor risk premiums: real GDP = 8%, inflation = 2%. Sensitivities (GDP, inflation): High Growth Fund (1.25, 1.5); Large Cap Fund (0.75, 1.25); Utility Fund (1.0, 2.0).

Problem 13

With \(r_f = 4\%\), the APT expected return of the High Growth Fund:

\[E(r) = 4\% + 1.25\times 8\% + 1.5\times 2\% = 4\% + 10\% + 3\% = \textbf{17\%}\]

Problem 14

No arbitrage opportunity is available. The APT estimate for the Large Cap Fund is

\[E(r) = r_f + 0.75\times 8\% + 1.25\times 2\% = r_f + 6\% + 2.5\% = r_f + 8.5\%,\]

which is exactly the fundamental estimate Kwon provides (8.5% above the risk-free rate). Since the APT equilibrium expected return equals the fundamentally expected return, the fund is fairly priced and no arbitrage (long/short) profit can be constructed.

Problem 15

Find weights \((w_H, w_L, w_U)\) such that the portfolio has GDP sensitivity 1 and inflation sensitivity 0:

\[\begin{cases} w_H + w_L + w_U = 1 \\ 1.25 w_H + 0.75 w_L + 1.0 w_U = 1 \\ 1.5 w_H + 1.25 w_L + 2.0 w_U = 0 \end{cases}\]

A <- rbind(c(1, 1, 1),
           c(1.25, 0.75, 1.0),
           c(1.5, 1.25, 2.0))
b <- c(1, 1, 0)
w <- solve(A, b)
round(setNames(w, c("High Growth", "Large Cap", "Utility")), 2)
## High Growth   Large Cap     Utility 
##         1.6         1.6        -2.2

Subtracting the first equation from the second gives \(w_H = w_L\); substituting into the system yields \(w_H = w_L = 1.6\) and \(w_U = 1 - 3.2 = -2.2\).

Answer: (a) −2.2 — the GDP Fund shorts the Utility Fund with a weight of −2.2.

Problem 16

Answer: (a) McCracken is correct and Stiles is wrong.

Stiles is wrong because the GDP Fund is not suitable for retirees who live off steady income: the fund is constructed to have unit exposure to real GDP risk, i.e., it is deliberately cyclical, and bearing factor risk means returns will fluctuate with the business cycle — exactly what income-dependent retirees should avoid. McCracken is correct: because the fund has full exposure to real GDP growth and zero exposure to inflation, it would benefit if supply-side macroeconomic policies succeed (higher real growth), making it a good choice for investors who want to bet on that outcome.


Questions using R codes (40%)

# Load libraries
library(tidyquant)
library(lubridate)
library(timetk)
library(purrr)
library(tidyverse)
library(PerformanceAnalytics)   # Return.calculate(), table.AnnualizedReturns()
library(xts)                    # endpoints(), xts objects

1. Import data

Download ETF daily data from Yahoo for SPY, QQQ, EEM, IWM, EFA, TLT, IYR and GLD from 2010 to the current date, and extract the adjusted daily closing prices.

tickers <- c("SPY", "QQQ", "EEM", "IWM", "EFA", "TLT", "IYR", "GLD")

prices_raw <- tq_get(tickers, get = "stock.prices",
                     from = "2010-01-01", to = Sys.Date())

# Adjusted daily closing prices in wide xts format
prices_xts <- prices_raw %>%
  select(symbol, date, adjusted) %>%
  pivot_wider(names_from = symbol, values_from = adjusted) %>%
  select(date, all_of(tickers)) %>%
  tk_xts(date_var = date)

head(prices_xts)
##                 SPY      QQQ      EEM      IWM      EFA      TLT      IYR
## 2010-01-04 84.79636 40.29078 30.35151 51.36657 35.12844 55.70951 26.76811
## 2010-01-05 85.02083 40.29078 30.57181 51.18992 35.15939 56.06933 26.83238
## 2010-01-06 85.08072 40.04777 30.63576 51.14178 35.30801 55.31875 26.82070
## 2010-01-07 85.43987 40.07380 30.45811 51.51911 35.17178 55.41182 27.06028
## 2010-01-08 85.72420 40.40363 30.69972 51.80010 35.45043 55.38696 26.87913
## 2010-01-11 85.84391 40.23871 30.63576 51.59136 35.74148 55.08308 27.00767
##               GLD
## 2010-01-04 109.80
## 2010-01-05 109.70
## 2010-01-06 111.51
## 2010-01-07 110.82
## 2010-01-08 111.37
## 2010-01-11 112.85
tail(prices_xts)
##               SPY    QQQ   EEM    IWM    EFA   TLT    IYR    GLD
## 2026-06-02 759.57 746.16 70.80 291.66 105.02 85.65  99.99 411.95
## 2026-06-03 754.24 744.21 69.92 287.67 104.12 85.31 100.00 407.87
## 2026-06-04 757.09 740.61 69.10 292.01 104.95 85.50 101.79 411.27
## 2026-06-05 737.55 705.06 64.59 281.65 102.26 85.06 102.54 396.24
## 2026-06-08 739.22 716.07 65.75 284.11 102.88 84.62 101.08 397.27
## 2026-06-09 737.05 707.83 65.82 285.02 102.90 85.12 103.49 390.78

2. Calculate weekly and monthly returns using simple returns

# Weekly simple (arithmetic) returns
ret_w_xts <- prices_xts[endpoints(prices_xts, on = "weeks")] %>%
  Return.calculate(method = "discrete") %>%
  na.omit()

# Monthly simple (arithmetic) returns
ret_m_xts <- prices_xts[endpoints(prices_xts, on = "months")] %>%
  Return.calculate(method = "discrete") %>%
  na.omit()

head(round(ret_w_xts, 4))
##                SPY     QQQ     EEM     IWM     EFA     TLT     IYR     GLD
## 2010-01-15 -0.0081 -0.0150 -0.0289 -0.0130 -0.0035  0.0200 -0.0063 -0.0046
## 2010-01-22 -0.0390 -0.0369 -0.0558 -0.0306 -0.0557  0.0101 -0.0418 -0.0333
## 2010-01-29 -0.0167 -0.0310 -0.0336 -0.0262 -0.0258  0.0034 -0.0084 -0.0113
## 2010-02-05 -0.0068  0.0044 -0.0282 -0.0140 -0.0191 -0.0001  0.0032 -0.0121
## 2010-02-12  0.0129  0.0181  0.0333  0.0295  0.0052 -0.0195 -0.0076  0.0225
## 2010-02-19  0.0287  0.0245  0.0245  0.0334  0.0230 -0.0082  0.0502  0.0227
head(round(ret_m_xts, 4))
##                SPY     QQQ     EEM     IWM     EFA     TLT     IYR     GLD
## 2010-02-26  0.0312  0.0460  0.0178  0.0448  0.0027 -0.0034  0.0546  0.0327
## 2010-03-31  0.0609  0.0771  0.0811  0.0823  0.0639 -0.0206  0.0975 -0.0044
## 2010-04-30  0.0155  0.0224 -0.0017  0.0568 -0.0280  0.0332  0.0639  0.0588
## 2010-05-28 -0.0795 -0.0739 -0.0939 -0.0754 -0.1119  0.0511 -0.0568  0.0305
## 2010-06-30 -0.0517 -0.0598 -0.0140 -0.0774 -0.0206  0.0580 -0.0467  0.0236
## 2010-07-30  0.0683  0.0726  0.1093  0.0673  0.1161 -0.0095  0.0940 -0.0509

3. Convert monthly returns into tibble format

Hint: use as_tibble(., rownames = 'date') or tk_tbl(., rename_index = 'date').

ret_m_tbl <- ret_m_xts %>%
  tk_tbl(rename_index = "date") %>%
  # use the first day of each month as the date key (for merging with FF data)
  mutate(date = floor_date(as.Date(date), "month"))

ret_m_tbl

4. Download Fama French 3 factors data and change to digit numbers (not in percentage)

Source: Ken French data library — Fama/French 3 factor monthly returns (Mkt-RF, SMB and HML), converted to xts data.

ff_url <- "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip"
tmp <- tempfile(fileext = ".zip")
download.file(ff_url, tmp, mode = "wb", quiet = TRUE)
csv_file <- unzip(tmp, exdir = tempdir())

ff3 <- read_csv(csv_file, skip = 3, col_types = cols(.default = "c")) %>%
  rename(date = 1, Mkt.RF = `Mkt-RF`) %>%
  filter(grepl("^\\d{6}$", date)) %>%            # keep monthly rows only
  mutate(date = ymd(paste0(date, "01")),
         across(-date, ~ as.numeric(.) / 100))   # percent -> decimal digits

# convert into xts
ff3_xts <- tk_xts(ff3, date_var = date)
head(ff3_xts)
##             Mkt.RF     SMB     HML     RF
## 1926-07-01  0.0289 -0.0255 -0.0239 0.0022
## 1926-08-01  0.0264 -0.0114  0.0381 0.0025
## 1926-09-01  0.0038 -0.0136  0.0005 0.0023
## 1926-10-01 -0.0327 -0.0014  0.0082 0.0032
## 1926-11-01  0.0254 -0.0011 -0.0061 0.0031
## 1926-12-01  0.0262 -0.0007  0.0006 0.0028
tail(ff3_xts)
##             Mkt.RF     SMB     HML     RF
## 2025-11-01 -0.0013  0.0054  0.0357 0.0030
## 2025-12-01 -0.0036 -0.0103  0.0236 0.0034
## 2026-01-01  0.0103  0.0212  0.0386 0.0030
## 2026-02-01 -0.0117  0.0024  0.0265 0.0028
## 2026-03-01 -0.0518  0.0044  0.0335 0.0029
## 2026-04-01  0.0994  0.0013 -0.0127 0.0029

5. Merge monthly return data in question 3 and 4 into tibble format

merged <- ret_m_tbl %>%
  inner_join(ff3, by = "date") %>%
  arrange(date)

merged
range(merged$date)
## [1] "2010-02-01" "2026-04-01"

6. CAPM model: GMV portfolio weights on 2015/01 and realized return on 2015/02

Based on the CAPM (single-index) model, the covariance matrix of the 8-asset portfolio is estimated from historical 60-month returns (2010/02–2015/01):

\[\hat\Sigma_{CAPM} = \hat\beta\hat\beta'\,\hat\sigma_M^2 + \mathrm{diag}(\hat\sigma_{\varepsilon,1}^2,\dots,\hat\sigma_{\varepsilon,8}^2)\]

The global minimum variance (GMV) weights are \(w_{GMV} = \dfrac{\Sigma^{-1}\mathbf{1}}{\mathbf{1}'\Sigma^{-1}\mathbf{1}}\).

# --- helper: GMV weights from a covariance matrix ---
gmv_weights <- function(Sigma) {
  ones <- rep(1, ncol(Sigma))
  w <- solve(Sigma, ones)
  w / sum(w)
}

# --- helper: covariance matrix from CAPM (single index) model ---
cov_capm <- function(window_df, assets) {
  R   <- as.matrix(window_df[, assets])
  exc <- R - window_df$RF             # excess returns of the 8 assets
  mkt <- window_df$Mkt.RF             # market excess return
  fit <- lm(exc ~ mkt)
  betas    <- coef(fit)[2, ]
  res_var  <- apply(resid(fit), 2, var)
  Sigma <- outer(betas, betas) * var(mkt) + diag(res_var)
  dimnames(Sigma) <- list(assets, assets)
  Sigma
}

# Estimation window: 2010/02 - 2015/01 (60 months)
win_60 <- merged %>%
  filter(date >= ymd("2010-02-01"), date <= ymd("2015-01-01"))
nrow(win_60)   # = 60 months
## [1] 60
Sigma_capm <- cov_capm(win_60, tickers)
w_capm <- gmv_weights(Sigma_capm)

round(Sigma_capm * 1e4, 3)      # covariance matrix (x 10^-4)
##         SPY     QQQ     EEM     IWM     EFA     TLT    IYR    GLD
## SPY  13.967  14.591  17.258  18.285  15.808 -10.115 11.790  2.351
## QQQ  14.591  18.156  18.148  19.227  16.623 -10.636 12.397  2.472
## EEM  17.258  18.148  33.498  22.741  19.661 -12.580 14.663  2.923
## IWM  18.285  19.227  22.741  26.990  20.830 -13.328 15.535  3.097
## EFA  15.808  16.623  19.661  20.830  24.130 -11.523 13.431  2.678
## TLT -10.115 -10.636 -12.580 -13.328 -11.523  16.211 -8.594 -1.713
## IYR  11.790  12.397  14.663  15.535  13.431  -8.594 20.246  1.997
## GLD   2.351   2.472   2.923   3.097   2.678  -1.713  1.997 28.987
round(w_capm, 4)                # optimal GMV weights on 2015/01
##     SPY     QQQ     EEM     IWM     EFA     TLT     IYR     GLD 
##  0.7748 -0.0130 -0.0361 -0.2029 -0.0357  0.4131  0.0373  0.0626
# Realized portfolio return on 2015/02
r_201502 <- merged %>% filter(date == ymd("2015-02-01"))
ret_capm_201502 <- sum(w_capm * as.numeric(r_201502[, tickers]))
ret_capm_201502
## [1] -0.003340795

Answer: allocating the 8 assets with the CAPM-based GMV weights estimated on 2015/01, the realized portfolio return in 2015/02 is -0.0033 (= -0.33%).

7. FF 3-factor model: GMV portfolio weights on 2015/01 and realized return on 2015/02

Based on the Fama-French 3-factor model the covariance matrix is

\[\hat\Sigma_{FF3} = B\,\hat\Sigma_F\,B' + \mathrm{diag}(\hat\sigma_{\varepsilon,1}^2,\dots,\hat\sigma_{\varepsilon,8}^2),\]

where \(B\) is the \(8\times3\) matrix of factor loadings on (Mkt-RF, SMB, HML) and \(\hat\Sigma_F\) is the sample covariance matrix of the factors.

# --- helper: covariance matrix from the FF 3-factor model ---
cov_ff3 <- function(window_df, assets) {
  R   <- as.matrix(window_df[, assets])
  exc <- R - window_df$RF
  Fm  <- as.matrix(window_df[, c("Mkt.RF", "SMB", "HML")])
  fit <- lm(exc ~ Fm)
  B       <- t(coef(fit)[-1, ])       # 8 x 3 factor loadings
  res_var <- apply(resid(fit), 2, var)
  Sigma <- B %*% cov(Fm) %*% t(B) + diag(res_var)
  dimnames(Sigma) <- list(assets, assets)
  Sigma
}

Sigma_ff3 <- cov_ff3(win_60, tickers)
w_ff3 <- gmv_weights(Sigma_ff3)

round(Sigma_ff3 * 1e4, 3)       # covariance matrix (x 10^-4)
##         SPY    QQQ     EEM     IWM     EFA     TLT    IYR    GLD
## SPY  13.967 14.640  17.248  17.868  16.007 -10.078 11.787  2.038
## QQQ  14.640 18.156  18.440  18.697  17.131  -9.951 12.481  3.372
## EEM  17.248 18.440  33.498  22.700  19.801 -12.276 14.704  3.504
## IWM  17.868 18.697  22.700  26.990  19.433 -13.794 15.521  4.691
## EFA  16.007 17.131  19.801  19.433  24.130 -11.036 13.472  2.368
## TLT -10.078 -9.951 -12.276 -13.794 -11.036  16.211 -8.506 -0.718
## IYR  11.787 12.481  14.704  15.521  13.472  -8.506 20.246  2.163
## GLD   2.038  3.372   3.504   4.691   2.368  -0.718  2.163 28.987
round(w_ff3, 4)                 # optimal GMV weights on 2015/01
##     SPY     QQQ     EEM     IWM     EFA     TLT     IYR     GLD 
##  0.8828 -0.1425 -0.0431 -0.1153 -0.1037  0.4159  0.0368  0.0691
# Realized portfolio return on 2015/02
ret_ff3_201502 <- sum(w_ff3 * as.numeric(r_201502[, tickers]))
ret_ff3_201502
## [1] -0.006559848
# Compare the two models' weights
tibble(asset = tickers,
       `GMV weight (CAPM)` = round(w_capm, 4),
       `GMV weight (FF3)`  = round(w_ff3, 4))

Answer: with the FF 3-factor-based GMV weights estimated on 2015/01, the realized portfolio return in 2015/02 is -0.0066 (= -0.66%).

8. Backtest the GMV portfolios based on CAPM and FF 3-factor models

Rolling-window backtest (implemented directly with R code instead of the SIT package, which the question allows):

  • For each month \(t\), the optimal GMV weights are estimated from the historical 60-month returns \(t-60\) to \(t-1\); the rolling window starts from 2010/02–2015/01.
  • The weights are then used to invest in month \(t\), producing the monthly GMV portfolio returns from 2015/02 to 2026/5.
  • Finally we show the cumulative returns of the portfolios constructed by the CAPM model and the three-factor model from 2015/2 to 2026/5.
# Investment months: 2015/02 - 2026/05. The estimation window (t-60 ... t-1)
# needs the FF factors, but the realized return in month t only needs the
# asset returns, so we take r_t from ret_m_tbl (the FF factor file is
# published with a lag and currently ends one month earlier).
bt_dates <- ret_m_tbl %>%
  filter(date >= ymd("2015-02-01"), date <= ymd("2026-05-01")) %>%
  pull(date)

backtest <- map_dfr(bt_dates, function(t) {
  win <- merged %>% filter(date < t) %>% slice_tail(n = 60)  # t-60 ... t-1
  r_t <- ret_m_tbl %>% filter(date == t)
  w1  <- gmv_weights(cov_capm(win, tickers))
  w2  <- gmv_weights(cov_ff3(win, tickers))
  tibble(date     = t,
         GMV_CAPM = sum(w1 * as.numeric(r_t[, tickers])),
         GMV_FF3  = sum(w2 * as.numeric(r_t[, tickers])))
})

head(backtest)
tail(backtest)
cumret <- backtest %>%
  mutate(`GMV (CAPM)` = cumprod(1 + GMV_CAPM) - 1,
         `GMV (FF3)`  = cumprod(1 + GMV_FF3) - 1) %>%
  select(date, `GMV (CAPM)`, `GMV (FF3)`) %>%
  pivot_longer(-date, names_to = "Model", values_to = "cum_return")

ggplot(cumret, aes(x = date, y = cum_return, color = Model)) +
  geom_line(linewidth = 0.9) +
  scale_y_continuous(labels = scales::percent) +
  scale_color_manual(values = c("GMV (CAPM)" = "#d62728",
                                "GMV (FF3)"  = "#1f77b4")) +
  labs(title = "Cumulative returns of GMV portfolios, 2015/02 - 2026/05",
       subtitle = "Rolling 60-month estimation window; CAPM vs Fama-French 3-factor covariance",
       x = NULL, y = "Cumulative return") +
  theme_tq() +
  theme(legend.position = "bottom")

bt_xts <- backtest %>% tk_xts(date_var = date)

# Annualized performance of the two GMV strategies
table.AnnualizedReturns(bt_xts, Rf = mean(merged$RF)) %>% round(4)
# Total cumulative return over the full backtest
round(apply(1 + bt_xts, 2, prod) - 1, 4)
## GMV_CAPM  GMV_FF3 
##    1.318    0.686

Comments: Both global-minimum-variance strategies deliver smooth, low-volatility performance, as designed. The two equity-factor models produce similar — but not identical — covariance estimates: the FF 3-factor model captures additional comovement through the SMB and HML factors, so its GMV weights and realized path differ somewhat from the single-index (CAPM) version. The cumulative-return chart above compares the two portfolios over the whole out-of-sample period 2015/02–2026/05.


Note: answers were written in R Markdown, knitted to HTML, and posted on RPubs as required.