Spreads souverains - spreads

Data - Investing

Niveau des spreads

1er juillet 2026-

Spreads quotidiens reconstitués à partir des rendements 10 ans de clôture (bonds.parquet) : rendement du pays moins rendement du Bund.

Code
sel <- c("France", "Italie", "Espagne", "Grèce", "Portugal", "Belgique")
sym <- c("France" = "France", "Italie" = "Italy", "Espagne" = "Spain",
         "Grèce" = "Greece", "Portugal" = "Portugal", "Belgique" = "Belgium")
start <- as.Date("2026-07-01")

bd <- read_parquet("bonds.parquet") |>
  filter(symbol %in% paste(c(unname(sym), "Germany"), "10Y"), Date >= start) |>
  mutate(pays = sub(" 10Y$", "", symbol)) |>
  select(pays, Date, Close) |>
  tidyr::pivot_wider(names_from = pays, values_from = Close)

d <- purrr::imap_dfr(sym, function(en, fr)
  tibble(country = fr, date = bd$Date,
         spread_bund = (bd[[en]] - bd$Germany) * 100)) |>
  filter(!is.na(spread_bund)) |>
  mutate(country_en = to_en(country), slug = to_slug(country_en),
         flag_img = ifelse(is.na(slug), NA_character_,
                           file.path("..", "..", "icon", "flag", "round",
                                     paste0(slug, ".png")))) |>
  add_flag_color("country_en")

span <- as.numeric(diff(range(d$date)))
gap  <- 0.085 * diff(range(d$spread_bund, na.rm = TRUE))

ends <- d |> group_by(country) |> slice_max(date, n = 1) |>
  ungroup() |> arrange(spread_bund)
y <- ends$spread_bund
for (i in seq_along(y)[-1]) if (y[i] - y[i - 1] < gap) y[i] <- y[i - 1] + gap
ends$y_lab <- y - mean(y - ends$spread_bund)

ggplot(d, aes(date, spread_bund)) +
  geom_line(aes(color = color, group = country), linewidth = 0.8) +
  geom_segment(data = ends, aes(x = date, xend = date + span * 0.025,
                                y = spread_bund, yend = y_lab, color = color),
               linewidth = 0.3) +
  ggimage::geom_image(
    data = ends, inherit.aes = FALSE, na.rm = TRUE,
    aes(x = date + span * 0.047, y = y_lab, image = flag_img),
    size = 0.03, asp = 1.8) +
  geom_text(
    data = ends,
    aes(x = date + span * 0.075, y = y_lab, label = country, color = color),
    hjust = 0, size = 3.3) +
  scale_color_identity() +
  scale_y_continuous(breaks = seq(0, 200, 10)) +
  scale_x_date(expand = expansion(mult = c(0.01, 0.17)),
               breaks = seq.Date(start, Sys.Date(), by = "month"),
               minor_breaks = seq.Date(start, Sys.Date(), by = "week"),
               labels = date_format("%b %Y")) +
  theme_minimal() + xlab("") + ylab("Spread vs Bund 10 ans (pb)")

1er septembre 2026-

Code
sel <- c("France", "Italie", "Espagne", "Grèce", "Portugal", "Belgique")
d <- spreads |> filter(country %in% sel)

if (n_distinct(d$scraped_date) < 2) {
  knitr::asis_output(paste0(
    "_Série trop courte (", n_distinct(d$scraped_date),
    " relevé) — le graphique se remplira au fil des instantanés quotidiens._"))
} else {
  d    <- add_flag_color(d, "country_en")
  span <- as.numeric(diff(range(d$scraped_date)))
  gap  <- 0.07 * diff(range(d$spread_bund, na.rm = TRUE))

  ends <- d |> group_by(country) |> slice_max(scraped_date, n = 1) |>
    ungroup() |> arrange(spread_bund)
  y <- ends$spread_bund
  for (i in seq_along(y)[-1]) if (y[i] - y[i - 1] < gap) y[i] <- y[i - 1] + gap
  ends$y_lab <- y - mean(y - ends$spread_bund)

  ggplot(d, aes(scraped_date, spread_bund)) +
    geom_line(aes(color = color, group = country), linewidth = 0.7) +
    ggimage::geom_image(
      data = ends, inherit.aes = FALSE, na.rm = TRUE,
      aes(x = scraped_date + span * 0.03 + 0.4, y = y_lab, image = flag_img),
      size = 0.05, asp = 1.5) +
    geom_text(
      data = ends,
      aes(x = scraped_date + span * 0.07 + 0.9, y = y_lab, label = country,
          color = color),
      hjust = 0, size = 3) +
    scale_color_identity() +
    scale_y_continuous(breaks = seq(0, 200, 10)) +
    scale_x_date(expand = expansion(mult = c(0.02, 0.24)),
                 breaks = seq.Date(as.Date("2026-09-01"), Sys.Date()),
                 labels = date_format("%d %b %Y")) +
    theme_minimal() + xlab("") + ylab("Spread vs Bund 10 ans (pb)") +
    theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1))
}

Principaux pays

Code
euro <- c("France", "Italie", "Espagne", "Pays-Bas", "Grèce")

spreads |>
  filter(scraped_date == last_day, country %in% euro) |>
  add_flag_color("country_en") |>
  bar_flags(flag_size = 0.07)

Tous les pays

Code
euro <- c("France", "Italie", "Espagne", "Portugal", "Belgique", "Pays-Bas",
          "Grèce", "Autriche", "Irlande", "Finlande", "Slovaquie", "Slovénie",
          "Lituanie", "Chypre", "Croatie", "Malte")

spreads |>
  filter(scraped_date == last_day, country %in% euro) |>
  add_flag_color("country_en") |>
  bar_flags()

Spread France-Allemagne, long terme

Spread OAT 10 ans - Bund 10 ans, quotidien (investing.com). Il est quasi nul avant 2007 ; il décolle avec la crise financière de 2008.

Sans bande

Code
bonds_fr_de <- read_parquet("bonds.parquet") |>
  filter(symbol %in% c("France 10Y", "Germany 10Y"), Date >= as.Date("2005-01-01")) |>
  select(symbol, Date, Close) |>
  tidyr::pivot_wider(names_from = symbol, values_from = Close) |>
  transmute(Date, spread = (`France 10Y` - `Germany 10Y`) * 100) |>
  filter(!is.na(spread))

events <- tibble::tribble(
  ~date,        ~y,  ~label,
  "2008-12-01",  72, "Crise financière de\n2007-2009",
  "2010-09-01", 100, "Crise de la Z€\n de 2011-12",
  "2012-09-01", 168, "Draghi :\n« Whatever\nit takes »",
  "2017-04-01",  98, "Élection 2017\n(menaces de Frexit)",
  "2020-03-15",  88, "Lagarde :\n« We are not\nhere to close\nspreads »",
  "2024-06-01", 108, "Dissolution\n2024") |>
  mutate(date = as.Date(date))

ggplot(bonds_fr_de, aes(Date, spread)) +
  geom_line(color = "black", linewidth = 0.5) +
  geom_text(data = events, aes(x = date, y = y, label = label),
            size = 3, lineheight = 0.9, color = "grey20") +
  scale_x_date(breaks = seq.Date(as.Date("2005-01-01"), Sys.Date(), by = "1 years"),
               labels = date_format("%Y")) +
  scale_y_continuous(breaks = seq(0, 200, 20)) +
  theme_minimal() + xlab("") +
  ylab("Spread OAT-Bund 10 ans (points de base)")

Avec Bande

Code
ggplot(bonds_fr_de, aes(Date, spread)) +
  annotate("rect", xmin = as.Date(-Inf), xmax = as.Date(Inf), ymin = 60, ymax = 100,
           fill = "grey90", alpha = 0.8) +
  geom_line(color = "#8B4A42", linewidth = 0.5) +
  geom_text(data = events, aes(x = date, y = y, label = label),
            size = 3, lineheight = 0.9, color = "grey20") +
  scale_x_date(breaks = seq.Date(as.Date("2005-01-01"), Sys.Date(), by = "2 years"),
               labels = date_format("%Y")) +
  scale_y_continuous(breaks = seq(0, 200, 20)) +
  theme_minimal() + xlab("") +
  ylab("Spread OAT-Bund 10 ans (points de base)")

Comparaison des épisodes : jours depuis le début de la hausse

Variation du spread OAT-Bund 10 ans par rapport au dernier jour avant la hausse (J0), en jours de bourse (J+1, J+2, …). Les chocs de 2017, de mars 2020 (déclaration de Lagarde le 12 mars, à J+11) et de 2024 sont rapides ; l’épisode actuel est beaucoup plus progressif.

Code
episodes <- tibble::tribble(
  ~episode,                                   ~j0,          ~color,
  "Crise des dettes souveraines (J0 = 3 juin 2011)", "2011-09-03", "#7A5195",
  "Élection 2017 (J0 = 24 janv. 2017)",       "2017-01-24", "#2E6DA4",
  "« Not here to close spreads » (J0 = 25 févr. 2020)", "2020-02-25", "#D98C1F",
  "Dissolution 2024 (J0 = 7 juin 2024)",      "2024-06-07", "#2F9E6B",
  "Épisode actuel (J0 = 1er juin 2026)",      "2026-06-01", "#8B2E2E") |>
  mutate(j0 = as.Date(j0))

ep <- purrr::pmap_dfr(episodes, function(episode, j0, color) {
  d <- bonds_fr_de |> filter(Date >= j0)
  tibble(episode = episode, color = color, Date = d$Date,
         jour = seq_len(nrow(d)) - 1L, delta = d$spread - d$spread[1])
}) |>
  filter(jour <= 90)

ends <- ep |> group_by(episode) |> slice_max(jour, n = 1) |> ungroup()

ggplot(ep, aes(jour, delta, color = color, group = episode)) +
  geom_hline(yintercept = 0, color = "grey60") +
  geom_line(linewidth = 0.9) +
  geom_point(data = ends, size = 2) +
  geom_text(data = ends, aes(label = paste0(sprintf("%+.0f", delta), " pb")),
            hjust = -0.2, size = 3.3, show.legend = FALSE) +
  scale_color_identity(guide = "legend", breaks = episodes$color,
                       labels = episodes$episode) +
  scale_x_continuous(breaks = seq(0, 90, 10), labels = function(x) paste0("J+", x),
                     expand = expansion(mult = c(0.01, 0.06))) +
  theme_minimal() + xlab("Jours de bourse depuis J0") +
  ylab("Variation du spread depuis J0 (pb)") +
  theme(legend.position = c(0.02, 0.98), legend.justification = c(0, 1),
        legend.title = element_blank())

Écart de taux par pays (monde)

Code
spreads |>
  filter(scraped_date == last_day, !is.na(spread_bund)) |>
  add_flag_color("country_en") |>
  bar_flags(flag_size = 0.055, asp = 0.30, txt_size = 2, label_size = 7)

Dernier instantané

Code
tbl <- spreads |>
  filter(scraped_date == last_day) |>
  mutate(Pays = ifelse(
    is.na(slug), country,
    sprintf('<span style="white-space:nowrap"><img src="../../icon/flag/round/%s.png" width="17" style="vertical-align:-3px;margin-right:6px">%s</span>',
            slug, country))) |>
  transmute(Pays,
            `Taux 10 ans` = yield,
            `Var.` = chg,
            `Vs. Bund` = spread_bund,
            `Vs. T-Note` = spread_tnote,
            Heure = quote_time) |>
  arrange(`Vs. Bund`)

if (knitr::is_html_output()) {
  DT::datatable(tbl, filter = "top", rownames = FALSE, escape = FALSE,
                options = list(columnDefs = list(list(width = "150px", targets = 0))))
} else {
  tbl |> mutate(Pays = gsub("<[^>]*>", "", Pays))
}

Source : investing.com, instantané du 14 octobre 2026.