Cours ajustés - France - adjusted_FRA

Data - Yahoo

Cours quotidiens Yahoo Finance de 480 sociétés cotées à Paris du catalogue investing.com (stocks_FRA_var), depuis 1989. Le ticker Yahoo (suffixe .PA) est retrouvé par ISIN (adjusted_map.parquet).

Close = cours de clôture corrigé des splits seulement ; Adjusted = cours corrigé des splits et des dividendes (dividendes réinvestis), donc Adjusted mesure le rendement total. Le rapport Adjusted / Close à une date donnée est le facteur cumulé des dividendes versés après cette date.

Couverture

Code
adj |> mutate(year = year(Date)) |> group_by(year) |>
  summarise(Titres = n_distinct(ticker), Lignes = n(), .groups = "drop") |>
  filter(year %% 5 == 0 | year == max(year)) |>
  print_table_conditional()
year Titres Lignes
1990 3 614
1995 21 5460
2000 254 58922
2005 307 76968
2010 369 93750
2015 434 108535
2020 478 122846
2025 478 121657
2026 480 93602
Code
adj |> mutate(year = year(Date)) |> group_by(year) |> summarise(n = n_distinct(ticker)) |>
  ggplot(aes(year, n)) + geom_line() + theme_minimal() + xlab("") + ylab("Nombre de titres")

Cours ajusté vs. non ajusté : exemples

Code
ex <- c("TTE.PA", "SAN.PA", "AC.PA", "ORA.PA", "BNP.PA", "MC.PA", "OR.PA", "AI.PA")
d <- adj |> filter(ticker %in% ex, Date >= as.Date("1995-01-01"), Adjusted > 0, Close > 0) |>
  group_by(ticker) |> arrange(Date) |>
  mutate(`Cours (hors dividendes)` = 100 * Close / first(Close),
         `Cours ajusté (dividendes réinvestis)` = 100 * Adjusted / first(Adjusted)) |> ungroup() |>
  pivot_longer(c(`Cours (hors dividendes)`, `Cours ajusté (dividendes réinvestis)`), names_to = "serie", values_to = "y")
ggplot(d, aes(Date, y, color = serie)) + geom_line(linewidth = 0.5) + facet_wrap(~ticker, scales = "free_y") +
  scale_y_log10() + theme_minimal() + xlab("") + ylab("Base 100 (échelle log)") +
  theme(legend.position = "bottom", legend.title = element_blank())

Code
adj |> filter(ticker %in% ex, Adjusted > 0, Close > 0) |> group_by(ticker) |> arrange(Date) |>
  summarise(Début = first(Date), `Années` = as.numeric(last(Date) - first(Date)) / 365.25,
            `Cours seul (%/an)` = 100 * ((last(Close) / first(Close))^(1 / `Années`) - 1),
            `Avec dividendes (%/an)` = 100 * ((last(Adjusted) / first(Adjusted))^(1 / `Années`) - 1),
            `Ecart = dividendes (pp/an)` = `Avec dividendes (%/an)` - `Cours seul (%/an)`, .groups = "drop") |>
  mutate(across(where(is.numeric), \(x) round(x, 2))) |> print_table_conditional()
ticker Début Années Cours seul (%/an) Avec dividendes (%/an) Ecart = dividendes (pp/an)
AC.PA 2000-01-03 26.76 1.04 8.93 7.89
AI.PA 2000-01-03 26.76 7.57 10.20 2.63
BNP.PA 1993-10-18 32.97 4.50 10.59 6.09
MC.PA 2000-01-03 26.76 5.67 7.65 1.98
OR.PA 2000-01-03 26.76 5.99 7.66 1.67
ORA.PA 2000-01-03 26.76 -7.96 -2.62 5.34
SAN.PA 2000-01-03 26.76 2.21 5.82 3.61
TTE.PA 2000-01-03 26.76 3.14 8.60 5.46

Tous les titres : ce que valent les dividendes

Pour chaque titre avec au moins 10 ans d’historique : écart entre le rendement annualisé avec et sans dividendes (proxy du rendement de dividende moyen réinvesti).

Code
gap <- adj |> inner_join(adj_map |> filter(TRUE) |> select(ticker), by = "ticker") |>
  filter(Adjusted > 0, Close > 0) |> group_by(ticker) |> arrange(Date) |>
  summarise(yrs = as.numeric(last(Date) - first(Date)) / 365.25,
            cagr_p = (last(Close) / first(Close))^(1 / yrs) - 1,
            cagr_a = (last(Adjusted) / first(Adjusted))^(1 / yrs) - 1, .groups = "drop") |>
  filter(yrs >= 10, is.finite(cagr_p), is.finite(cagr_a)) |>
  mutate(gap = 100 * (cagr_a - cagr_p))
gap |> summarise(Titres = n(), `Cours seul médian (%/an)` = 100 * median(cagr_p),
                 `Avec dividendes médian (%/an)` = 100 * median(cagr_a),
                 `Ecart médian (pp/an)` = median(gap), `Ecart moyen (pp/an)` = mean(gap),
                 `Part sans dividende (écart < 0,05)` = mean(gap < 0.05)) |>
  mutate(across(where(is.numeric), \(x) round(x, 2))) |> print_table_conditional()
Titres Cours seul médian (%/an) Avec dividendes médian (%/an) Ecart médian (pp/an) Ecart moyen (pp/an) Part sans dividende (écart < 0,05)
441 -0.66 2.24 1.67 2.95 0.26
Code
ggplot(gap |> filter(gap < 15), aes(gap)) + geom_histogram(bins = 60, fill = viridis::viridis(3)[1]) +
  theme_minimal() + xlab("Ecart de rendement annualisé, avec - sans dividendes (pp)") + ylab("Titres")

Indice équipondéré : cours vs. rendement total

Moyenne simple des rendements mensuels des titres disponibles (au moins 20 titres), avec et sans dividendes. Attention au biais de survie : seuls les titres encore cotés figurent dans la base.

Code
keep <- adj_map |> filter(TRUE) |> pull(ticker)
dt <- as.data.table(adj)[ticker %in% keep & Adjusted > 0 & Close > 0]
dt[, month := as.Date(format(Date, "%Y-%m-01"))]
mo <- dt[order(Date), .(Close = last(Close), Adjusted = last(Adjusted)), by = .(ticker, month)]
setorder(mo, ticker, month)
mo[, `:=`(rp = Close / shift(Close) - 1, ra = Adjusted / shift(Adjusted) - 1,
          gap = as.numeric(month - shift(month))), by = ticker]
ew <- mo[!is.na(rp) & gap <= 31 & abs(rp) < 1 & abs(ra) < 1,
         .(n = .N, Prix = mean(rp), `Rendement total` = mean(ra)), by = month][n >= 20][order(month)]
ew_l <- ew |> pivot_longer(c(Prix, `Rendement total`), names_to = "serie", values_to = "r") |>
  group_by(serie) |> arrange(month) |> mutate(y = 100 * cumprod(1 + r)) |> ungroup()
ggplot(ew_l, aes(month, y, color = serie)) + geom_line() + scale_y_log10() + theme_minimal() +
  xlab("") + ylab("Base 100 (échelle log)") + theme(legend.position = c(0.2, 0.85), legend.title = element_blank())

Code
ew_l |> group_by(serie) |> summarise(Début = min(month), `Rendement annualisé (%)` = round(100 * (prod(1 + r)^(12 / n()) - 1), 2),
                                    .groups = "drop") |> print_table_conditional()
serie Début Rendement annualisé (%)
Prix 1992-12-01 4.86
Rendement total 1992-12-01 9.73

Qualité

Code
tibble(`Lignes` = nrow(adj), `Titres` = n_distinct(adj$ticker),
       `Adjusted <= 0` = sum(adj$Adjusted <= 0, na.rm = TRUE),
       `Titres concernés` = n_distinct(adj$ticker[adj$Adjusted <= 0 & !is.na(adj$Adjusted)]),
       `Close <= 0` = sum(adj$Close <= 0, na.rm = TRUE)) |> print_table_conditional()
Lignes Titres Adjusted <= 0 Titres concernés Close <= 0
2729213 480 18391 8 0

Les Adjusted <= 0 viennent d’un défaut de Yahoo (dividende exceptionnel supérieur au cours) : à écarter avant de calculer des rendements.