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)
## Loading required package: foreach
## Loading required package: PerformanceAnalytics
## 
## Attaching package: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
## 
##     legend
## Registered S3 method overwritten by 'PortfolioAnalytics':
##   method           from
##   print.constraint ROI
library(PerformanceAnalytics)
library(ROI)
## ROI: R Optimization Infrastructure
## Registered solver plugins: nlminb, symphony, quadprog.
## Default solver: auto.
## 
## Attaching package: 'ROI'
## The following objects are masked from 'package:PortfolioAnalytics':
## 
##     is.constraint, objective
library(ROI.plugin.quadprog)

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")
library(quantmod)
## Loading required package: TTR
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
library(PerformanceAnalytics)
library(tseries)

symbols <- c("SPY", "QQQ", "IWM")
getSymbols(symbols, from = "2020-01-01", to = "2024-12-31")
## [1] "SPY" "QQQ" "IWM"
rets <- na.omit(Return.calculate(merge(Cl(SPY), Cl(QQQ), Cl(IWM))))
colnames(rets) <- symbols

target_returns <- seq(min(colMeans(rets)), max(colMeans(rets)), length.out = 25)

frontier <- lapply(target_returns, function(target) {
  portfolio.optim(x = rets, pm = target)
})

means <- sapply(frontier, function(x) x$pm)
risks <- sapply(frontier, function(x) sqrt(x$ps))

jpeg("ef_plot.jpg")
plot(risks, means, type = "l", col = "blue", lwd = 2,
     xlab = "Risk (Standard Deviation)", ylab = "Expected Return",
     main = "Efficient Frontier: SPY, QQQ, IWM")
points(risks, means, pch = 16, col = "red")
dev.off()
## png 
##   2
knitr::include_graphics("ef_plot.jpg")

library(quantmod)
getSymbols("AAPL", from = "2023-01-01", to = "2024-01-01")
## [1] "AAPL"
pre_event <- window(Cl(AAPL), end = as.Date("2023-07-31"))
post_event <- window(Cl(AAPL), start = as.Date("2023-08-02"))

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
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.8772802 0.8268346
## QQQ 0.8772802 1.0000000 0.6647968
## IWM 0.8268346 0.6647968 1.0000000
portfolios <- data.frame(
  Portfolio = c("W", "X", "Z", "Y"),
  Return = c(15, 12, 5, 9),
  StdDev = c(36, 15, 7, 21)
)

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

beta_A <- 1.0
beta_B <- 1.0
rf <- 0.05               
market_premium <- 0.08   

exp_return_A <- rf + beta_A * market_premium
exp_return_B <- rf + beta_B * market_premium

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%"
risk_free_rate <- 0.05      
market_risk_premium <- 0.08   
beta <- 1.5

expected_return <- risk_free_rate + beta * market_risk_premium

paste0("Expected return = ", round(expected_return * 100, 2), "%")
## [1] "Expected return = 17%"
library(quantmod)
library(PerformanceAnalytics)

getSymbols(c("AAPL", "^GSPC"), from = "2023-01-01", to = "2024-01-01")
## [1] "AAPL" "GSPC"
stock_ret <- dailyReturn(Cl(AAPL))
market_ret <- dailyReturn(Cl(GSPC))

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
r_squared <- summary(model)$r.squared
std_error <- summary(model)$sigma

r_squared
## [1] 0.5260981
std_error
## [1] 0.008650579
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
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
rf <- 0.03  

r_return <- 0.11
r_sd <- 0.10
r_beta <- 0.5

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

install.packages("ggplot2")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.4'
## (as 'lib' is unspecified)
install.packages("dplyr")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.4'
## (as 'lib' is unspecified)
library(ggplot2)
library(dplyr)

rf <- 0.03              
m_return <- 0.10    

beta_vals <- seq(0, 1.5, by = 0.1)
sml_returns <- rf + beta_vals * (m_return - rf)

sml_df <- data.frame(
  Beta = beta_vals,
  ExpectedReturn = sml_returns
)

portfolio_data <- data.frame(
  Portfolio = c("A", "B", "C"),
  Beta = c(0.5, 1.0, 1.3),
  Return = c(0.06, 0.10, 0.13)
)

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.

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
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
rf <- 0.06

beta1_A <- 1.5
beta2_A <- 2.0

expected_A <- rf + beta1_A * lambdas[1] + beta2_A * lambdas[2]

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

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)

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)

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

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

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
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
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
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.
sml_data$portfolio <- cut(sml_data$beta, breaks = 3, labels = c("Low", "Medium", "High"))

portfolio_summary <- sml_data %>%
  group_by(portfolio) %>%
  summarize(beta = mean(beta), return = mean(return))

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
ggplot(portfolio_summary, aes(x = beta, y = return)) +
  geom_point(size = 3) +
  geom_smooth(method = "lm", se = FALSE, color = "pink") +
  labs(title = "SML with Portfolios", x = "Portfolio Beta", y = "Portfolio Excess Return") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

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.
portfolio_summary$sd <- c(0.07, 0.09, 0.12)  
market_return <- 0.009
market_sd <- 0.10
rf_rate <- 0.002

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(cml_x, cml_y, type = "l", col = "pink", lwd = 2,
     main = "Capital Market Line", xlab = "Standard Deviation", ylab = "Expected Return")
points(portfolio_summary$sd, portfolio_summary$return, pch = 19, col = "purple")
text(portfolio_summary$sd, portfolio_summary$return + 0.001,
     labels = portfolio_summary$portfolio, pos = 3)
points(market_sd, market_return, col = "red", pch = 17)
legend("topleft", legend = c("CML", "Portfolios", "Market"), col = c("pink", "purple", "red"),
       lty = c(1, NA, NA), pch = c(NA, 19, 17))

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

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