For a portfolio with weight \(w_m\) in the S&P 500 and \(w_f = 1 - w_m\) in T-bills:
\[E(r_p) = w_f \cdot r_f + w_m \cdot r_m \qquad \sigma_p^2 = (w_m \cdot \sigma_m)^2\]
weights <- c(0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
E_rp <- (1 - weights) * rf + weights * rm
Var_p <- (weights * sig_m)^2
Sig_p <- sqrt(Var_p)
p10 <- data.frame(
`Weight in S&P 500` = weights,
`Expected Return` = paste0(round(E_rp * 100, 1), "%"),
`Variance` = round(Var_p, 4),
`Std Deviation` = paste0(round(Sig_p * 100, 1), "%"),
check.names = FALSE
)
kable(p10, align = "c", caption = "Table 1: Portfolio Return and Risk")| Weight in S&P 500 | Expected Return | Variance | Std Deviation |
|---|---|---|---|
| 0.0 | 5% | 0.0000 | 0% |
| 0.2 | 6.6% | 0.0016 | 4% |
| 0.4 | 8.2% | 0.0064 | 8% |
| 0.6 | 9.8% | 0.0144 | 12% |
| 0.8 | 11.4% | 0.0256 | 16% |
| 1.0 | 13% | 0.0400 | 20% |
w_seq <- seq(0, 1, by = 0.01)
er_seq <- (1 - w_seq) * rf + w_seq * rm
sig_seq <- w_seq * sig_m
ggplot(data.frame(sigma = sig_seq, er = er_seq), aes(x = sigma, y = er)) +
geom_line(color = "#2c7bb6", linewidth = 1.2) +
geom_point(data = data.frame(sigma = Sig_p, er = E_rp),
color = "red", size = 3) +
geom_text(data = data.frame(sigma = Sig_p, er = E_rp,
label = paste0("w=", weights)),
aes(label = label), vjust = -0.9, size = 3.2) +
scale_x_continuous(labels = scales::percent_format()) +
scale_y_continuous(labels = scales::percent_format()) +
labs(title = "Capital Allocation Line (CAL)",
x = "Portfolio Standard Deviation (σ)",
y = "Expected Return E(r)") +
theme_minimal(base_size = 13)\[U = E(r_p) - A \cdot \sigma_p^2\]
A2 <- 2
U2 <- E_rp - A2 * Var_p
p11 <- data.frame(
`Weight in S&P 500` = weights,
`E(r)` = round(E_rp, 4),
`Variance` = round(Var_p, 4),
`Utility (A=2)` = round(U2, 4),
check.names = FALSE
)
kable(p11, align = "c", caption = "Table 2: Utility Scores with A = 2")| Weight in S&P 500 | E(r) | Variance | Utility (A=2) |
|---|---|---|---|
| 0.0 | 0.050 | 0.0000 | 0.0500 |
| 0.2 | 0.066 | 0.0016 | 0.0628 |
| 0.4 | 0.082 | 0.0064 | 0.0692 |
| 0.6 | 0.098 | 0.0144 | 0.0692 |
| 0.8 | 0.114 | 0.0256 | 0.0628 |
| 1.0 | 0.130 | 0.0400 | 0.0500 |
best11 <- weights[which.max(U2)]
cat("Best Weight in S&P 500:", best11, " | Max Utility:", round(max(U2), 4))## Best Weight in S&P 500: 0.6 | Max Utility: 0.0692
\[w^* = \frac{E(r_m) - r_f}{A \cdot \sigma_m^2}\]
w_star_A2 <- (rm - rf) / (A2 * sig_m^2)
cat("Analytical w* (A=2):", round(w_star_A2, 4), "=", round(w_star_A2*100, 1), "%")## Analytical w* (A=2): 1 = 100 %
ggplot(data.frame(w = weights, U = U2), aes(x = w, y = U)) +
geom_line(color = "#1a9641", linewidth = 1.2) +
geom_point(size = 3, color = "#1a9641") +
geom_point(data = data.frame(w = best11, U = max(U2)),
color = "red", size = 5, shape = 18) +
scale_x_continuous(labels = scales::percent_format()) +
labs(title = "Utility Function: A = 2",
subtitle = "Red diamond = maximum utility",
x = "Weight in S&P 500", y = "Utility") +
theme_minimal(base_size = 13)Conclusion: With A = 2, the analytical optimum is 100% in S&P 500.
Moderate risk aversion leads to a balanced allocation.
A3 <- 3
U3 <- E_rp - A3 * Var_p
p12 <- data.frame(
`Weight in S&P 500` = weights,
`E(r)` = round(E_rp, 4),
`Variance` = round(Var_p, 4),
`Utility (A=3)` = round(U3, 4),
check.names = FALSE
)
kable(p12, align = "c", caption = "Table 3: Utility Scores with A = 3")| Weight in S&P 500 | E(r) | Variance | Utility (A=3) |
|---|---|---|---|
| 0.0 | 0.050 | 0.0000 | 0.0500 |
| 0.2 | 0.066 | 0.0016 | 0.0612 |
| 0.4 | 0.082 | 0.0064 | 0.0628 |
| 0.6 | 0.098 | 0.0144 | 0.0548 |
| 0.8 | 0.114 | 0.0256 | 0.0372 |
| 1.0 | 0.130 | 0.0400 | 0.0100 |
w_star_A3 <- (rm - rf) / (A3 * sig_m^2)
cat("Analytical w* (A=3):", round(w_star_A3, 4), "=", round(w_star_A3*100, 1), "%")## Analytical w* (A=3): 0.6667 = 66.7 %
df_compare <- data.frame(
w = rep(weights, 2),
U = c(U2, U3),
A = rep(c("A = 2", "A = 3"), each = length(weights))
)
ggplot(df_compare, aes(x = w, y = U, color = A)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
scale_x_continuous(labels = scales::percent_format()) +
scale_color_manual(values = c("A = 2" = "#1a9641", "A = 3" = "#d7191c")) +
labs(title = "Utility Comparison: A = 2 vs A = 3",
x = "Weight in S&P 500", y = "Utility", color = "Risk Aversion") +
theme_minimal(base_size = 13)Conclusion: Higher risk aversion (A = 3) shifts the optimal portfolio to 66.7% in S&P 500,
compared to 100% for A = 2. More risk-averse investors hold more T-bills.
\[U = E(r) - 4\sigma^2\]
A_cfa <- 4
cfa1 <- data.frame(
Investment = 1:4,
`E(r)` = c(0.12, 0.15, 0.21, 0.24),
Variance = c(0.0225, 0.1225, 0.0400, 0.0625),
check.names = FALSE
)
cfa1 <- cfa1 %>%
mutate(`Utility (A=4)` = round(`E(r)` - A_cfa * Variance, 4))
kable(cfa1, align = "c", caption = "Table 4: CFA Problem 1 — Investment Utilities")| Investment | E(r) | Variance | Utility (A=4) |
|---|---|---|---|
| 1 | 0.12 | 0.0225 | 0.03 |
| 2 | 0.15 | 0.1225 | -0.34 |
| 3 | 0.21 | 0.0400 | 0.05 |
| 4 | 0.24 | 0.0625 | -0.01 |
best_cfa1 <- cfa1$Investment[which.max(cfa1$`Utility (A=4)`)]
cat("Best Investment: #", best_cfa1, "| Utility =", max(cfa1$`Utility (A=4)`))## Best Investment: # 3 | Utility = 0.05
Answer: Investment 3 yields the highest utility of 0.05.
A risk-neutral investor has A = 0, so \(U = E(r)\). They simply maximise expected return.
returns_cfa2 <- c(0.12, 0.15, 0.21, 0.24)
best_cfa2 <- which.max(returns_cfa2)
df_cfa2 <- data.frame(Investment = 1:4,
`E(r)` = paste0(returns_cfa2*100, "%"),
check.names = FALSE)
kable(df_cfa2, align = "c", caption = "Table 5: CFA Problem 2 — Returns")| Investment | E(r) |
|---|---|
| 1 | 12% |
| 2 | 15% |
| 3 | 21% |
| 4 | 24% |
## Best Investment: # 4 | Return = 24 %
Answer: Investment 4 — the highest expected return, with no variance penalty.
The mean-variance utility function is:
\[U = E(r_p) - \frac{1}{2} A \sigma_p^2\]
| Value of A | Interpretation |
|---|---|
| A > 0 | Risk-averse (prefers less risk) |
| A = 0 | Risk-neutral (ignores variance) |
| A < 0 | Risk-seeking (prefers more risk) |
Answer: (b) Investor’s degree of risk aversion.
A larger A means the investor penalises variance more heavily and will accept a lower expected
return in exchange for a reduction in portfolio risk.
summary_df <- data.frame(
Problem = c("10", "11 (A=2)", "12 (A=3)", "CFA 1 (A=4)", "CFA 2", "CFA 3"),
Answer = c(
"See CAL table & plot above",
paste0("w* = ", round(w_star_A2*100,1), "% in S&P 500"),
paste0("w* = ", round(w_star_A3*100,1), "% in S&P 500"),
paste0("Investment ", best_cfa1, " (U = ", max(cfa1[["Utility (A=4)"]]), ")"),
paste0("Investment ", best_cfa2, " (highest return = 24%)"),
"b. Investor's degree of risk aversion"
)
)
kable(summary_df, align = "l", caption = "Final Summary")| Problem | Answer |
|---|---|
| 10 | See CAL table & plot above |
| 11 (A=2) | w* = 100% in S&P 500 |
| 12 (A=3) | w* = 66.7% in S&P 500 |
| CFA 1 (A=4) | Investment 3 (U = 0.05) |
| CFA 2 | Investment 4 (highest return = 24%) |
| CFA 3 | b. Investor’s degree of risk aversion |