We’re told that risk and return are positively related: the higher the risk, the higher the return. We assume it’s true ex-ante (security market line). But is it true ex-post?

library(tidyverse)
library(lubridate)
library(scales)
library(ggrepel)
library(tidyquant)
library(jsonlite)
theme_set(theme_minimal())
invisible(Sys.setlocale("LC_TIME", "en_US.UTF-8"))
# Load data
library(frenchdata)

industrydata <- frenchdata::download_french_data("49 Industry Portfolios")

industryrets <- industrydata$subsets |> 
  filter(name == "Average Value Weighted Returns -- Monthly") |> 
  unnest(data) |> 
  mutate(date = lubridate::ymd(paste0(date, "01"))) |> 
  mutate_if(is.numeric, function(x) ifelse(x == -99.99, NA_real_, x / 100)) |> 
  select(-name) |> 
  pivot_longer(-date) |> 
  arrange(name, date) |> 
  drop_na()

industryrets |> saveRDS("industryrets.RDS")
industryrets <- readRDS("industryrets.RDS") |> 
  rename(r = value)
industryrets |> 
  group_by(name, decade = paste0(floor(year(date) / 10) * 10, "s")) |> 
  summarise(n = n(),
            mu = prod(1 + r)^(12/n()) - 1,
            sd = sd(r, na.rm = TRUE)*sqrt(12)) |> 
  group_by(decade) |> 
  filter(n == max(n)) |> 
  ggplot(aes(x = sd, y = mu, label = name)) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 0.3, fullrange = T) +
  geom_point(size = 0.6, alpha = 0.6) +
  # red shaddow for y below 0
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 0, fill = "red", alpha = 0.1) +
  # geom_smooth(method = "lm", se = FALSE) +
  scale_x_continuous(labels = scales::percent) +
  scale_y_continuous(labels = scales::percent) +
  coord_cartesian(xlim = c(0, 0.8)) +
  labs(x = "Standard deviation", y = "Mean return", title = "Risk and return by industry") +
  facet_wrap(~decade, scales = "fixed")

Risk-return is clearly upward sloping in the 1940s and 2020s. It’s somewhat upward sloping in the 1950s and 1960s. But in 6 out of 11 decades, it’s flat or downward sloping.

At least on indsutry level, risk and return are not positively related ex-post.