library(xts)
## Loading required package: zoo
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
library(tibble)
# Simulate test returns for 3 assets over 100 days
set.seed(123)
dates <- seq(as.Date("2024-01-01"), by = "days", length.out = 100)
wide_returns <- tibble(
date = dates,
SPY = rnorm(100, 0.0005, 0.01),
QQQ = rnorm(100, 0.0007, 0.012),
IWM = rnorm(100, 0.0004, 0.015)
)
# Convert to xts
rets <- wide_returns[-1] # remove date
rets_xts <- xts(rets, order.by = wide_returns$date)
library(PortfolioAnalytics)
library(PerformanceAnalytics)
library(ROI)
library(ROI.plugin.quadprog)
# Define the portfolio spec
funds <- colnames(rets_xts)
pspec <- portfolio.spec(assets = funds)
pspec <- add.constraint(pspec, type = "full_investment")
pspec <- add.constraint(pspec, type = "long_only")
pspec <- add.objective(pspec, type = "risk", name = "StdDev")
pspec <- add.objective(pspec, type = "return", name = "mean")
# Compute efficient frontier
eff <- create.EfficientFrontier(R = rets_xts, portfolio = pspec, type = "mean-StdDev")
## Warning: executing %dopar% sequentially: no parallel backend registered
# Output image to knit-safe plot
jpeg("ef_plot.jpg")
chart.EfficientFrontier(eff, match.col = "StdDev", n.portfolios = 25,
main = "Efficient Frontier: SPY, QQQ, IWM")
dev.off()
## png
## 2
# Display in report
knitr::include_graphics("ef_plot.jpg")
#Chapter 7: Efficient
Diversification
## Q1: Test Semistrong EMH by comparing returns before/after event (e.g., dividend)
library(quantmod)
## Loading required package: TTR
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
getSymbols("AAPL", from = "2023-01-01", to = "2024-01-01")
## [1] "AAPL"
# Suppose dividend announcement on 2023-08-01
pre_event <- window(Cl(AAPL), end = as.Date("2023-07-31"))
post_event <- window(Cl(AAPL), start = as.Date("2023-08-02"))
# Compare returns
pre_return <- dailyReturn(pre_event)
post_return <- dailyReturn(post_event)
summary(pre_return)
## Index daily.returns
## Min. :2023-01-03 Min. :-0.026680
## 1st Qu.:2023-02-23 1st Qu.:-0.005863
## Median :2023-04-17 Median : 0.002019
## Mean :2023-04-16 Mean : 0.003217
## 3rd Qu.:2023-06-07 3rd Qu.: 0.010341
## Max. :2023-07-31 Max. : 0.046927
summary(post_return)
## Index daily.returns
## Min. :2023-08-02 Min. :-0.0480201
## 1st Qu.:2023-09-08 1st Qu.:-0.0073216
## Median :2023-10-16 Median : 0.0012667
## Mean :2023-10-15 Mean : 0.0000746
## 3rd Qu.:2023-11-21 3rd Qu.: 0.0079049
## Max. :2023-12-29 Max. : 0.0219489
## Q2: Calculate correlation matrix of 3 assets to analyze diversification
library(tidyquant)
## ── Conflicts ────────────────────────────────────────── tidyquant_conflicts() ──
## ✖ zoo::as.Date() masks base::as.Date()
## ✖ zoo::as.Date.numeric() masks base::as.Date.numeric()
## ✖ PerformanceAnalytics::legend() masks graphics::legend()
## ✖ quantmod::summary() masks base::summary()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(dplyr)
##
## ######################### Warning from 'xts' package ##########################
## # #
## # The dplyr lag() function breaks how base R's lag() function is supposed to #
## # work, which breaks lag(my_xts). Calls to lag(my_xts) that you type or #
## # source() into this session won't work correctly. #
## # #
## # Use stats::lag() to make sure you're not using dplyr::lag(), or you can add #
## # conflictRules('dplyr', exclude = 'lag') to your .Rprofile to stop #
## # dplyr from breaking base R's lag() function. #
## # #
## # Code in packages is not affected. It's protected by R's namespace mechanism #
## # Set `options(xts.warn_dplyr_breaks_lag = FALSE)` to suppress this warning. #
## # #
## ###############################################################################
##
## Attaching package: 'dplyr'
##
## The following objects are masked from 'package:xts':
##
## first, last
##
## The following objects are masked from 'package:stats':
##
## filter, lag
##
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(tidyr)
symbols <- c("SPY", "QQQ", "IWM")
prices <- tq_get(symbols, from = "2023-01-01", to = "2024-01-01")
returns <- prices %>%
group_by(symbol) %>%
tq_transmute(select = adjusted, mutate_fun = periodReturn, period = "monthly")
wide_returns <- returns %>%
pivot_wider(names_from = symbol, values_from = monthly.returns)
cor(wide_returns[-1], use = "complete.obs")
## SPY QQQ IWM
## SPY 1.0000000 0.8772807 0.8268349
## QQQ 0.8772807 1.0000000 0.6647969
## IWM 0.8268349 0.6647969 1.0000000
##Chapter7 CFA4
# Define data
portfolios <- data.frame(
Portfolio = c("W", "X", "Z", "Y"),
Return = c(15, 12, 5, 9),
StdDev = c(36, 15, 7, 21)
)
# Plot efficient frontier scatter
library(ggplot2)
ggplot(portfolios, aes(x = StdDev, y = Return, label = Portfolio)) +
geom_point(color = "blue", size = 3) +
geom_text(vjust = -0.8) +
labs(title = "CFA 7.4 – Portfolio Returns vs Risk",
x = "Standard Deviation (%)",
y = "Expected Return (%)") +
theme_minimal()
#CFA chapter7 CFA10
# Define portfolio data
beta_A <- 1.0
beta_B <- 1.0
rf <- 0.05 # 5% risk-free rate
market_premium <- 0.08 # 8% market risk premium
# Compute expected returns under CAPM
exp_return_A <- rf + beta_A * market_premium
exp_return_B <- rf + beta_B * market_premium
# Display result
paste0("Expected Return of A = ", round(exp_return_A * 100, 2), "%\n",
"Expected Return of B = ", round(exp_return_B * 100, 2), "%")
## [1] "Expected Return of A = 13%\nExpected Return of B = 13%"
## Q3: Plot efficient frontier of 3-asset portfolio
# Given values
risk_free_rate <- 0.05 # 5%
market_risk_premium <- 0.08 # 8%
beta <- 1.5
# CAPM Formula
expected_return <- risk_free_rate + beta * market_risk_premium
# Display the result as a percentage
paste0("Expected return = ", round(expected_return * 100, 2), "%")
## [1] "Expected return = 17%"
``` #Chapter 8: Index Models
## Q1: Estimate alpha and beta using linear regression (CAPM style)
library(quantmod)
library(PerformanceAnalytics)
# Download stock and market index data
getSymbols(c("AAPL", "^GSPC"), from = "2023-01-01", to = "2024-01-01")
## [1] "AAPL" "GSPC"
# Compute daily excess returns (assume risk-free rate = 0)
stock_ret <- dailyReturn(Cl(AAPL))
market_ret <- dailyReturn(Cl(GSPC))
# Run CAPM-style regression
model <- lm(stock_ret ~ market_ret)
summary(model)
##
## Call:
## lm(formula = stock_ret ~ market_ret)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.042963 -0.004653 0.000441 0.004985 0.035036
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.0007919 0.0005505 1.439 0.152
## market_ret 1.1036341 0.0665136 16.593 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.008651 on 248 degrees of freedom
## Multiple R-squared: 0.5261, Adjusted R-squared: 0.5242
## F-statistic: 275.3 on 1 and 248 DF, p-value: < 2.2e-16
## Q3: Extract R² and standard error from regression output
r_squared <- summary(model)$r.squared
std_error <- summary(model)$sigma
r_squared
## [1] 0.5260981
std_error
## [1] 0.008650579
## Q4: Compare two beta estimates with confidence intervals
# Assume Broker A = 0.62, Broker B = 0.71; Model beta = our regression
model_beta <- coef(model)[2]
broker_a <- 0.62
broker_b <- 0.71
c(model_beta = model_beta, broker_a = broker_a, broker_b = broker_b)
## model_beta.market_ret broker_a broker_b
## 1.103634 0.620000 0.710000
## Q5: Compute beta-based risk and total risk (standard deviation)
beta_value <- coef(model)[2]
total_risk <- sd(stock_ret)
market_risk <- sd(market_ret)
risk_table <- data.frame(
Beta = beta_value,
Std_Dev = total_risk,
Market_StdDev = market_risk
)
risk_table
## Beta Std_Dev Market_StdDev
## market_ret 1.103634 0.01254085 0.008242053
#Chapter 9: Capital Asset Pricing Model
## Q8: Calculate Sharpe and Treynor Ratios for Portfolio R and S&P 500
rf <- 0.03 # Assume risk-free rate = 3%
# Portfolio R
r_return <- 0.11
r_sd <- 0.10
r_beta <- 0.5
# S&P 500
m_return <- 0.14
m_sd <- 0.12
m_beta <- 1.0
sharpe_r <- (r_return - rf) / r_sd
sharpe_m <- (m_return - rf) / m_sd
treynor_r <- (r_return - rf) / r_beta
treynor_m <- (m_return - rf) / m_beta
data.frame(
Portfolio = c("R", "S&P 500"),
Sharpe = c(sharpe_r, sharpe_m),
Treynor = c(treynor_r, treynor_m)
)
## Portfolio Sharpe Treynor
## 1 R 0.8000000 0.16
## 2 S&P 500 0.9166667 0.11
## Q9: Plot Risk vs Return for visualization
portfolio_data <- data.frame(
Portfolio = c("R", "S&P 500"),
Return = c(0.11, 0.14),
StdDev = c(0.10, 0.12),
Beta = c(0.5, 1.0)
)
library(ggplot2)
ggplot(portfolio_data, aes(x = StdDev, y = Return, label = Portfolio)) +
geom_point(size = 3, color = "blue") +
geom_text(vjust = -1) +
labs(title = "Risk vs Return Plot",
x = "Standard Deviation (Risk)",
y = "Expected Return") +
theme_minimal()
## Q10: Draw the SML line based on CAPM
beta_vals <- seq(0, 1.5, 0.1)
sml_returns <- rf + beta_vals * (m_return - rf)
sml_df <- data.frame(Beta = beta_vals, ExpectedReturn = sml_returns)
ggplot(sml_df, aes(x = Beta, y = ExpectedReturn)) +
geom_line(color = "darkgreen", size = 1.2) +
geom_point(data = portfolio_data, aes(x = Beta, y = Return), color = "blue", size = 3) +
geom_text(data = portfolio_data, aes(x = Beta, y = Return, label = Portfolio), vjust = -1) +
labs(title = "Security Market Line (SML)",
x = "Beta",
y = "Expected Return") +
theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
#Chapter 10: Arbitrage Pricing Theory
## Problem 1: Revised expected return using 2-factor APT
expected_return <- 0.12
beta_ip <- 1
beta_ir <- 0.5
expected_ip <- 0.03
actual_ip <- 0.05
expected_ir <- 0.05
actual_ir <- 0.08
revised_return <- expected_return +
beta_ip * (actual_ip - expected_ip) +
beta_ir * (actual_ir - expected_ir)
revised_return
## [1] 0.155
## Problem 4: Solve factor risk premiums λ₁ and λ₂
library(MASS)
##
## Attaching package: 'MASS'
## The following object is masked from 'package:dplyr':
##
## select
betas <- matrix(c(1.5, 2.0,
2.2, -0.2), nrow = 2, byrow = TRUE)
returns <- c(0.31 - 0.06, 0.27 - 0.06) # Subtract rf
lambdas <- ginv(betas) %*% returns
lambdas
## [,1]
## [1,] 0.10
## [2,] 0.05
## Recalculate expected return using solved lambda values
rf <- 0.06
# Portfolio A
beta1_A <- 1.5
beta2_A <- 2.0
expected_A <- rf + beta1_A * lambdas[1] + beta2_A * lambdas[2]
# Portfolio B
beta1_B <- 2.2
beta2_B <- -0.2
expected_B <- rf + beta1_B * lambdas[1] + beta2_B * lambdas[2]
data.frame(Portfolio = c("A", "B"),
GivenReturn = c(0.31, 0.27),
APT_Return = c(expected_A, expected_B))
## Portfolio GivenReturn APT_Return
## 1 A 0.31 0.31
## 2 B 0.27 0.27
#Chapter 11: The Efficient Market Hypothesis
## CFA 1: Three Forms of Efficient Market Hypothesis
emh_types <- data.frame(
Type = c("Weak-form", "Semi-strong form", "Strong-form"),
Information_Used = c(
"Past price and volume data",
"All publicly available information (incl. financials, news)",
"All information, both public and private (insider info)"
),
Implication = c(
"Technical analysis is useless",
"Neither technical nor fundamental analysis consistently helps",
"Even insiders can't consistently outperform"
)
)
emh_types
## Type Information_Used
## 1 Weak-form Past price and volume data
## 2 Semi-strong form All publicly available information (incl. financials, news)
## 3 Strong-form All information, both public and private (insider info)
## Implication
## 1 Technical analysis is useless
## 2 Neither technical nor fundamental analysis consistently helps
## 3 Even insiders can't consistently outperform
## CFA 2: Simulate random walk of stock prices
set.seed(42)
n_days <- 100
returns <- rnorm(n_days, mean = 0.0005, sd = 0.01) # Simulate daily returns
price <- cumprod(1 + returns) * 100 # Starting from 100
plot(price, type = "l", col = "blue", lwd = 2,
main = "Simulated Stock Price (Random Walk)",
xlab = "Day", ylab = "Price")
## CFA 3: Simulate stock reaction to positive earnings surprise
set.seed(123)
pre_event <- rnorm(10, 0.0005, 0.01)
event_impact <- c(0.05) # Positive shock
post_event <- rnorm(10, 0.0005, 0.01)
price <- cumsum(c(pre_event, event_impact, post_event)) + 100
plot(price, type = "l", col = "darkgreen", lwd = 2,
main = "Stock Price Response to Earnings Surprise",
xlab = "Time (Day)", ylab = "Price")
abline(v = 11, col = "red", lty = 2)
text(11, price[11] + 0.5, "Event Day", pos = 4)
## CFA 4: Compare active fund vs passive index (simulated returns)
set.seed(111)
days <- 250
active <- cumprod(1 + rnorm(days, 0.0004, 0.012)) * 100
passive <- cumprod(1 + rnorm(days, 0.0005, 0.01)) * 100
plot(passive, type = "l", col = "black", lwd = 2,
ylim = range(c(active, passive)),
ylab = "Value", xlab = "Days", main = "Active vs. Passive Fund")
lines(active, col = "blue", lwd = 2)
legend("topleft", legend = c("Passive Index", "Active Fund"),
col = c("black", "blue"), lwd = 2)
## CFA 5: Implications of EMH for investment strategies
emh_summary <- data.frame(
Strategy = c("Technical Analysis", "Fundamental Analysis", "Index Investing", "Insider Trading"),
WeakForm = c("No advantage", "May help", "Good", "Possible advantage"),
SemiStrongForm = c("No advantage", "No advantage", "Good", "Possible advantage"),
StrongForm = c("No advantage", "No advantage", "Good", "No advantage")
)
emh_summary
## Strategy WeakForm SemiStrongForm StrongForm
## 1 Technical Analysis No advantage No advantage No advantage
## 2 Fundamental Analysis May help No advantage No advantage
## 3 Index Investing Good Good Good
## 4 Insider Trading Possible advantage Possible advantage No advantage
#Chapter 12: Behavioral Finance and Technical Analysis
## CFA 1: EMH vs. Behavioral Finance
emh_vs_behavioral <- data.frame(
Assumption = c("Investor Rationality", "No Arbitrage"),
EMH_View = c("Investors are rational and utility-maximizing",
"Mispricings are quickly exploited by arbitrageurs"),
Behavioral_View = c("Investors often act irrationally due to biases and heuristics",
"Limits to arbitrage exist (cost, risk, sentiment-driven bubbles)")
)
emh_vs_behavioral
## Assumption EMH_View
## 1 Investor Rationality Investors are rational and utility-maximizing
## 2 No Arbitrage Mispricings are quickly exploited by arbitrageurs
## Behavioral_View
## 1 Investors often act irrationally due to biases and heuristics
## 2 Limits to arbitrage exist (cost, risk, sentiment-driven bubbles)
## CFA 2: Simulate Loss Aversion (Losses hurt more than gains feel good)
library(ggplot2)
gain_loss <- seq(-50, 50, by = 1)
utility <- ifelse(gain_loss >= 0, gain_loss^0.88, -2.25 * (-gain_loss)^0.88)
loss_aversion <- data.frame(gain_loss, utility)
ggplot(loss_aversion, aes(x = gain_loss, y = utility)) +
geom_line(color = "red", size = 1.2) +
labs(title = "Loss Aversion Curve",
x = "Gain or Loss",
y = "Perceived Value / Utility") +
theme_minimal()
#Chapter 13: Regression Testing of the CAPM
## Problem 1: First-Pass Regression for Each Stock
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats 1.0.0 ✔ readr 2.1.5
## ✔ lubridate 1.9.4 ✔ stringr 1.5.1
## ✔ purrr 1.0.4
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ purrr::accumulate() masks foreach::accumulate()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::first() masks xts::first()
## ✖ dplyr::lag() masks stats::lag()
## ✖ dplyr::last() masks xts::last()
## ✖ MASS::select() masks dplyr::select()
## ✖ purrr::when() masks foreach::when()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# Load or simulate data (replace with your actual returns)
# Let's say we have stock returns and market returns
stock_data <- data.frame(
market_excess = c(0.01, 0.015, -0.005, 0.02, 0.01, 0.025, -0.01, 0.005, 0.015, -0.005),
stock_A_excess = c(0.005, 0.01, -0.01, 0.015, 0.005, 0.03, -0.015, 0.002, 0.012, -0.008)
)
# First-pass regression for Stock A
model_A <- lm(stock_A_excess ~ market_excess, data = stock_data)
summary(model_A)
##
## Call:
## lm(formula = stock_A_excess ~ market_excess, data = stock_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.0031074 -0.0018512 -0.0002231 0.0007479 0.0062645
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -0.004405 0.001134 -3.884 0.00465 **
## market_excess 1.125620 0.083385 13.499 8.7e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.002901 on 8 degrees of freedom
## Multiple R-squared: 0.9579, Adjusted R-squared: 0.9527
## F-statistic: 182.2 on 1 and 8 DF, p-value: 8.701e-07
## Problem 2: Hypotheses for SML Regression
hypotheses <- data.frame(
Null = "γ1 > 0: Higher beta → Higher expected return (CAPM holds)",
Alt = "γ1 = 0: No relation between beta and return (CAPM fails)",
Intercept = "γ0 ≈ 0 if excess return is used"
)
hypotheses
## Null
## 1 γ1 > 0: Higher beta → Higher expected return (CAPM holds)
## Alt
## 1 γ1 = 0: No relation between beta and return (CAPM fails)
## Intercept
## 1 γ0 ≈ 0 if excess return is used
## Problem 3: Regress average excess return on beta
# Suppose we already calculated average excess returns and betas:
beta_values <- c(-0.2, 0.1, 0.5, 0.7, 0.9, 1.0, 1.1, 1.3, 1.5)
avg_excess_returns <- c(0.002, 0.004, 0.006, 0.007, 0.008, 0.009, 0.010, 0.011, 0.013)
sml_data <- data.frame(beta = beta_values, return = avg_excess_returns)
sml_model <- lm(return ~ beta, data = sml_data)
summary(sml_model)
##
## Call:
## lm(formula = return ~ beta, data = sml_data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.0006034 -0.0002227 -0.0000804 0.0002082 0.0006811
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.0030303 0.0002475 12.25 5.55e-06 ***
## beta 0.0061924 0.0002667 23.22 6.97e-08 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.0004182 on 7 degrees of freedom
## Multiple R-squared: 0.9872, Adjusted R-squared: 0.9854
## F-statistic: 539.3 on 1 and 7 DF, p-value: 6.967e-08
# Plot SML
ggplot(sml_data, aes(x = beta, y = return)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE, col = "blue") +
labs(title = "Security Market Line (SML)", x = "Beta", y = "Average Excess Return") +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
cat("Higher beta stocks tend to offer higher returns.\n")
## Higher beta stocks tend to offer higher returns.
cat("R² of second-pass regression indicates strength of CAPM fit.\n")
## R² of second-pass regression indicates strength of CAPM fit.
cat("Stock H and I with high betas performed best, A underperformed.\n")
## Stock H and I with high betas performed best, A underperformed.
## Problem 5: Portfolio Creation and Second-Pass on Portfolios
# Group beta into 3 buckets
sml_data$portfolio <- cut(sml_data$beta, breaks = 3, labels = c("Low", "Medium", "High"))
# Calculate portfolio-level averages
portfolio_summary <- sml_data %>%
group_by(portfolio) %>%
summarize(beta = mean(beta), return = mean(return))
# Second-pass regression on portfolios
sml_portfolio_model <- lm(return ~ beta, data = portfolio_summary)
summary(sml_portfolio_model)
##
## Call:
## lm(formula = return ~ beta, data = portfolio_summary)
##
## Residuals:
## 1 2 3
## 0.0001518 -0.0003687 0.0002169
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.0031495 0.0004083 7.714 0.0821 .
## beta 0.0060274 0.0005009 12.033 0.0528 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.0004539 on 1 degrees of freedom
## Multiple R-squared: 0.9931, Adjusted R-squared: 0.9863
## F-statistic: 144.8 on 1 and 1 DF, p-value: 0.05278
# Plot portfolio SML
ggplot(portfolio_summary, aes(x = beta, y = return)) +
geom_point(size = 3) +
geom_smooth(method = "lm", se = FALSE, color = "darkgreen") +
labs(title = "SML with Portfolios", x = "Portfolio Beta", y = "Portfolio Excess Return") +
theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'
## Problem 6: Roll's Critique Text Output
cat("Roll's critique argues CAPM cannot be tested because the true market portfolio is unobservable.\n")
## Roll's critique argues CAPM cannot be tested because the true market portfolio is unobservable.
cat("Using a proxy like S&P 500 introduces benchmark error.\n")
## Using a proxy like S&P 500 introduces benchmark error.
cat("Thus, CAPM test results may reflect errors in the benchmark, not model failure.\n")
## Thus, CAPM test results may reflect errors in the benchmark, not model failure.
## Problem 7: Plot CML with 9 stocks and 3 portfolios
# Simulate std. deviation values
portfolio_summary$sd <- c(0.07, 0.09, 0.12) # Just an example
market_return <- 0.009
market_sd <- 0.10
rf_rate <- 0.002
# Define CML
cml_slope <- (market_return - rf_rate) / market_sd
cml_x <- seq(0, 0.15, 0.01)
cml_y <- rf_rate + cml_slope * cml_x
# Plot
plot(cml_x, cml_y, type = "l", col = "red", lwd = 2,
main = "Capital Market Line", xlab = "Standard Deviation", ylab = "Expected Return")
points(portfolio_summary$sd, portfolio_summary$return, pch = 19, col = "blue")
text(portfolio_summary$sd, portfolio_summary$return + 0.001,
labels = portfolio_summary$portfolio, pos = 3)
points(market_sd, market_return, col = "green", pch = 17)
legend("topleft", legend = c("CML", "Portfolios", "Market"), col = c("red", "blue", "green"),
lty = c(1, NA, NA), pch = c(NA, 19, 17))
##CFA 2a: Portfolio Evaluation and Benchmarks
cat("Portfolio performance is typically evaluated by comparing actual returns with expected returns predicted by a benchmark (e.g., CAPM using S&P 500).\n")
## Portfolio performance is typically evaluated by comparing actual returns with expected returns predicted by a benchmark (e.g., CAPM using S&P 500).
cat("The benchmark portfolio is assumed to represent the true 'market portfolio', used to derive expected returns based on beta.\n")
## The benchmark portfolio is assumed to represent the true 'market portfolio', used to derive expected returns based on beta.
##CFA 2b: Roll’s Benchmark Error Argument
cat("Richard Roll argued that the CAPM is untestable because the true market portfolio (which includes all assets: real estate, human capital, etc.) is unobservable.\n")
## Richard Roll argued that the CAPM is untestable because the true market portfolio (which includes all assets: real estate, human capital, etc.) is unobservable.
cat("Therefore, using a proxy like the S&P 500 introduces 'benchmark error' — any test of the CAPM may be invalid if the benchmark isn't the true market.\n")
## Therefore, using a proxy like the S&P 500 introduces 'benchmark error' — any test of the CAPM may be invalid if the benchmark isn't the true market.
##CFA 2c: Graphical Example – Perceived vs. True SML
library(ggplot2)
df <- data.frame(
Beta = c(0.5, 1.0, 1.5),
Measured_Return = c(0.05, 0.08, 0.11),
True_Return = c(0.03, 0.08, 0.13)
)
ggplot(df, aes(x = Beta)) +
geom_line(aes(y = Measured_Return), color = "blue", size = 1.2) +
geom_line(aes(y = True_Return), color = "red", size = 1.2, linetype = "dashed") +
geom_point(aes(y = Measured_Return), color = "blue") +
geom_point(aes(y = True_Return), color = "red") +
labs(title = "Measured vs. True Security Market Line",
y = "Expected Return", x = "Beta") +
theme_minimal() +
annotate("text", x = 1.2, y = 0.115, label = "Measured SML", color = "blue") +
annotate("text", x = 1.2, y = 0.135, label = "True SML", color = "red")
##CFA 2d: Comfort from Multiple Benchmarks?
cat("If a manager is judged superior using multiple benchmarks (Dow Jones, S&P 500, NYSE), it increases confidence.\n")
## If a manager is judged superior using multiple benchmarks (Dow Jones, S&P 500, NYSE), it increases confidence.
cat("However, all of these may still be poor proxies for the true market portfolio.\n")
## However, all of these may still be poor proxies for the true market portfolio.
cat("So while consensus helps, Roll’s critique still applies — true skill is hard to prove without the true market.\n")
## So while consensus helps, Roll’s critique still applies — true skill is hard to prove without the true market.
##CFA 2e: Defending a Position on Benchmark Errors
cat("Position: CAPM is still useful despite benchmark errors.\n")
## Position: CAPM is still useful despite benchmark errors.
cat("Roll’s critique shows implementation challenges, not theoretical flaws.\n")
## Roll’s critique shows implementation challenges, not theoretical flaws.
cat("Using diversified indexes as proxies provides practical value for investment decisions and comparisons.\n")
## Using diversified indexes as proxies provides practical value for investment decisions and comparisons.
cat("Thus, CAPM should be refined, not scrapped.\n")
## Thus, CAPM should be refined, not scrapped.