1 Introduction

This document presents a complete, fully elaborated solution to the Investment Portfolio Management midterm examination. Every numerical result is accompanied by its economic interpretation — the hallmark of rigorous financial analysis rather than rote calculation.

The analysis is structured around two major pillars:

  • Part I (Computer Questions, 40%) — Empirical portfolio construction using CAPM and the Fama-French Three-Factor Model, applied to an 8-ETF universe over a rolling 60-month estimation window. We go beyond reporting weights: we explain why the models produce different allocations and what that means for a real investor.

  • Part II (Theory Questions, 60%) — Textbook and CFA problems spanning Chapters 5 through 8 of Bodie, Kane, and Marcus Investments (12th ed.). Each answer is expanded with intuition, formulas, and real-world relevance.

Key Theme: Correlation structure — not just individual risk-return characteristics — is the master variable in portfolio construction. Investors who grasp this insight are better positioned to build resilient, efficient portfolios that endure across market regimes.


2 Part I: Computer Questions (40%)

2.1 Data Preparation

2.1.1 Required Libraries

The following R packages power this analysis. Each library plays a distinct role: quantmod handles financial data retrieval; PerformanceAnalytics provides risk-return metrics; quadprog solves the constrained optimization; and the tidyverse family handles data wrangling and visualization.

library(quantmod)
library(tidyverse)
library(PerformanceAnalytics)
library(xts)
library(zoo)
library(quadprog)
library(knitr)
library(kableExtra)

2.1.2 Step 1: Download ETF Data from Yahoo Finance (2010–2025)

The eight ETFs were selected to span major asset classes, ensuring genuine diversification across equity styles, geographies, sectors, and alternative assets. This breadth is deliberate — when assets respond differently to macroeconomic shocks, their correlations are lower, and the benefits of portfolio optimization are most pronounced.

Ticker Asset Class Why Included
SPY US Large-Cap Equity (S&P 500) Core US market exposure
QQQ US Technology / NASDAQ-100 Growth factor tilt
EEM Emerging Market Equity Geographic diversification, EM risk premium
IWM US Small-Cap Equity (Russell 2000) Size factor exposure
EFA Developed International Equity Ex-US developed market diversifier
TLT US Long-Term Treasury Bonds Duration risk, flight-to-quality hedge
IYR US Real Estate (REITs) Inflation hedge, income stream
GLD Gold / Commodity Crisis hedge, low-/negative-correlation asset
tickers <- c("SPY", "QQQ", "EEM", "IWM", "EFA", "TLT", "IYR", "GLD")

# Download adjusted prices (accounts for dividends and stock splits)
getSymbols(tickers,
           src  = "yahoo",
           from = "2010-01-01",
           to   = "2025-04-30",
           auto.assign = TRUE)
## [1] "SPY" "QQQ" "EEM" "IWM" "EFA" "TLT" "IYR" "GLD"
# Extract adjusted close prices and merge into one xts object
prices_list  <- lapply(tickers, function(tk) Ad(get(tk)))
prices_daily <- do.call(merge, prices_list)
colnames(prices_daily) <- tickers

cat("Daily price matrix:", nrow(prices_daily), "rows x", ncol(prices_daily), "columns\n")
## Daily price matrix: 3854 rows x 8 columns
cat("Date range:", format(index(prices_daily)[1]), "to",
    format(index(prices_daily)[nrow(prices_daily)]), "\n")
## Date range: 2010-01-04 to 2025-04-29

2.1.3 Step 2: Compute Monthly Discrete Returns

We use discrete (simple) returns\(R_t = (P_t - P_{t-1}) / P_{t-1}\) — rather than log returns, as required by the exam. Monthly prices are taken as the last observation in each calendar month, which avoids bid-ask noise and dividend timing issues.

Why simple returns, not log returns? Log returns are additive across time but not across assets. Simple returns are additive across assets (portfolio return = weighted sum), making them the natural choice for cross-sectional portfolio analysis. Log returns are preferable for time-series properties (e.g., continuously compounded growth), but simple returns align with how portfolio returns are actually calculated.

# Aggregate to monthly end-of-month prices
prices_monthly <- to.monthly(prices_daily, indexAt = "lastof", OHLC = FALSE)

# Discrete (simple) returns: R_t = (P_t - P_{t-1}) / P_{t-1}
etf_returns <- Return.calculate(prices_monthly, method = "discrete")
etf_returns <- na.omit(etf_returns)

cat("Monthly return matrix:", nrow(etf_returns), "months x", ncol(etf_returns), "ETFs\n")
## Monthly return matrix: 183 months x 8 ETFs
cat("Period:", format(index(etf_returns)[1]), "to",
    format(index(etf_returns)[nrow(etf_returns)]), "\n")
## Period: 2010-02-28 to 2025-04-30

2.1.4 Step 3: Load and Clean Fama-French 3-Factor Data

The Fama-French factors are the bedrock of modern empirical asset pricing. Loaded from the pre-downloaded CSV, they are converted from percentage to decimal form before merging.

Factor Definitions:

  • Mkt-RF (Market Risk Premium): Excess return of the value-weighted market portfolio over the risk-free rate. This is the central variable in CAPM — compensation for bearing undiversifiable systematic risk. Economically, it reflects the collective risk aversion of investors who demand payment for holding the market portfolio through booms and busts.

  • SMB (Small Minus Big): Return spread between small-cap and large-cap stocks. This captures the size premium — the empirical regularity (documented by Banz, 1981 and Fama-French, 1992) that small firms earn higher average returns, plausibly because they carry greater distress risk and are harder to arbitrage away.

  • HML (High Minus Low): Return spread between value stocks (high book-to-market) and growth stocks (low book-to-market). This captures the value premium — the finding that cheap, out-of-favor stocks outperform glamour stocks over long horizons, possibly due to exposure to economic distress risk or behavioral mispricing.

  • RF: Monthly risk-free rate (1-month T-bill). This is the opportunity cost of any investment — the return available with zero risk.

These factors are not merely statistical constructs — they represent persistent, economically meaningful return premia documented across decades and international markets.

ff_raw <- read.csv("F-F_Research_Data_Factors.csv",
                   skip = 3, header = TRUE, stringsAsFactors = FALSE)

# Keep only monthly rows (6-digit YYYYMM)
ff_raw <- ff_raw[nchar(trimws(ff_raw[,1])) == 6, ]
ff_raw <- ff_raw[!is.na(suppressWarnings(as.numeric(trimws(ff_raw[,1])))), ]

colnames(ff_raw)[1] <- "Date"
ff_raw$Date <- as.numeric(trimws(ff_raw$Date))

for (col in c("Mkt.RF", "SMB", "HML", "RF")) {
  ff_raw[[col]] <- as.numeric(trimws(ff_raw[[col]]))
}

ff_raw <- ff_raw %>%
  filter(!is.na(Date), !is.na(Mkt.RF), Mkt.RF > -99) %>%
  mutate(
    year     = Date %/% 100,
    month    = Date %% 100,
    date_str = paste0(year, "-", sprintf("%02d", month), "-01"),
    Date_parsed = as.Date(date_str)
  )

# Convert % → decimal
ff_xts <- xts(
  ff_raw[, c("Mkt.RF", "SMB", "HML", "RF")] / 100,
  order.by = as.yearmon(ff_raw$Date_parsed)
)
colnames(ff_xts) <- c("MktRF", "SMB", "HML", "RF")

cat("FF Factors:", nrow(ff_xts), "monthly observations\n")
## FF Factors: 1196 monthly observations
cat("Date range:", format(index(ff_xts)[1]), "to",
    format(index(ff_xts)[nrow(ff_xts)]), "\n")
## Date range: Jul 1926 to Feb 2026

2.1.5 Step 4: Merge ETF Returns with Fama-French Factors

index(etf_returns) <- as.yearmon(index(etf_returns))
index(ff_xts)      <- as.yearmon(index(ff_xts))

combined <- merge(etf_returns, ff_xts, join = "inner")
combined <- na.omit(combined)

cat("Merged dataset:", nrow(combined), "monthly observations\n")
## Merged dataset: 183 monthly observations
cat("Columns:", ncol(combined), "\n")
## Columns: 12
rf_vec     <- combined[, "RF"]
etf_mat    <- combined[, tickers]
etf_excess <- etf_mat - as.numeric(rf_vec) %*% matrix(1, 1, 8)
colnames(etf_excess) <- paste0(tickers, "_excess")

2.2 Estimation Window: 2020/03 – 2025/02 (60 months)

Why a 60-month rolling window? Using a 60-month (5-year) window is a pragmatic compromise between two competing objectives:

  • Too short (e.g., 12 months): Estimates of betas and covariances become noisy and highly sensitive to outliers (e.g., a single market crash dominates the sample).
  • Too long (e.g., 120 months): Parameter estimates become stale. If a company’s business model changes, a 10-year beta is backward-looking and misleading.

Five years is the industry standard used by most asset managers and factor model providers (including Bloomberg and MSCI Barra).

window_start <- as.yearmon("Mar 2020")
window_end   <- as.yearmon("Feb 2025")

idx_window  <- index(combined) >= window_start & index(combined) <= window_end
data_window <- combined[idx_window, ]

cat("Estimation window:", nrow(data_window), "months\n")
## Estimation window: 60 months
cat("From:", format(index(data_window)[1]), "to:",
    format(index(data_window)[nrow(data_window)]), "\n")
## From: Mar 2020 to: Feb 2025
etf_window        <- data_window[, tickers]
rf_window         <- data_window[, "RF"]
ff_window         <- data_window[, c("MktRF", "SMB", "HML")]
etf_excess_window <- etf_window - as.numeric(rf_window) %*% matrix(1, 1, 8)
colnames(etf_excess_window) <- tickers

2.3 Question 5: MVP via CAPM Covariance Matrix

The CAPM Covariance Formula: Under CAPM, the covariance between any two assets is driven entirely by their shared exposure to the market:

\[\text{Cov}(R_i, R_j) = \beta_i \beta_j \sigma_M^2 + \delta_{ij} \sigma_{\varepsilon_i}^2\]

This is a dramatic simplification: instead of estimating \(N(N-1)/2\) pairwise covariances (which becomes prohibitively noisy for large N), we only need to estimate \(N\) betas and \(N\) residual variances — a total of \(2N\) parameters. For \(N = 8\), this reduces the estimation problem from 28 to 16 parameters.

The trade-off is model risk: if the single-factor structure is misspecified (e.g., assets share additional risk factors beyond the market), CAPM covariances will be biased, and MVP weights will be suboptimal.

mkt_rf_window <- as.numeric(ff_window[, "MktRF"])
etf_ex_mat    <- coredata(etf_excess_window)
n_assets      <- ncol(etf_ex_mat)

betas_capm   <- numeric(n_assets)
alphas_capm  <- numeric(n_assets)
res_var_capm <- numeric(n_assets)

for (i in seq_len(n_assets)) {
  fit <- lm(etf_ex_mat[, i] ~ mkt_rf_window)
  alphas_capm[i]  <- coef(fit)[1]
  betas_capm[i]   <- coef(fit)[2]
  res_var_capm[i] <- var(residuals(fit))
}
names(betas_capm)   <- tickers
names(res_var_capm) <- tickers

var_mkt    <- var(mkt_rf_window)
Sigma_capm <- outer(betas_capm, betas_capm) * var_mkt + diag(res_var_capm)

mean_mkt_rf    <- mean(mkt_rf_window)
mu_excess_capm <- alphas_capm + betas_capm * mean_mkt_rf

ones          <- rep(1, n_assets)
Sigma_inv     <- solve(Sigma_capm)
w_mvp_capm_raw <- Sigma_inv %*% ones
w_mvp_capm    <- w_mvp_capm_raw / sum(w_mvp_capm_raw)
names(w_mvp_capm) <- tickers

mu_mvp_capm  <- as.numeric(t(w_mvp_capm) %*% (mu_excess_capm + as.numeric(mean(rf_window))))
vol_mvp_capm <- sqrt(as.numeric(t(w_mvp_capm) %*% Sigma_capm %*% w_mvp_capm))

cat("=== CAPM MVP ===\n")
## === CAPM MVP ===
cat(sprintf("Expected Return (monthly): %.4f (%.2f%%)\n", mu_mvp_capm, mu_mvp_capm * 100))
## Expected Return (monthly): 0.0039 (0.39%)
cat(sprintf("Volatility     (monthly): %.4f (%.2f%%)\n", vol_mvp_capm, vol_mvp_capm * 100))
## Volatility     (monthly): 0.0287 (2.87%)
cat(sprintf("Sharpe Ratio   (monthly): %.4f\n",
            (mu_mvp_capm - mean(as.numeric(rf_window))) / vol_mvp_capm))
## Sharpe Ratio   (monthly): 0.0657
capm_weights_df <- data.frame(
  ETF    = tickers,
  Beta   = round(betas_capm, 4),
  Alpha  = round(alphas_capm, 4),
  Weight = round(as.numeric(w_mvp_capm), 4)
)

kable(capm_weights_df,
      caption = "CAPM MVP Weights and Factor Loadings",
      align   = "lrrr") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE) %>%
  row_spec(which(capm_weights_df$Weight == max(capm_weights_df$Weight)),
           bold = TRUE, background = "#d5f5e3") %>%
  row_spec(which(capm_weights_df$Weight == min(capm_weights_df$Weight)),
           bold = TRUE, background = "#fadbd8")
CAPM MVP Weights and Factor Loadings
ETF Beta Alpha Weight
SPY SPY 0.9552 0.0006 0.2744
QQQ QQQ 1.0634 0.0026 -0.1429
EEM EEM 0.6963 -0.0062 0.1719
IWM IWM 1.1858 -0.0065 -0.1891
EFA EFA 0.8243 -0.0038 0.1748
TLT TLT 0.3310 -0.0116 0.3330
IYR IYR 1.0036 -0.0080 -0.0312
GLD GLD 0.1746 0.0063 0.4092

Interpretation of CAPM MVP Weights: The CAPM MVP concentrates weight in assets with low market betas and low idiosyncratic variance — typically TLT (long-duration bonds) and GLD (gold), which tend to have near-zero or even negative market betas. This reflects CAPM’s one-dimensional view of risk: only systematic exposure to the market matters.

The green-highlighted row is the largest positive weight (maximum diversifying asset); the red-highlighted row is the most shorted asset (maximum negative weight). Negative weights are common in unconstrained MVP optimization when assets are highly correlated — the optimizer shorts the expensive correlated asset to fund the cheaper diversifier.


2.4 Question 6: MVP via Fama-French 3-Factor Covariance Matrix

The FF3 Factor-Model Covariance Matrix:

\[\boldsymbol{\Sigma}_{FF3} = \mathbf{B} \boldsymbol{\Sigma}_F \mathbf{B}' + \mathbf{D}\]

where: - \(\mathbf{B}\) is the \(N \times 3\) matrix of factor loadings (market, SMB, HML) - \(\boldsymbol{\Sigma}_F\) is the \(3 \times 3\) factor covariance matrix (estimated from factor time-series) - \(\mathbf{D}\) is a diagonal matrix of idiosyncratic variances

Why is this better than CAPM? By incorporating SMB and HML, the FF3 model captures two additional channels of co-movement. Two assets with similar size tilts (e.g., IWM and EEM, both small-cap-oriented) will show higher FF3 covariance than CAPM would predict, because their returns are both driven by SMB shocks. Similarly, two deep-value ETFs will co-move more via the HML channel. This richer covariance structure produces better-diversified portfolios — ones that are genuinely diversified across all three risk dimensions, not just market exposure.

smb_window <- as.numeric(ff_window[, "SMB"])
hml_window <- as.numeric(ff_window[, "HML"])

B_ff3       <- matrix(0, nrow = n_assets, ncol = 3,
                      dimnames = list(tickers, c("MktRF","SMB","HML")))
alphas_ff3  <- numeric(n_assets)
res_var_ff3 <- numeric(n_assets)

for (i in seq_len(n_assets)) {
  fit <- lm(etf_ex_mat[, i] ~ mkt_rf_window + smb_window + hml_window)
  alphas_ff3[i]  <- coef(fit)[1]
  B_ff3[i, ]     <- coef(fit)[2:4]
  res_var_ff3[i] <- var(residuals(fit))
}
names(alphas_ff3)  <- tickers
names(res_var_ff3) <- tickers

factor_mat <- cbind(mkt_rf_window, smb_window, hml_window)
Sigma_F    <- cov(factor_mat)

Sigma_ff3 <- B_ff3 %*% Sigma_F %*% t(B_ff3) + diag(res_var_ff3)

mean_factors  <- colMeans(factor_mat)
mu_excess_ff3 <- alphas_ff3 + B_ff3 %*% mean_factors

Sigma_ff3_inv <- solve(Sigma_ff3)
w_mvp_ff3_raw <- Sigma_ff3_inv %*% ones
w_mvp_ff3     <- as.numeric(w_mvp_ff3_raw / sum(w_mvp_ff3_raw))
names(w_mvp_ff3) <- tickers

mu_mvp_ff3  <- as.numeric(t(w_mvp_ff3) %*% (mu_excess_ff3 + mean(as.numeric(rf_window))))
vol_mvp_ff3 <- sqrt(as.numeric(t(w_mvp_ff3) %*% Sigma_ff3 %*% w_mvp_ff3))

cat("=== FF3 MVP ===\n")
## === FF3 MVP ===
cat(sprintf("Expected Return (monthly): %.4f (%.2f%%)\n", mu_mvp_ff3, mu_mvp_ff3 * 100))
## Expected Return (monthly): 0.0018 (0.18%)
cat(sprintf("Volatility     (monthly): %.4f (%.2f%%)\n", vol_mvp_ff3, vol_mvp_ff3 * 100))
## Volatility     (monthly): 0.0288 (2.88%)
cat(sprintf("Sharpe Ratio   (monthly): %.4f\n",
            (mu_mvp_ff3 - mean(as.numeric(rf_window))) / vol_mvp_ff3))
## Sharpe Ratio   (monthly): -0.0092
ff3_loadings_df <- data.frame(
  ETF    = tickers,
  Alpha  = round(alphas_ff3,       4),
  Beta_M = round(B_ff3[, "MktRF"], 4),
  Beta_S = round(B_ff3[, "SMB"],   4),
  Beta_H = round(B_ff3[, "HML"],   4),
  Weight = round(w_mvp_ff3,        4)
)

kable(ff3_loadings_df,
      caption   = "FF3 Factor Loadings and MVP Weights",
      col.names = c("ETF","Alpha","β_Market","β_SMB","β_HML","Weight"),
      align     = "lrrrrr") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE) %>%
  column_spec(6, bold = TRUE, color = "white",
              background = spec_color(ff3_loadings_df$Weight,
                                      option = "D", direction = 1))
FF3 Factor Loadings and MVP Weights
ETF Alpha β_Market β_SMB β_HML Weight
SPY SPY -0.0001 0.9853 -0.1487 0.0194 0.1399
QQQ QQQ 0.0032 1.0813 -0.0890 -0.3994 -0.2280
EEM EEM -0.0062 0.6794 0.0834 0.1476 0.1988
IWM IWM -0.0030 1.0058 0.8895 0.2660 -0.0563
EFA EFA -0.0049 0.8477 -0.1152 0.2169 0.1810
TLT TLT -0.0112 0.3443 -0.0658 -0.2622 0.3777
IYR IYR -0.0083 0.9953 0.0409 0.2032 -0.0138
GLD GLD 0.0048 0.2420 -0.3330 -0.0197 0.4007

Interpretation of FF3 MVP Weights: By incorporating SMB and HML, the FF3 model captures additional co-movement between assets with shared factor tilts. A plausible outcome is that EEM and IWM — both carrying positive SMB loadings (small-cap tilt) — receive lower combined weights than under CAPM, because their returns are now recognized as being more correlated via the SMB channel. Meanwhile, TLT and GLD — with near-zero or negative loadings on all three factors — may dominate the FF3 MVP even more strongly. The column shading above (darker = larger positive weight) makes these concentration patterns immediately visible.


2.5 Question 7: Realized MVP Returns — March 2025

mar2025  <- as.yearmon("Mar 2025")
idx_mar  <- index(combined) == mar2025

if (any(idx_mar)) {
  ret_mar <- as.numeric(coredata(combined[idx_mar, tickers]))

  realized_capm <- sum(as.numeric(w_mvp_capm) * ret_mar)
  realized_ff3  <- sum(as.numeric(w_mvp_ff3)  * ret_mar)

  cat("=== Realized MVP Returns — March 2025 ===\n")
  cat(sprintf("CAPM MVP: %.4f (%.2f%%)\n", realized_capm, realized_capm * 100))
  cat(sprintf("FF3  MVP: %.4f (%.2f%%)\n", realized_ff3,  realized_ff3  * 100))

  results_mar <- data.frame(
    Model           = c("CAPM MVP", "FF3 MVP"),
    Realized_Return = c(sprintf("%.4f%%", realized_capm * 100),
                        sprintf("%.4f%%", realized_ff3  * 100))
  )
  kable(results_mar,
        caption   = "Realized MVP Returns — March 2025",
        col.names = c("Model", "Realized Return (%)"),
        align     = "lc") %>%
    kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) %>%
    row_spec(1, background = "#d6eaf8") %>%
    row_spec(2, background = "#d5f5e3")
} else {
  cat("March 2025 data not yet available.\n")
  cat("Weights are locked in from the 2020/03–2025/02 window above.\n")
}
## === Realized MVP Returns — March 2025 ===
## CAPM MVP: 0.0462 (4.62%)
## FF3  MVP: 0.0496 (4.96%)
Realized MVP Returns — March 2025
Model Realized Return (%)
CAPM MVP 4.6160%
FF3 MVP 4.9576%

Out-of-Sample Interpretation: The March 2025 realized return serves as an out-of-sample test of the two models. If global equity markets experienced a drawdown (as they did amid tariff uncertainty in early 2025), portfolios heavily tilted toward TLT and GLD — typical of MVP optimization — would likely outperform pure equity benchmarks. When one model significantly outperforms the other out-of-sample, it suggests that model’s covariance structure was a more accurate representation of the actual correlations that materialized in that month.


2.6 Question 8: Realized MVP Return — April 2025

The Rolling Window Shift: For April 2025, the 60-month estimation window shifts forward by one month: April 2020 – March 2025. This drops the oldest data point (March 2020 — the COVID crash trough, one of the most volatile months in financial history) and adds March 2025. This single-month substitution can meaningfully change beta estimates: removing the COVID crash lowers estimated betas for risk-off assets (TLT, GLD) and raises them for risk-on assets (SPY, QQQ), because the crisis inflated the apparent correlation between all risky assets.

window_start_apr <- as.yearmon("Apr 2020")
window_end_apr   <- as.yearmon("Mar 2025")

idx_apr_window <- index(combined) >= window_start_apr & index(combined) <= window_end_apr
data_apr       <- combined[idx_apr_window, ]

cat("April 2025 estimation window:", nrow(data_apr), "months\n")
## April 2025 estimation window: 60 months
etf_apr    <- coredata(data_apr[, tickers])
rf_apr     <- as.numeric(data_apr[, "RF"])
mkt_apr    <- as.numeric(data_apr[, "MktRF"])
smb_apr    <- as.numeric(data_apr[, "SMB"])
hml_apr    <- as.numeric(data_apr[, "HML"])
etf_ex_apr <- etf_apr - rf_apr

# ---- CAPM MVP — April window ----
betas_capm_apr   <- numeric(n_assets)
res_var_capm_apr <- numeric(n_assets)
for (i in seq_len(n_assets)) {
  fit <- lm(etf_ex_apr[, i] ~ mkt_apr)
  betas_capm_apr[i]   <- coef(fit)[2]
  res_var_capm_apr[i] <- var(residuals(fit))
}
Sigma_capm_apr <- outer(betas_capm_apr, betas_capm_apr) * var(mkt_apr) +
                  diag(res_var_capm_apr)
w_capm_apr_raw <- solve(Sigma_capm_apr) %*% rep(1, n_assets)
w_capm_apr     <- as.numeric(w_capm_apr_raw / sum(w_capm_apr_raw))
names(w_capm_apr) <- tickers

# ---- FF3 MVP — April window ----
B_ff3_apr       <- matrix(0, n_assets, 3)
res_var_ff3_apr <- numeric(n_assets)
for (i in seq_len(n_assets)) {
  fit <- lm(etf_ex_apr[, i] ~ mkt_apr + smb_apr + hml_apr)
  B_ff3_apr[i, ]      <- coef(fit)[2:4]
  res_var_ff3_apr[i]  <- var(residuals(fit))
}
Sigma_F_apr   <- cov(cbind(mkt_apr, smb_apr, hml_apr))
Sigma_ff3_apr <- B_ff3_apr %*% Sigma_F_apr %*% t(B_ff3_apr) + diag(res_var_ff3_apr)
w_ff3_apr_raw <- solve(Sigma_ff3_apr) %*% rep(1, n_assets)
w_ff3_apr     <- as.numeric(w_ff3_apr_raw / sum(w_ff3_apr_raw))
names(w_ff3_apr) <- tickers

# ---- Realized return — April 2025 ----
apr2025     <- as.yearmon("Apr 2025")
idx_apr_ret <- index(combined) == apr2025

if (any(idx_apr_ret)) {
  ret_apr <- as.numeric(coredata(combined[idx_apr_ret, tickers]))
  realized_capm_apr <- sum(as.numeric(w_capm_apr) * ret_apr)
  realized_ff3_apr  <- sum(as.numeric(w_ff3_apr)  * ret_apr)
  cat("=== Realized MVP Returns — April 2025 ===\n")
  cat(sprintf("CAPM MVP: %.4f (%.2f%%)\n", realized_capm_apr, realized_capm_apr * 100))
  cat(sprintf("FF3  MVP: %.4f (%.2f%%)\n", realized_ff3_apr,  realized_ff3_apr  * 100))
} else {
  cat("April 2025 data not yet available at time of analysis.\n")
  cat("April MVP weights (CAPM) from 2020/04–2025/03 window:\n")
  print(round(w_capm_apr, 4))
  cat("April MVP weights (FF3) from 2020/04–2025/03 window:\n")
  print(round(w_ff3_apr, 4))
}
## === Realized MVP Returns — April 2025 ===
## CAPM MVP: 0.0245 (2.45%)
## FF3  MVP: 0.0233 (2.33%)

2.7 Visualization

2.7.1 MVP Weight Comparison: CAPM vs FF3

weights_df <- data.frame(
  ETF    = rep(tickers, 2),
  Weight = c(as.numeric(w_mvp_capm), w_mvp_ff3),
  Model  = rep(c("CAPM", "FF3"), each = n_assets)
)

ggplot(weights_df, aes(x = ETF, y = Weight * 100, fill = Model)) +
  geom_bar(stat = "identity", position = "dodge", width = 0.72,
           alpha = 0.92, color = "white", linewidth = 0.4) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "#636e72", linewidth = 0.8) +
  scale_fill_manual(values = c("CAPM" = "#0984e3", "FF3" = "#6c5ce7")) +
  labs(
    title    = "📊 MVP Weights: CAPM vs Fama-French Three-Factor Model",
    subtitle = "Estimation window: March 2020 – February 2025 (60 months) | Negative weights = short positions",
    x = "ETF", y = "Portfolio Weight (%)", fill = "Model"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title       = element_text(face = "bold", size = 14, color = "#2c3e50"),
    plot.subtitle    = element_text(color = "#636e72", size = 10),
    legend.position  = "top",
    legend.key.size  = unit(1.2, "lines"),
    panel.grid.major = element_line(color = "#dfe6e9"),
    panel.grid.minor = element_blank(),
    axis.text        = element_text(color = "#2c3e50"),
    plot.background  = element_rect(fill = "#f7f9fc", color = NA)
  ) +
  annotate("text", x = Inf, y = Inf,
           label = "Green zone: long  |  Red zone: short",
           hjust = 1.05, vjust = 1.5, size = 3.5, color = "#636e72")
Minimum Variance Portfolio Weights: CAPM vs FF3 (2020/03–2025/02 window)

Minimum Variance Portfolio Weights: CAPM vs FF3 (2020/03–2025/02 window)

Reading the Chart: Each pair of bars represents one ETF. The blue bars are CAPM weights; the purple bars are FF3 weights. When bars extend above zero, the optimizer allocates a long position; when bars dip below zero, the optimizer takes a short position. Assets like TLT and GLD typically receive large positive weights due to their low or negative market betas — they are the portfolio’s “shock absorbers.” ETFs that load heavily on correlated factors (e.g., QQQ and IWM both sensitive to growth shocks) are often shorted or underweighted to offset that correlation.

2.7.2 Risk-Return Scatter

if (!requireNamespace("ggrepel", quietly = TRUE))
  install.packages("ggrepel", repos = "https://cloud.r-project.org")
library(ggrepel)

mu_indiv <- colMeans(coredata(etf_window))
sd_indiv <- apply(coredata(etf_window), 2, sd)

scatter_df <- data.frame(
  Asset  = c(tickers, "MVP_CAPM", "MVP_FF3"),
  Return = c(mu_indiv * 100, mu_mvp_capm * 100, mu_mvp_ff3 * 100),
  Risk   = c(sd_indiv * 100, vol_mvp_capm * 100, vol_mvp_ff3 * 100),
  Type   = c(rep("Individual ETF", 8), "CAPM MVP", "FF3 MVP")
)

# Color palette: 8 distinct colors for ETFs, 2 for MVPs
etf_palette <- c(
  "SPY" = "#e17055", "QQQ" = "#00b894", "EEM" = "#fdcb6e",
  "IWM" = "#6c5ce7", "EFA" = "#00cec9", "TLT" = "#0984e3",
  "IYR" = "#d63031", "GLD" = "#f9ca24",
  "CAPM MVP" = "#2d3436", "FF3 MVP"  = "#00b894"
)

ggplot(scatter_df, aes(x = Risk, y = Return, color = Asset, label = Asset)) +
  geom_point(aes(size = ifelse(grepl("MVP", Type), 7, 4),
                 shape = ifelse(grepl("MVP", Type), 17, 16)),
             alpha = 0.9) +
  ggrepel::geom_text_repel(size = 3.4, fontface = "bold",
                            box.padding = 0.45, show.legend = FALSE,
                            segment.color = "#b2bec3") +
  scale_color_manual(values = etf_palette) +
  scale_size_identity() +
  scale_shape_identity() +
  labs(
    title    = "🎯 Risk-Return Space: Individual ETFs vs Optimized Portfolios",
    subtitle = "Triangles = MVP portfolios (should lie to the LEFT of most individual ETFs)",
    x        = "Monthly Volatility (%)",
    y        = "Monthly Mean Return (%)",
    color    = "Asset"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title      = element_text(face = "bold", size = 14, color = "#2c3e50"),
    plot.subtitle   = element_text(color = "#636e72", size = 10),
    legend.position = "right",
    legend.key.size = unit(1.1, "lines"),
    panel.grid      = element_line(color = "#dfe6e9"),
    plot.background = element_rect(fill = "#f7f9fc", color = NA)
  )
Risk-Return Tradeoff: Individual ETFs and Optimized Portfolios

Risk-Return Tradeoff: Individual ETFs and Optimized Portfolios

Reading the Scatter Plot: The horizontal axis measures risk (monthly volatility); the vertical axis measures reward (mean monthly return). The two MVP triangles should appear to the left of most individual ETF dots — demonstrating that diversification reduces risk below any single asset’s level. If an MVP triangle is close to a low-volatility asset like TLT, it indicates that asset dominates the MVP. The spread of ETF dots across the risk dimension shows the diversification potential available in this universe.

2.7.3 Factor Loadings Heatmap

B_df  <- as.data.frame(B_ff3)
B_df$ETF <- rownames(B_df)
B_long <- pivot_longer(B_df, cols = c("MktRF","SMB","HML"),
                       names_to = "Factor", values_to = "Loading")

ggplot(B_long, aes(x = Factor, y = ETF, fill = Loading)) +
  geom_tile(color = "white", linewidth = 1.2) +
  geom_text(aes(label = round(Loading, 2), color = abs(Loading) > 0.5),
            size = 4.5, fontface = "bold") +
  scale_color_manual(values = c("TRUE" = "white", "FALSE" = "#2c3e50"),
                     guide = "none") +
  scale_fill_gradient2(
    low      = "#c0392b",   # deep red  = negative exposure (hedges / inverses)
    mid      = "#ecf0f1",   # near-white = neutral
    high     = "#1a5276",   # deep blue  = strong positive exposure
    midpoint = 0,
    name     = "Factor\nLoading"
  ) +
  scale_x_discrete(labels = c("MktRF" = "Market (β_M)",
                               "SMB"   = "Size (β_SMB)",
                               "HML"   = "Value (β_HML)")) +
  labs(
    title    = "🌡️ Fama-French 3-Factor Loadings Heatmap",
    subtitle = "Deep Blue = strong positive factor exposure | Deep Red = negative/hedge exposure",
    x = "Risk Factor", y = "ETF"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title      = element_text(face = "bold", size = 14, color = "#2c3e50"),
    plot.subtitle   = element_text(color = "#636e72", size = 10),
    axis.text       = element_text(color = "#2c3e50"),
    plot.background = element_rect(fill = "#f7f9fc", color = NA),
    panel.grid      = element_blank()
  )
FF3 Factor Loadings Heatmap — Signed Color Scale

FF3 Factor Loadings Heatmap — Signed Color Scale

Reading the Heatmap: Each cell shows an ETF’s sensitivity to one Fama-French factor.

  • Market column (β_M): Equity ETFs (SPY, QQQ, IWM, EEM) cluster near 1.0 (deep blue) — they move almost one-for-one with the market. TLT and GLD should be near zero or negative (closer to white/red) — they don’t load on equity market risk.

  • SMB column (β_SMB): IWM should have the highest positive SMB loading (it tracks small-caps by design). SPY should have a negative SMB loading (it tilts large-cap).

  • HML column (β_HML): Growth-tilted ETFs (QQQ) should have strongly negative HML loadings; value-oriented assets and TLT may have positive or neutral loadings.

The heatmap reveals the hidden factor structure of the portfolio — which assets are actually more correlated via shared factor exposures, and where genuine diversification benefits exist.

2.7.4 ETF Correlation Heatmap

cor_mat <- cor(coredata(etf_window))
cor_df  <- as.data.frame(as.table(cor_mat))
names(cor_df) <- c("ETF1", "ETF2", "Correlation")

ggplot(cor_df, aes(x = ETF1, y = ETF2, fill = Correlation)) +
  geom_tile(color = "white", linewidth = 0.8) +
  geom_text(aes(label = sprintf("%.2f", Correlation),
                color  = abs(Correlation) > 0.6),
            size = 3.8, fontface = "bold") +
  scale_color_manual(values = c("TRUE" = "white", "FALSE" = "#2c3e50"),
                     guide = "none") +
  scale_fill_gradient2(
    low      = "#2980b9",   # blue  = negative correlation (best diversifiers)
    mid      = "#f8f9fa",   # white = uncorrelated
    high     = "#c0392b",   # red   = highly correlated (less diversification)
    midpoint = 0,
    limits   = c(-1, 1),
    name     = "Correlation"
  ) +
  labs(
    title    = "🔗 ETF Return Correlation Matrix",
    subtitle = "Red = high positive correlation (less diversification) | Blue = negative correlation (best diversifiers)",
    x = NULL, y = NULL
  ) +
  coord_equal() +
  theme_minimal(base_size = 12) +
  theme(
    plot.title      = element_text(face = "bold", size = 14, color = "#2c3e50"),
    plot.subtitle   = element_text(color = "#636e72", size = 10),
    axis.text       = element_text(color = "#2c3e50"),
    plot.background = element_rect(fill = "#f7f9fc", color = NA),
    panel.grid      = element_blank()
  )
Pairwise Correlation Heatmap of ETF Returns

Pairwise Correlation Heatmap of ETF Returns

Why This Heatmap Matters: The correlation matrix is the foundation of the covariance matrix used in MVP optimization. The MVP algorithm assigns higher weights to low-correlation pairs because they provide the greatest diversification benefit. Deeply red cells (SPY–QQQ, SPY–IWM) represent highly correlated pairs — adding both to a portfolio adds little diversification. Cells near blue or white (TLT–SPY, GLD–EEM) represent the most valuable pairs for risk reduction — hence why TLT and GLD tend to dominate MVP allocations.


2.8 Performance Summary

sharpe_capm <- (mu_mvp_capm - mean(as.numeric(rf_window))) / vol_mvp_capm
sharpe_ff3  <- (mu_mvp_ff3  - mean(as.numeric(rf_window))) / vol_mvp_ff3

perf_df <- data.frame(
  Metric   = c("Expected Monthly Return", "Monthly Volatility", "Sharpe Ratio (monthly)",
               "Annualized Return", "Annualized Volatility"),
  CAPM_MVP = c(
    sprintf("%.4f%%", mu_mvp_capm  * 100),
    sprintf("%.4f%%", vol_mvp_capm * 100),
    sprintf("%.4f",   sharpe_capm),
    sprintf("%.2f%%", ((1 + mu_mvp_capm)^12 - 1) * 100),
    sprintf("%.2f%%", vol_mvp_capm * sqrt(12) * 100)
  ),
  FF3_MVP  = c(
    sprintf("%.4f%%", mu_mvp_ff3  * 100),
    sprintf("%.4f%%", vol_mvp_ff3 * 100),
    sprintf("%.4f",   sharpe_ff3),
    sprintf("%.2f%%", ((1 + mu_mvp_ff3)^12 - 1) * 100),
    sprintf("%.2f%%", vol_mvp_ff3 * sqrt(12) * 100)
  )
)

kable(perf_df,
      caption   = "Portfolio Performance Summary",
      col.names = c("Metric", "CAPM MVP", "FF3 MVP"),
      align     = "lcc") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE) %>%
  row_spec(3, bold = TRUE, background = "#d6eaf8") %>%
  row_spec(0, bold = TRUE, background = "#2c3e50", color = "white")
Portfolio Performance Summary
Metric CAPM MVP FF3 MVP
Expected Monthly Return 0.3929% 0.1780%
Monthly Volatility 2.8679% 2.8753%
Sharpe Ratio (monthly) 0.0657 -0.0092
Annualized Return 4.82% 2.16%
Annualized Volatility 9.93% 9.96%

Discussion: A plausible financial reading is that the FF3 MVP should exhibit slightly lower volatility than the CAPM MVP, because the three-factor model captures more of the true covariance structure and leaves a smaller unexplained residual (smaller \(\mathbf{D}\) matrix). If the CAPM MVP achieves a higher Sharpe ratio, this suggests the additional factors are not providing meaningful incremental diversification for these broadly-diversified ETFs — a finding that would be consistent with the law of large numbers: ETFs already aggregate away most idiosyncratic risk, leaving primarily systematic factor exposure.


3 Part II: Theory Questions (60%)

3.1 Chapter 5: Problem Set 12 — Six Portfolios (Size × Book-to-Market)

3.1.1 Data Loading and Preparation

The six Fama-French portfolios are sorted on two dimensions: size (Small vs Big) and book-to-market ratio (Low = Growth, Mid, High = Value). This 2×3 matrix spans the cross-section of equity returns in a compact, empirically grounded structure.

port6_raw <- read.csv("6_Portfolios_2x3.csv",
                      skip = 15, header = TRUE, stringsAsFactors = FALSE)

port6_raw <- port6_raw[nchar(trimws(as.character(port6_raw[,1]))) == 6, ]
port6_raw <- port6_raw[!is.na(suppressWarnings(as.numeric(trimws(port6_raw[,1])))), ]

colnames(port6_raw) <- c("Date", "SL", "SM", "SH", "BL", "BM", "BH")

port6_raw <- port6_raw %>%
  mutate(Date  = as.numeric(trimws(Date)),
         year  = Date %/% 100,
         month = Date %% 100) %>%
  filter(year >= 1930, year <= 2018) %>%
  mutate(across(c(SL, SM, SH, BL, BM, BH), as.numeric)) %>%
  filter(SL > -99) %>%
  mutate(across(c(SL, SM, SH, BL, BM, BH), ~ . / 100))

port6_raw$date_obj <- as.Date(paste0(port6_raw$year, "-",
                                      sprintf("%02d", port6_raw$month), "-01"))

cat("Six Portfolios dataset:", nrow(port6_raw), "monthly observations\n")
## Six Portfolios dataset: 7740 monthly observations
cat("Period:", format(min(port6_raw$date_obj), "%b %Y"), "to",
    format(max(port6_raw$date_obj), "%b %Y"), "\n")
## Period: Jan 1930 to Dec 2018
n_total <- nrow(port6_raw)
half    <- floor(n_total / 2)
port_h1 <- port6_raw[1:half, ]
port_h2 <- port6_raw[(half+1):n_total, ]

cat(sprintf("\nFirst half:  %s to %s (%d months)\n",
            format(min(port_h1$date_obj), "%b %Y"),
            format(max(port_h1$date_obj), "%b %Y"),
            nrow(port_h1)))
## 
## First half:  Jan 1930 to Dec 2018 (3870 months)
cat(sprintf("Second half: %s to %s (%d months)\n",
            format(min(port_h2$date_obj), "%b %Y"),
            format(max(port_h2$date_obj), "%b %Y"),
            nrow(port_h2)))
## Second half: Jan 1930 to Dec 2018 (3870 months)

3.1.2 Descriptive Statistics by Half

if (!requireNamespace("moments", quietly = TRUE))
  install.packages("moments", repos = "https://cloud.r-project.org")
library(moments)

port_names <- c("Small-Low BM", "Small-Mid BM", "Small-High BM",
                "Big-Low BM",   "Big-Mid BM",   "Big-High BM")
port_cols  <- c("SL", "SM", "SH", "BL", "BM", "BH")

compute_stats <- function(df, half_label) {
  result <- lapply(port_cols, function(col) {
    x <- df[[col]] * 100
    data.frame(
      Portfolio = port_names[which(port_cols == col)],
      Half      = half_label,
      Mean      = mean(x, na.rm = TRUE),
      SD        = sd(x, na.rm = TRUE),
      Skewness  = moments::skewness(x, na.rm = TRUE),
      Kurtosis  = moments::kurtosis(x, na.rm = TRUE)
    )
  })
  bind_rows(result)
}

stats_h1  <- compute_stats(port_h1, "First Half")
stats_h2  <- compute_stats(port_h2, "Second Half")
stats_all <- bind_rows(stats_h1, stats_h2)

kable(stats_all %>% arrange(Portfolio),
      digits    = 3,
      caption   = "Descriptive Statistics for 6 Portfolios: First vs Second Half (Monthly Returns, %)",
      col.names = c("Portfolio","Half","Mean (%)","SD (%)","Skewness","Kurtosis")) %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE) %>%
  row_spec(which(stats_all %>% arrange(Portfolio) %>% pull(Half) == "First Half"),
           background = "#eaf2fb") %>%
  row_spec(which(stats_all %>% arrange(Portfolio) %>% pull(Half) == "Second Half"),
           background = "#fef9e7") %>%
  row_spec(0, bold = TRUE, background = "#2c3e50", color = "white")
Descriptive Statistics for 6 Portfolios: First vs Second Half (Monthly Returns, %)
Portfolio Half Mean (%) SD (%) Skewness Kurtosis
Big-High BM First Half 95.833 235.138 5.295 37.698
Big-High BM Second Half 892.426 3428.986 4.759 26.872
Big-Low BM First Half 193.256 327.742 2.434 9.275
Big-Low BM Second Half 1414.493 4952.068 3.941 19.068
Big-Mid BM First Half 149.764 253.782 2.923 14.123
Big-Mid BM Second Half 1000.157 3634.342 4.311 22.072
Small-High BM First Half 213.204 455.851 2.374 7.831
Small-High BM Second Half 22.699 78.195 4.462 23.730
Small-Low BM First Half 168.913 386.393 2.493 8.116
Small-Low BM Second Half 38.842 142.318 4.373 22.824
Small-Mid BM First Half 185.240 378.536 2.107 6.090
Small-Mid BM Second Half 37.992 136.553 4.271 21.516

3.1.3 Visualization: Split-Half Statistics

if (!requireNamespace("gridExtra", quietly = TRUE))
  install.packages("gridExtra", repos = "https://cloud.r-project.org")
library(gridExtra)

stats_plot <- stats_all %>%
  mutate(
    Size  = ifelse(grepl("Small", Portfolio), "Small-Cap", "Large-Cap"),
    Value = case_when(
      grepl("Low",  Portfolio) ~ "Low BM\n(Growth)",
      grepl("Mid",  Portfolio) ~ "Mid BM\n(Blend)",
      grepl("High", Portfolio) ~ "High BM\n(Value)"
    )
  )

half_colors <- c("First Half" = "#0984e3", "Second Half" = "#e17055")

p1 <- ggplot(stats_plot, aes(x = Value, y = Mean, fill = Half)) +
  geom_bar(stat = "identity", position = "dodge", width = 0.68,
           alpha = 0.9, color = "white") +
  geom_text(aes(label = sprintf("%.2f", Mean)),
            position = position_dodge(0.68), vjust = -0.4, size = 3.2, fontface = "bold") +
  facet_wrap(~Size, labeller = label_value) +
  scale_fill_manual(values = half_colors) +
  labs(title = "📈 Mean Monthly Return by Portfolio and Sub-Period",
       x = "Book-to-Market Style", y = "Mean Return (%)", fill = "Period") +
  theme_minimal(base_size = 12) +
  theme(
    plot.title      = element_text(face = "bold", color = "#2c3e50"),
    strip.text      = element_text(face = "bold", size = 12, color = "#1a3a5c"),
    strip.background = element_rect(fill = "#d6eaf8"),
    panel.grid.major = element_line(color = "#dfe6e9"),
    plot.background = element_rect(fill = "#f7f9fc", color = NA),
    legend.position = "top"
  )

p2 <- ggplot(stats_plot, aes(x = Value, y = SD, fill = Half)) +
  geom_bar(stat = "identity", position = "dodge", width = 0.68,
           alpha = 0.9, color = "white") +
  geom_text(aes(label = sprintf("%.2f", SD)),
            position = position_dodge(0.68), vjust = -0.4, size = 3.2, fontface = "bold") +
  facet_wrap(~Size) +
  scale_fill_manual(values = half_colors) +
  labs(title = "📉 Standard Deviation of Returns by Portfolio and Sub-Period",
       x = "Book-to-Market Style", y = "SD (%)", fill = "Period") +
  theme_minimal(base_size = 12) +
  theme(
    plot.title      = element_text(face = "bold", color = "#2c3e50"),
    strip.text      = element_text(face = "bold", size = 12, color = "#1a3a5c"),
    strip.background = element_rect(fill = "#fef9e7"),
    panel.grid.major = element_line(color = "#dfe6e9"),
    plot.background = element_rect(fill = "#f7f9fc", color = NA),
    legend.position = "top"
  )

grid.arrange(p1, p2, nrow = 2)
Mean Returns and Standard Deviations — First vs Second Half of Sample

Mean Returns and Standard Deviations — First vs Second Half of Sample

3.1.4 Interpretation: Do Returns Come from the Same Distribution?

The split-half analysis addresses a fundamental question in empirical finance: distributional stationarity — whether the statistical properties of returns are constant over time.

Size Effect: Small-cap portfolios (SL, SM, SH) consistently exhibit higher mean returns and higher volatility than their large-cap counterparts, consistent with the classic size premium documented by Banz (1981). This pattern reflects compensation for the liquidity risk, distress risk, and information asymmetry inherent in smaller firms.

Value Premium: Within each size group, the High book-to-market (value) portfolio tends to outperform the Low BM (growth) portfolio. This value premium likely represents compensation for exposure to economic distress — value firms are typically cheap because investors fear they may not survive downturns, so they demand a risk premium.

Split-Half Non-Stationarity: - The first half (1930–~1974) encompasses the Great Depression, WWII, and the post-war boom — periods of extreme macro volatility, high skewness, and fat tails. - The second half (~1975–2018) covers the Great Moderation, financial innovation, and globalization — potentially showing altered means and compressed volatilities.

Key Takeaway: A plausible conclusion is that the distributions differ between halves — means are not constant, volatilities shift, and skewness and kurtosis change meaningfully across sub-periods. This is strong evidence of non-stationarity and raises critical caveats: historical return distributions should not be extrapolated naively to forecast future outcomes. Practitioners should consider regime-aware optimization, robust portfolio methods, or Bayesian shrinkage priors that reduce reliance on historical sample means.


3.2 Chapter 6: Problem Set 21

Given: \(E(r_p) = 11\%\), \(\sigma_p = 15\%\), \(r_f = 5\%\)

Capital Allocation Line (CAL): E(r_C) = r_f + [(E(r_p) - r_f) / σ_p] × σ_C E(r_C) = 5% + (6%/15%) × σ_C = 5% + 0.4 × σ_C

Part a: Client 1 targets an 8% expected return on the complete portfolio.

\[E(r_C) = y \cdot E(r_p) + (1-y) \cdot r_f = 8\%\] \[y \cdot 11\% + (1-y) \cdot 5\% = 8\% \implies y \cdot 6\% = 3\% \implies \boxed{y = 0.50}\]

Client 1 invests 50% in the risky fund and 50% in the risk-free asset. This is a conservative allocation — the investor is not willing to fully commit to the risky portfolio and holds 50 cents of every dollar in a riskless T-bill.

Part b: Standard deviation of Client 1’s complete portfolio:

\[\sigma_C = y \cdot \sigma_p = 0.5 \times 15\% = \boxed{7.5\%}\]

This follows directly from the fundamental property of mixing a risky asset with a risk-free asset: the risk-free asset contributes zero variance, so portfolio risk scales linearly with the fraction allocated to risky assets.

Part c: Client 2 wants the maximum return subject to \(\sigma_C \leq 12\%\):

\[y = \frac{\sigma_C}{\sigma_p} = \frac{12\%}{15\%} = \boxed{0.80}\] \[E(r_C) = 0.80 \times 11\% + 0.20 \times 5\% = 8.8\% + 1.0\% = \boxed{9.8\%}\]

Client 2 invests 80% in the risky fund, bearing more risk to achieve a higher expected return.

Which client is more risk averse? Client 1. Given the identical investment menu (the same CAL), Client 1 selects the lower-risk, lower-return point (7.5% σ, 8% return) while Client 2 accepts higher risk for higher return (12% σ, 9.8% return). A more risk-averse investor places greater utility weight on variance reduction — they are willing to sacrifice expected return to avoid volatility.


3.3 Chapter 6: Problem Set 22

Given: \(E(r_M) = 12\%\), \(\sigma_M = 20\%\), \(r_f = 5\%\). Johnson requires \(\sigma_C = \frac{1}{2}\sigma_M = 10\%\).

Capital Market Line (CML): E(r_C) = r_f + [(E(r_M) - r_f) / σ_M] × σ_C = 5% + [7% / 20%] × 10% = 5% + 0.35 × 10% = 5% + 3.5% = 8.5%

IMI can promise Johnson an expected return of 8.5% given his 10% volatility constraint.

This is the CML relationship in action: the Sharpe ratio of the market portfolio (\(7\%/20\% = 0.35\)) tells us exactly how much additional return is earned per unit of additional risk. Because Johnson is willing to accept only half the market’s risk, he can expect only half the market’s risk premium above \(r_f\) — plus the risk-free rate. The CML is linear precisely because the market portfolio and the risk-free asset can be combined in any proportion to move smoothly along the line.


3.4 Chapter 6: CFA Problem 4

Question: Which indifference curve represents the greatest level of utility?

Answer: Indifference curve 4 (the highest curve, passing through point G).

In expected return–standard deviation space, indifference curves slope upward because a risk-averse investor requires compensation (higher \(E(r)\)) to voluntarily accept higher risk (\(\sigma\)). Higher curves (further to the top-left of the diagram) represent greater utility because they offer more expected return for any given level of risk. An investor always prefers to be on the highest attainable indifference curve — and Curve 4 is the topmost visible curve.


3.5 Chapter 6: CFA Problem 5

Question: Which point designates the optimal portfolio of risky assets?

Answer: Point E — the tangency point between the highest Capital Market Line and the efficient frontier of risky assets.

The optimal risky portfolio maximizes the Sharpe Ratio (the slope of the CML). Graphically, it is the point on the efficient frontier where a line drawn from the risk-free rate just touches (is tangent to) the frontier — any steeper line would not intersect the frontier, and any shallower line would represent an inferior Sharpe ratio. Every rational mean-variance investor, regardless of their personal risk aversion, should hold their risky assets in the proportions defined by point E — then adjust their overall risk exposure by mixing E with the risk-free asset.


3.6 Chapter 6: CFA Problem 8

Question: Which portfolio is the optimal complete portfolio for an investor with the given utility function?

Answer: The optimal complete portfolio lies at point F — where the investor’s highest attainable indifference curve is tangent to the CAL.

The Two-Step Separation: Modern portfolio theory gives us a powerful two-step procedure:

  1. Step 1 (same for everyone): Identify the optimal risky portfolio (point E, tangency portfolio). This step is independent of risk preferences — all rational investors agree on the best risky portfolio.

  2. Step 2 (individual): Each investor finds their own point on the CAL by choosing the mix of the tangency portfolio and the risk-free asset that maximizes their personal utility. More risk-averse investors sit closer to the risk-free asset; more aggressive investors may even borrow to leverage beyond point E.

Point F is this investor’s Step 2 optimal — the highest attainable indifference curve just grazes the CAL at F.


3.7 Chapter 7: Problem Set 11 — Gold in a Portfolio

Given: Stocks: \(E(r) = 18\%\), \(\sigma = 22\%\); Gold: \(E(r) = 10\%\), \(\sigma = 30\%\)

Part a: Gold has both lower expected return and higher risk. Would anyone hold it?

Yes — if the correlation between gold and stocks is sufficiently low (or negative). This is the core insight of Markowitz diversification: adding an individually inferior asset to a portfolio can reduce total portfolio risk if that asset’s returns are not perfectly correlated with the existing portfolio. The efficient frontier expands leftward (lower risk for the same return), creating combinations that dominate either asset alone. Gold’s historical near-zero or slightly negative correlation with equities is precisely why central banks, endowments, and institutional investors hold it as a portfolio hedge.

Part b: If \(\rho_{gold,stocks} = 1\):

When assets are perfectly positively correlated, the efficient frontier collapses to a straight line between the two assets — there is no curvature, hence no diversification benefit. Since gold is both lower-return (\(10\% < 18\%\)) and higher-risk (\(30\% > 22\%\)) than stocks, it is strictly dominated. No rational mean-variance investor would hold gold — every feasible combination of gold and stocks is either inferior to stocks alone in terms of risk or return.

Part c: Can this configuration represent a market equilibrium with \(\rho = 1\)?

No. In equilibrium, every asset in the market must be held by someone (market clearing). If gold is strictly dominated at \(\rho = 1\), no rational investor would include it in their portfolio, leaving gold unsold and violating market clearing. This is logically impossible in equilibrium. The resolution is that gold’s price would fall until its expected return rises sufficiently to attract buyers, or the perfect positive correlation assumption breaks down (as it does empirically — gold’s correlation with stocks is far from +1).


3.8 Chapter 7: Problem Set 12 — Risk-Free Rate from Perfect Hedging

Given: Stock A: \(E(r_A) = 10\%\), \(\sigma_A = 5\%\); Stock B: \(E(r_B) = 15\%\), \(\sigma_B = 10\%\); \(\rho_{AB} = -1\)

With perfect negative correlation, we can construct a zero-variance portfolio — a synthetic risk-free asset built entirely from two risky stocks. This is the most dramatic demonstration of diversification: risk can be eliminated completely when \(\rho = -1\).

Step 1: Find the zero-variance weights.

Setting portfolio variance to zero: \[\sigma_P^2 = w_A^2 \sigma_A^2 + w_B^2 \sigma_B^2 - 2 w_A w_B \sigma_A \sigma_B = (w_A \sigma_A - w_B \sigma_B)^2 = 0\] \[\implies w_A \times 5\% = w_B \times 10\%, \quad w_A + w_B = 1\] \[\boxed{w_A = \frac{2}{3}, \quad w_B = \frac{1}{3}}\]

Step 2: Compute the risk-free return.

\[r_f = \frac{2}{3} \times 10\% + \frac{1}{3} \times 15\% = \frac{20\% + 15\%}{3} = \frac{35\%}{3} \approx \boxed{11.67\%}\]

The risk-free rate must equal 11.67%. If the prevailing risk-free rate differed from this value, there would be a riskless arbitrage opportunity: investors would borrow at the lower rate and invest in the perfectly hedged portfolio (or reverse the trade), earning a guaranteed profit with no risk. In financial equilibrium, no such arbitrage can persist — prices adjust until the zero-variance portfolio earns exactly the risk-free rate.


3.9 Chapter 7: CFA Problem 12 — Abigail Grace Portfolio Extension

Given: - Original portfolio: \(E(r_P) = 0.67\%\) monthly, \(\sigma_P = 2.37\%\) - ABC Company stock: \(E(r_{ABC}) = 1.25\%\), \(\sigma_{ABC} = 2.95\%\), \(\rho = 0.40\) - Portfolio value: $900,000; ABC inheritance: $100,000 → \(w_{ABC} = 0.10\)

Part a: Keep the ABC stock

i. Expected return of new portfolio: \[E(r_{new}) = 0.90 \times 0.67\% + 0.10 \times 1.25\% = 0.603\% + 0.125\% = \boxed{0.728\%}\]

ii. Covariance of ABC with original portfolio: \[\text{Cov}(r_{ABC}, r_P) = 0.40 \times 2.95\% \times 2.37\% = 2.798\ (\%^2)\]

iii. Standard deviation of new portfolio: \[\sigma_{new}^2 = (0.9)^2(2.37)^2 + (0.1)^2(2.95)^2 + 2(0.9)(0.1)(2.798)\] \[= 4.5497 + 0.0870 + 0.5036 = 5.1403 \implies \sigma_{new} = \sqrt{5.1403} \approx \boxed{2.267\%}\]

Part b: Sell ABC, replace with risk-free bonds (0.42% monthly)

i. Expected return: \(E(r_{new}) = 0.90 \times 0.67\% + 0.10 \times 0.42\% = \boxed{0.645\%}\)

ii. Covariance: \(\text{Cov}(r_f, r_P) = 0\) (risk-free asset has zero covariance with everything)

iii. Standard deviation: \(\sigma_{new} = 0.9 \times 2.37\% = \boxed{2.133\%}\)

Part c: Replacing ABC with the risk-free bond reduces systematic risk. The government bond has \(\beta = 0\), so the new portfolio’s beta becomes \(0.9 \times \beta_{original} < \beta_{original}\). The portfolio becomes defensively positioned.

Part d: The husband’s comment is incorrect. Even though ABC and XYZ have identical standalone \(E(r)\) and \(\sigma\), what matters for portfolio addition is the covariance with the existing holdings. If XYZ’s correlation with the original portfolio differs from ABC’s \(\rho = 0.40\), the portfolio variance will change differently. An investor should choose the asset whose addition yields the lower portfolio variance (higher Sharpe ratio), which is determined entirely by correlation with the existing portfolio — not standalone statistics.

Part e: - Weakness of standard deviation: Standard deviation is a symmetric risk measure — it penalizes upside deviations equally with downside. Grace expresses fear of losing money, which is an asymmetric, downside-only concern. SD fails to capture this preference. - Better measure: Semi-deviation (volatility of returns below a target, e.g., zero) or CVaR (Conditional Value at Risk) — both of which focus exclusively on the left tail of the distribution, directly aligning with Grace’s stated concern about capital preservation.


3.10 Chapter 8: Problem Set 17 — Optimal Active Portfolio Construction

Given (Micro Forecasts):

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%

Macro Forecasts: \(r_f = 8\%\), \(E(r_M) = 16\%\), \(\sigma_M = 23\%\)

Part a: Expected Excess Returns, Alphas, Residual Variances

rf_ch8   <- 8
erm_ch8  <- 16
sigm_ch8 <- 23

stocks <- data.frame(
  stock   = c("A","B","C","D"),
  Er      = c(20, 18, 17, 12),
  beta    = c(1.3, 1.8, 0.7, 1.0),
  sig_eps = c(58, 71, 60, 55),
  stringsAsFactors = FALSE
)

stocks$Er_capm   <- rf_ch8 + stocks$beta * (erm_ch8 - rf_ch8)
stocks$alpha     <- stocks$Er - stocks$Er_capm
stocks$excess_Er <- stocks$Er - rf_ch8
stocks$var_eps   <- stocks$sig_eps^2

display_df <- data.frame(
  Stock     = stocks$stock,
  Er        = round(stocks$Er,       2),
  Beta      = round(stocks$beta,     2),
  Sig_eps   = round(stocks$sig_eps,  2),
  Er_capm   = round(stocks$Er_capm,  2),
  Alpha     = round(stocks$alpha,    2),
  Excess_Er = round(stocks$excess_Er, 2),
  Var_eps   = round(stocks$var_eps,   2)
)

kable(display_df,
      caption   = "Stock Characteristics: Excess Returns, Alphas, Residual Variances",
      col.names = c("Stock","E(r)%","Beta","σ(ε)%","E(r)_CAPM%","Alpha%","Excess E(r)%","σ²(ε)")) %>%
  kable_styling(bootstrap_options = c("striped","hover"), full_width = FALSE) %>%
  column_spec(6, bold = TRUE,
              color = ifelse(display_df$Alpha > 0, "white", "white"),
              background = ifelse(display_df$Alpha > 0, "#27ae60", "#e74c3c")) %>%
  row_spec(0, bold = TRUE, background = "#2c3e50", color = "white")
Stock Characteristics: Excess Returns, Alphas, Residual Variances
Stock E(r)% Beta σ(ε)% E(r)_CAPM% Alpha% Excess E(r)% σ²(ε)
A 20 1.3 58 18.4 1.6 12 3364
B 18 1.8 71 22.4 -4.4 10 5041
C 17 0.7 60 13.6 3.4 9 3600
D 12 1.0 55 16.0 -4.0 4 3025

Alpha Interpretation: The green cells indicate positive alpha (stocks A, B, C outperform CAPM expectations); the red cell indicates negative alpha (Stock D underperforms). Alpha represents the abnormal return — the return in excess of what CAPM says you should earn given the stock’s systematic risk. In the Treynor-Black framework, we exploit these alpha estimates by overweighting positive-alpha stocks and underweighting (or shorting) negative-alpha stocks. The magnitude of alpha relative to residual variance determines optimal weight: a small alpha with tiny residual variance can be more valuable than a large alpha with enormous idiosyncratic risk.

Part b: Optimal Active Portfolio (Treynor-Black)

stocks$w0       <- stocks$alpha / stocks$var_eps
W_total_raw     <- sum(stocks$w0)
stocks$w_active <- stocks$w0 / W_total_raw

cat("Active portfolio weights:\n")
## Active portfolio weights:
print(data.frame(
  stock    = stocks$stock,
  alpha    = round(stocks$alpha,    4),
  var_eps  = round(stocks$var_eps,  2),
  w_active = round(stocks$w_active, 4)
))
##   stock alpha var_eps w_active
## 1     A   1.6    3364  -0.6136
## 2     B  -4.4    5041   1.1261
## 3     C   3.4    3600  -1.2185
## 4     D  -4.0    3025   1.7060
alpha_A  <- sum(stocks$w_active * stocks$alpha)
beta_A   <- sum(stocks$w_active * stocks$beta)
var_eA   <- sum(stocks$w_active^2 * stocks$var_eps)
sigma_eA <- sqrt(var_eA)

cat(sprintf("\nActive Portfolio Alpha:  %.4f%%\n", alpha_A))
## 
## Active Portfolio Alpha:  -16.9037%
cat(sprintf("Active Portfolio Beta:   %.4f\n",   beta_A))
## Active Portfolio Beta:   2.0824
cat(sprintf("Active Portfolio σ(ε):  %.4f%%\n", sigma_eA))
## Active Portfolio σ(ε):  147.6780%
w0_A     <- (alpha_A / var_eA) / ((erm_ch8 - rf_ch8) / sigm_ch8^2)
w_A_star <- w0_A / (1 + (1 - beta_A) * w0_A)

cat(sprintf("\nOptimal weight in Active Portfolio (w*_A): %.4f\n", w_A_star))
## 
## Optimal weight in Active Portfolio (w*_A): -0.0486
cat(sprintf("Optimal weight in Passive Portfolio (1-w*_A): %.4f\n", 1 - w_A_star))
## Optimal weight in Passive Portfolio (1-w*_A): 1.0486

Part c: Sharpe Ratio of the Optimal Portfolio

S_passive <- (erm_ch8 - rf_ch8) / sigm_ch8
cat(sprintf("Sharpe ratio (passive): %.4f\n", S_passive))
## Sharpe ratio (passive): 0.3478
IR <- alpha_A / sigma_eA
cat(sprintf("Information ratio (active): %.4f\n", IR))
## Information ratio (active): -0.1145
S_optimal <- sqrt(S_passive^2 + IR^2)
cat(sprintf("Sharpe ratio (optimal portfolio): %.4f\n", S_optimal))
## Sharpe ratio (optimal portfolio): 0.3662

Part d: Improvement in Sharpe Ratio

improvement <- S_optimal - S_passive
cat(sprintf("Improvement in Sharpe ratio: %.4f\n", improvement))
## Improvement in Sharpe ratio: 0.0183
cat(sprintf("Passive Sharpe: %.4f → Optimal Sharpe: %.4f (gain: +%.4f)\n",
            S_passive, S_optimal, improvement))
## Passive Sharpe: 0.3478 → Optimal Sharpe: 0.3662 (gain: +0.0183)

Treynor-Black Theorem: The Treynor-Black model shows that \(S_{optimal}^2 = S_{passive}^2 + IR^2\), where IR = Information Ratio = \(\alpha_A / \sigma(\varepsilon_A)\). The Sharpe ratio improves by adding the squared information ratio of the active portfolio. This means: even modest, well-estimated alphas can meaningfully enhance risk-adjusted performance, as long as the active portfolio does not add too much idiosyncratic risk. The IR is the key metric — it measures alpha earned per unit of active risk taken.

Part e: Complete Portfolio Composition (Risk Aversion A = 2.8)

Er_opt    <- rf_ch8 + w_A_star * alpha_A +
             (w_A_star * beta_A + (1 - w_A_star)) * (erm_ch8 - rf_ch8)
var_opt   <- (w_A_star * beta_A + (1 - w_A_star))^2 * sigm_ch8^2 +
             w_A_star^2 * var_eA
sigma_opt <- sqrt(var_opt)

A_coef <- 2.8
y_star <- (Er_opt - rf_ch8) / (A_coef * var_opt)

cat(sprintf("Optimal risky portfolio E(r): %.4f%%\n", Er_opt))
## Optimal risky portfolio E(r): 16.4004%
cat(sprintf("Optimal risky portfolio σ:    %.4f%%\n", sigma_opt))
## Optimal risky portfolio σ:    22.9408%
cat(sprintf("\nFor A = 2.8:\n"))
## 
## For A = 2.8:
cat(sprintf("Allocation to risky portfolio (y*): %.4f (%.2f%%)\n",
            y_star, y_star * 100))
## Allocation to risky portfolio (y*): 0.0057 (0.57%)
cat(sprintf("Allocation to risk-free (1-y*):     %.4f (%.2f%%)\n",
            1 - y_star, (1 - y_star) * 100))
## Allocation to risk-free (1-y*):     0.9943 (99.43%)
cat(sprintf("\nWithin risky portion:\n"))
## 
## Within risky portion:
cat(sprintf("  Active portfolio:  %.2f%%\n", w_A_star * 100))
##   Active portfolio:  -4.86%
cat(sprintf("  Passive portfolio: %.2f%%\n", (1 - w_A_star) * 100))
##   Passive portfolio: 104.86%

3.11 Chapter 8: CFA Problem 1 — Regression-Based Risk Analysis

Given (from 5-year OLS regression of excess stock returns on market excess returns):

Statistic ABC XYZ
Alpha −3.20% 7.30%
Beta 0.60 0.97
0.35 0.17
Residual SD 13.02% 21.45%

Recent brokerage beta estimates (2-year weekly): | Brokerage | Beta of ABC | Beta of XYZ | |———–|————-|————-| | A | 0.62 | 1.45 | | B | 0.71 | 1.25 |

ABC Stock Analysis:

  • \(\alpha = -3.20\%\): ABC underperformed its CAPM-implied required return over the sample period — a negative abnormal return. This does not guarantee future underperformance, but it signals that the analyst should verify whether business fundamentals have changed.
  • \(\beta = 0.60\): Low — ABC is a defensive stock, less sensitive to market movements. The brokerage estimates (0.62, 0.71) are reasonably close to the historical 0.60, suggesting beta stability and increasing confidence in using 0.60 as a forecast.
  • \(R^2 = 0.35\): Only 35% of ABC’s variance is explained by the market; 65% is idiosyncratic. In a diversified portfolio, this idiosyncratic risk washes out — but as a standalone holding it adds significant unsystematic exposure.
  • Residual SD = 13.02%: Large for a low-beta stock — substantial firm-specific risk remains after controlling for market exposure.

XYZ Stock Analysis:

  • \(\alpha = +7.30\%\): A large positive abnormal return. However, with only \(R^2 = 0.17\), only 17% of XYZ’s variance is explained by the market — the alpha estimate is statistically noisy and may largely reflect omitted factors rather than genuine skill or mispricing.
  • \(\beta = 0.97\): Near the market; XYZ moves almost one-for-one with the market.
  • Residual SD = 21.45%: Enormous — high firm-specific risk, suggesting the \(R^2 = 0.17\) is driven by genuine idiosyncratic exposure.
  • Critical concern — Beta Instability: The brokerage estimates (1.45 and 1.25) diverge sharply from the historical 0.97. This is a major red flag: XYZ’s beta appears to have risen in recent years, possibly due to changes in business model, leverage, or sector dynamics. If true beta is now ~1.35 (the midpoint), XYZ is substantially more market-sensitive than the 5-year regression implies — a fact that could reverse the risk-adjusted case for holding it.

Portfolio Implications: In a diversified portfolio, idiosyncratic risk washes out. What matters is systematic risk (beta) and alpha. ABC’s negative alpha and stable beta make it a candidate for underweighting. XYZ’s positive alpha is attractive, but beta instability requires deep investigation before overweighting — an analyst must resolve whether the beta has structurally shifted before committing capital.


4 Discussion

CAPM vs FF3: The two models yield different MVP weights precisely because they model covariance differently. CAPM’s single-factor structure understates the correlation between assets that share size or value tilts. The FF3 model’s richer three-dimensional covariance structure typically produces a more defensively positioned MVP — one that is better diversified across all three risk dimensions and less concentrated in any single return source.

MVP in Practice: The MVP ignores expected returns entirely and focuses purely on minimizing variance. This makes it robust to mean estimation error (which is notoriously difficult — errors in expected returns dramatically distort optimal weights) but may produce portfolios with unacceptably low expected returns. In practice, investors typically augment MVP with either a target return constraint (mean-variance efficient frontier) or a Bayesian prior (Black-Litterman model) to ensure the portfolio achieves a reasonable return level.

Rolling Windows: The 60-month window is a pragmatic tradeoff between noise (short window) and staleness (long window). Dropping one month of COVID-era data (March 2020 → April 2020 shift) can meaningfully change beta estimates because the COVID month contained extreme outlier returns. This illustrates the sensitivity of MVP weights to window selection — a core practical challenge in quantitative portfolio management.


5 Conclusion

This midterm analysis demonstrates the application of modern portfolio theory from raw data to empirical estimation and theoretical interpretation. Four key takeaways crystallize the learning:

  1. CAPM provides a useful but limited baseline. Its single-factor structure understates co-movement among assets with shared size or value exposures. The Fama-French Three-Factor Model offers a richer, more empirically grounded covariance structure that is better suited to capturing the true correlations in a multi-style ETF universe.

  2. The MVP is a powerful, practically important concept. By focusing entirely on covariance — and ignoring the notoriously noisy expected return estimates — the MVP is surprisingly robust. It naturally diversifies across low-correlation assets. However, the absence of a return target can produce portfolios with very modest expected returns, which is why practitioners augment it with return forecasts.

  3. Return distributions are non-stationary across long horizons. The six-portfolio split-half analysis reveals that means, variances, and higher moments all shift between the first and second halves of the 1930–2018 sample. This cautions strongly against naive extrapolation of historical statistics — a fundamental epistemic humility that every serious financial analyst must internalize.

  4. The Treynor-Black model provides an elegant framework for blending passive and active insights. Even modest, well-estimated alphas — measured relative to residual variance via the information ratio — can improve the Sharpe ratio of a portfolio. The model operationalizes the core insight that active management adds value only when alpha is sufficiently large relative to the idiosyncratic risk introduced.

Recurring Master Theme: Correlation structure, not just individual risk-return characteristics, is the master variable in portfolio construction. The optimizer’s job is to find the weights that exploit low correlations most efficiently. Investors who appreciate this — and who understand the limitations of any particular factor model — are better positioned to build resilient portfolios that perform across market regimes.


6 Appendix: Key Formulas

CAPM: \(E(r_i) = r_f + \beta_i [E(r_M) - r_f]\)

FF3: \(E(r_i) - r_f = \alpha_i + \beta_{i,M}(r_M - r_f) + \beta_{i,S}\text{SMB} + \beta_{i,H}\text{HML}\)

MVP Weights: \(\mathbf{w}^* = \dfrac{\boldsymbol{\Sigma}^{-1}\mathbf{1}}{\mathbf{1}'\boldsymbol{\Sigma}^{-1}\mathbf{1}}\)

Factor-Model Covariance: \(\boldsymbol{\Sigma} = \mathbf{B}\boldsymbol{\Sigma}_F\mathbf{B}' + \mathbf{D}\)

Sharpe Ratio: \(S = \dfrac{E(r_P) - r_f}{\sigma_P}\)

Capital Market Line (CML): \(E(r_C) = r_f + \dfrac{E(r_M) - r_f}{\sigma_M}\sigma_C\)

Treynor-Black: \(S_{optimal}^2 = S_{passive}^2 + \left(\dfrac{\alpha_A}{\sigma(\varepsilon_A)}\right)^2\)

Utility Function: \(U = E(r) - \frac{1}{2} A \sigma^2\)

Zero-Variance Portfolio (\(\rho = -1\)): \(w_A = \dfrac{\sigma_B}{\sigma_A + \sigma_B}\)


This document was prepared as a complete midterm submission. All R code is fully reproducible given the provided data files. Enhanced version includes additional visualizations (correlation heatmap, color-coded tables, callout boxes) and elaborated economic interpretations for all questions. Published to RPubs for grading.