1 Contexte

Quand un sinistre survient, il n’est pas toujours réglé immédiatement. Entre la date de survenance, la déclaration et le paiement final, il peut s’écouler plusieurs années — surtout en responsabilité civile corporelle. L’assureur doit donc provisionner : mettre de côté aujourd’hui une somme suffisante pour payer demain.

C’est une obligation réglementaire. En Tunisie, le Comité Général des Assurances (CGA) impose aux compagnies de justifier leurs provisions techniques à chaque clôture annuelle. Une provision sous-estimée fragilise la solvabilité de la compagnie. Une provision sur-estimée immobilise inutilement du capital.

L’objectif de ce projet est d’estimer ces provisions à partir d’un triangle de développement, en appliquant deux méthodes : Chain Ladder et Bornhuetter-Ferguson, puis de comparer leurs résultats.

2 Construction du triangle de développement

2.1 Les données

On construit un triangle de charges cumulées sur 7 années de survenance et 7 années de développement. Ce type de données est la matière première du provisionnement — chaque ligne est une année où des sinistres sont survenus, chaque colonne est le délai après lequel on observe les paiements cumulés.

# Triangle de charges cumulées (en milliers d'euros)
# Lignes = années de survenance (2017 à 2023)
# Colonnes = années de développement (1 à 7)

triangle_brut <- matrix(
  c(2341, 3116, 3456, 3607, 3660, 3681, 3689,
    2554, 3376, 3757, 3923, 3985, 4008,   NA,
    2724, 3610, 4013, 4190, 4264,   NA,   NA,
    2901, 3843, 4274, 4469,   NA,   NA,   NA,
    3092, 4099, 4559,   NA,   NA,   NA,   NA,
    3294, 4368,   NA,   NA,   NA,   NA,   NA,
    3512,   NA,   NA,   NA,   NA,   NA,   NA),
  nrow = 7, byrow = TRUE,
  dimnames = list(
    Survenance  = 2017:2023,
    Developpement = 1:7
  )
)

print(triangle_brut)
##           Developpement
## Survenance    1    2    3    4    5    6    7
##       2017 2341 3116 3456 3607 3660 3681 3689
##       2018 2554 3376 3757 3923 3985 4008   NA
##       2019 2724 3610 4013 4190 4264   NA   NA
##       2020 2901 3843 4274 4469   NA   NA   NA
##       2021 3092 4099 4559   NA   NA   NA   NA
##       2022 3294 4368   NA   NA   NA   NA   NA
##       2023 3512   NA   NA   NA   NA   NA   NA

La partie inférieure droite du triangle est vide — ce sont les paiements futurs qu’on cherche à estimer. L’idée centrale de Chain Ladder est que le rythme de développement passé se répète : si historiquement les sinistres de l’année 1 représentent 63% du total final, on peut extrapoler les années récentes à partir de ce ratio.

2.2 Visualisation du triangle

triangle_df <- as.data.frame(triangle_brut) %>%
  mutate(Survenance = 2017:2023) %>%
  pivot_longer(-Survenance, names_to = "Dev", values_to = "Charge") %>%
  mutate(Dev = as.integer(gsub("D", "", Dev)),
         Connu = !is.na(Charge))

ggplot(triangle_df, aes(x = Dev, y = factor(Survenance), fill = Charge)) +
  geom_tile(color = "white", linewidth = 0.5) +
  geom_text(aes(label = ifelse(!is.na(Charge), format(round(Charge), big.mark = " "), "?")),
            size = 3, color = "white", fontface = "bold") +
  scale_fill_gradient(low = "#2E86AB", high = "#1B4F72", na.value = "#BDC3C7",
                      name = "Charge (k€)") +
  labs(title = "Triangle de développement des charges cumulées",
       subtitle = "Les cases grises représentent les montants à estimer",
       x = "Année de développement", y = "Année de survenance") +
  theme_minimal() +
  theme(legend.position = "right")

3 Méthode Chain Ladder

3.1 Calcul des facteurs de développement

n <- nrow(triangle_brut)
triangle_cl <- triangle_brut

# Calcul des facteurs de développement colonne par colonne
facteurs <- numeric(n - 1)
for (j in 1:(n - 1)) {
  lignes_connues <- !is.na(triangle_cl[, j]) & !is.na(triangle_cl[, j + 1])
  facteurs[j] <- sum(triangle_cl[lignes_connues, j + 1]) /
                 sum(triangle_cl[lignes_connues, j])
}

cat("Facteurs de développement Chain Ladder :\n")
## Facteurs de développement Chain Ladder :
for (j in 1:(n-1)) {
  cat(sprintf("  f%d→%d : %.4f  (+%.1f%%)\n",
              j, j+1, facteurs[j], (facteurs[j]-1)*100))
}
##   f1→2 : 1.3257  (+32.6%)
##   f2→3 : 1.1117  (+11.2%)
##   f3→4 : 1.0445  (+4.4%)
##   f4→5 : 1.0161  (+1.6%)
##   f5→6 : 1.0058  (+0.6%)
##   f6→7 : 1.0022  (+0.2%)

Chaque facteur représente la croissance moyenne des charges d’une année de développement à la suivante. Un facteur de 1.32 entre la colonne 1 et la colonne 2 signifie que les charges augmentent en moyenne de 32% entre la première et la deuxième année — les dossiers se complètent, les expertises arrivent, les règlements partiels s’accumulent.

On remarque que les facteurs se rapprochent de 1 au fil du développement : les charges se stabilisent, les dossiers se ferment. C’est le comportement attendu d’un portefeuille qui arrive à maturité.

3.2 Complétion du triangle

# Complétion du triangle par projection
for (i in 2:n) {
  for (j in (n - i + 2):n) {
    if (is.na(triangle_cl[i, j])) {
      triangle_cl[i, j] <- triangle_cl[i, j - 1] * facteurs[j - 1]
    }
  }
}

cat("\nTriangle complété (Chain Ladder) :\n")
## 
## Triangle complété (Chain Ladder) :
print(round(triangle_cl))
##           Developpement
## Survenance    1    2    3    4    5    6    7
##       2017 2341 3116 3456 3607 3660 3681 3689
##       2018 2554 3376 3757 3923 3985 4008 4017
##       2019 2724 3610 4013 4190 4264 4289 4298
##       2020 2901 3843 4274 4469 4541 4567 4577
##       2021 3092 4099 4559 4762 4838 4866 4877
##       2022 3294 4368 4856 5072 5153 5183 5194
##       2023 3512 4656 5176 5406 5493 5525 5537
# Provision = ultime estimé - dernier connu
derniers_connus_cl <- sapply(1:n, function(i) {
  vals <- triangle_brut[i, ]
  vals[max(which(!is.na(vals)))]
})

ultimes_cl   <- triangle_cl[, n]
provisions_cl <- ultimes_cl - derniers_connus_cl

cat("\n── Provisions Chain Ladder par année de survenance (k€) ──\n")
## 
## ── Provisions Chain Ladder par année de survenance (k€) ──
for (i in 1:n) {
  cat(sprintf("  %d : %.0f k€\n", 2016 + i, provisions_cl[i]))
}
##   2017 : 0 k€
##   2018 : 9 k€
##   2019 : 34 k€
##   2020 : 108 k€
##   2021 : 318 k€
##   2022 : 826 k€
##   2023 : 2025 k€
cat(sprintf("\n  TOTAL : %.0f k€\n", sum(provisions_cl)))
## 
##   TOTAL : 3319 k€

4 Méthode Bornhuetter-Ferguson

4.1 Principe

Bornhuetter-Ferguson (BF) est une méthode hybride : elle combine l’expérience observée dans le triangle avec une estimation a priori du niveau de sinistralité. C’est particulièrement utile pour les années de survenance récentes, où peu de développement a été observé et où Chain Ladder peut produire des estimations instables.

La provision BF est calculée ainsi :

\[\text{Provision BF} = \text{Part non développée} \times \text{Sinistres ultimes a priori}\]

La part non développée est 1 - 1/facteur_cumulé, c’est-à-dire la fraction du développement qui reste encore à venir.

# Facteurs cumulés (de chaque colonne jusqu'à l'ultime)
facteurs_cum <- cumprod(rev(facteurs))
facteurs_cum <- rev(facteurs_cum)
facteurs_cum <- c(facteurs_cum, 1)

cat("Facteurs cumulés jusqu'à l'ultime :\n")
## Facteurs cumulés jusqu'à l'ultime :
for (j in 1:n) {
  cat(sprintf("  Colonne %d : %.4f  (part développée : %.1f%%)\n",
              j, facteurs_cum[j], 100/facteurs_cum[j]))
}
##   Colonne 1 : 1.5765  (part développée : 63.4%)
##   Colonne 2 : 1.1892  (part développée : 84.1%)
##   Colonne 3 : 1.0697  (part développée : 93.5%)
##   Colonne 4 : 1.0242  (part développée : 97.6%)
##   Colonne 5 : 1.0079  (part développée : 99.2%)
##   Colonne 6 : 1.0022  (part développée : 99.8%)
##   Colonne 7 : 1.0000  (part développée : 100.0%)
# Parts non développées
parts_non_dev <- 1 - 1 / facteurs_cum

# Primes acquises a priori (hypothèse : ratio sinistres/primes de 72%)
primes_a_priori <- c(5100, 5450, 5800, 6150, 6500, 6900, 7350)
ratio_sp        <- 0.72
ultimes_a_priori <- primes_a_priori * ratio_sp

# Dernier connu par année
col_derniere <- apply(triangle_brut, 1, function(x) max(which(!is.na(x))))

derniers_connus_bf <- sapply(1:n, function(i) {
  triangle_brut[i, col_derniere[i]]
})

# Provision BF
provisions_bf <- parts_non_dev[col_derniere] * ultimes_a_priori

cat("\n── Provisions Bornhuetter-Ferguson par année de survenance (k€) ──\n")
## 
## ── Provisions Bornhuetter-Ferguson par année de survenance (k€) ──
for (i in 1:n) {
  cat(sprintf("  %d : %.0f k€\n", 2016 + i, provisions_bf[i]))
}
##   2017 : 0 k€
##   2018 : 9 k€
##   2019 : 33 k€
##   2020 : 105 k€
##   2021 : 305 k€
##   2022 : 790 k€
##   2023 : 1935 k€
cat(sprintf("\n  TOTAL : %.0f k€\n", sum(provisions_bf)))
## 
##   TOTAL : 3177 k€

5 Comparaison des deux méthodes

resultats <- data.frame(
  Annee       = 2017:2023,
  Chain_Ladder = round(provisions_cl),
  BF           = round(provisions_bf),
  Ecart        = round(provisions_bf - provisions_cl)
)

cat("── Comparaison des provisions (k€) ──\n")
## ── Comparaison des provisions (k€) ──
print(resultats)
##      Annee Chain_Ladder   BF Ecart
## 2017  2017            0    0     0
## 2018  2018            9    9     0
## 2019  2019           34   33    -1
## 2020  2020          108  105    -4
## 2021  2021          318  305   -13
## 2022  2022          826  790   -36
## 2023  2023         2025 1935   -89
cat(sprintf("\nTotal Chain Ladder : %.0f k€\n", sum(provisions_cl)))
## 
## Total Chain Ladder : 3319 k€
cat(sprintf("Total BF           : %.0f k€\n", sum(provisions_bf)))
## Total BF           : 3177 k€
cat(sprintf("Écart              : %.0f k€ (%.1f%%)\n",
            sum(provisions_bf) - sum(provisions_cl),
            100 * (sum(provisions_bf) - sum(provisions_cl)) / sum(provisions_cl)))
## Écart              : -143 k€ (-4.3%)
resultats_long <- resultats %>%
  pivot_longer(c(Chain_Ladder, BF),
               names_to  = "Methode",
               values_to = "Provision")

ggplot(resultats_long, aes(x = factor(Annee), y = Provision, fill = Methode)) +
  geom_col(position = "dodge", width = 0.6) +
  scale_fill_manual(values = c("Chain_Ladder" = "#2E86AB", "BF" = "#E84855"),
                    labels  = c("Chain Ladder", "Bornhuetter-Ferguson")) +
  labs(title    = "Provisions estimées par méthode et par année de survenance",
       subtitle = "Les années récentes montrent les plus grandes divergences entre les deux méthodes",
       x = "Année de survenance", y = "Provision (k€)", fill = "Méthode") +
  theme_minimal()

5.1 Lecture des résultats

Les deux méthodes donnent des résultats proches sur les années anciennes ( 2018 ,2019), où le triangle est presque entièrement développé et où Chain Ladder dispose de suffisamment d’observations pour être fiable.

Les écarts se creusent sur les années récentes (2022, 2023), ce qui est logique : pour 2023, on ne dispose que d’une seule diagonale d’observation. Chain Ladder extrapole alors à partir d’un seul point, ce qui le rend sensible aux fluctuations aléatoires. Bornhuetter-Ferguson, en ancrant son estimation sur une sinistralité a priori raisonnée, produit une provision plus stable — parfois plus prudente, parfois plus optimiste selon la qualité de l’a priori choisi.

6 Conclusion

Ce projet illustre deux piliers du provisionnement actuariel en assurance non-vie :

  • Chain Ladder est simple, transparent et entièrement piloté par les données historiques. Sa faiblesse est sa sensibilité aux anomalies sur les années récentes.
  • Bornhuetter-Ferguson introduit un a priori externe qui stabilise les estimations quand l’expérience observée est encore limitée. Sa qualité dépend de la pertinence de cet a priori.

Dans un contexte réel, ces méthodes seraient complétées par des tests de sensibilité, une analyse de l’évolution des facteurs dans le temps, et une comparaison avec les provisions de l’exercice précédent. La provision finale retenue relève d’un jugement actuariel — les modèles éclairent, mais ne décident pas.

mini-projet réalisé par Eya Douiri Future actuaire et data scientist ❤️ contact :