σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyParcours
Ctrl K
AO
+1,2 k apprenants
ConnexionRejoindre ↗
Parcours
AO
+1,2 k apprenants
Connexion
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Parcours Python›Module 4 · Valorisation des produits de taux et d'options›Sujet 4.2·~45 minIntermédiaire

Prix des obligations

Position dans le parcoursModule 4 sur 11 · Sujet 2 sur 16
4.2 Prix des obligations
—Score du lab— / 100XP total—Série—

Une obligation est un flux de promesses

Une obligation est un prêt découpé en morceaux négociables. L'émetteur promet un coupon fixe à chaque période et la valeur nominale (pair) à l'échéance. Une obligation à 10 ans de valeur nominale 1000 et de coupon 5 % versé deux fois par an promet 25 tous les six mois et 1000 en plus au dernier paiement. Son prix est ce que valent aujourd'hui ces promesses : la valeur actuelle des flux, actualisés au rendement que le marché exige actuellement pour ce risque et cette échéance.

import numpy as np

def bond_cashflows(face, coupon_rate, years, freq):
    n = int(round(years * freq))                       # number of payments
    times = np.arange(1, n + 1) / freq                 # in years: 0.5, 1.0, 1.5, ...
    flows = np.full(n, face * coupon_rate / freq)      # a coupon each time
    flows[-1] += face                                  # and the face value with the last one
    return times, flows

times, flows = bond_cashflows(1000, 0.05, 2, 2)        # a 2-year bond, to keep the output short
print(times.tolist())    # [0.5, 1.0, 1.5, 2.0]
print(flows.tolist())    # [25.0, 25.0, 25.0, 1025.0]

Prix et rendement évoluent en sens inverse

Actualisez chaque flux à (1 + ytm / freq) ** (freq * time) et additionnez. Quand le rendement égale le taux du coupon, l'obligation vaut exactement sa valeur nominale (elle se négocie au pair). Si les rendements de marché dépassent le coupon, les nouvelles obligations paient davantage que celle-ci, son prix tombe donc sous le pair (une décote) ; si les rendements baissent, elle se négocie avec une prime. C'est le mécanisme du sujet précédent, avec de vraies dates de paiement. Le rendement courant, coupon annuel sur prix, est une mesure rapide mais incomplète car il ignore le gain ou la perte à l'échéance.

def bond_price(face, coupon_rate, ytm, years, freq=2):
    times, flows = bond_cashflows(face, coupon_rate, years, freq)
    return (flows / (1 + ytm / freq) ** (freq * times)).sum()

for ytm in (0.03, 0.05, 0.06, 0.08):
    print(ytm, round(bond_price(1000, 0.05, ytm, 10), 2))
# 0.03 1171.69   yield below the coupon: premium
# 0.05 1000.0    yield = coupon: par
# 0.06 925.61    yield above the coupon: discount
# 0.08 796.15

print(round(50 / bond_price(1000, 0.05, 0.06, 10), 5))    # 0.05402   current yield: 5.40%, not the 6% yield to maturity

Les obligations longues réagissent plus

La variation relative du prix pour un mouvement de rendement donné dépend de l'échéance. Les paiements lointains sont actualisés par une puissance élevée, ils sont donc très sensibles au taux. Une obligation à 1 an perd environ 1 % quand les rendements montent d'un point, une à 10 ans environ 7 %, une à 30 ans près de 14 %. C'est pourquoi les investisseurs obligataires parlent de risque de taux et pourquoi les sujets suivants construisent des mesures (rendement à l'échéance, duration) pour le quantifier.

for years in (1, 5, 10, 30):
    change = bond_price(1000, 0.05, 0.06, years) / 1000 - 1     # the coupon is 5%, the yield rises to 6%
    print(years, round(change, 4))
# 1 -0.0096
# 5 -0.0427
# 10 -0.0744
# 30 -0.1384
# The same 6% yield with annual, semiannual and monthly coupons: the quoting convention matters a little
for freq in (1, 2, 12):
    print(freq, round(bond_price(1000, 0.05, 0.06, 10, freq), 2))
# 1 926.4
# 2 925.61
# 12 924.94
Les rendements cotés suivent une conventionUn rendement de 6 % avec des coupons semestriels n'est pas un taux annuel effectif de 6 % : il capitalise deux fois par an, son taux effectif est donc de 6,09 %. Précisez toujours la capitalisation (freq) à côté d'un rendement. Les vraies obligations ont aussi un coupon couru entre les dates de coupon et des conventions de décompte des jours, que ce lab ignore en valorisant aux dates de coupon.
  • Prix = somme de flux / (1 + ytm / freq) ** (freq * temps) ; des coupons à chaque période, le nominal avec le dernier.
  • Rendement = taux du coupon : prix = pair ; rendement au-dessus du coupon : décote ; en dessous : prime. Prix et rendement évoluent en sens inverse.
  • Plus l'échéance est longue, plus le prix varie pour un même mouvement de rendement : ce risque de taux est ce que mesure la duration ensuite.

À suivre : le rendement à l'échéance, dans l'autre sens : du prix de marché au rendement.

Lab : ce qui est noté

  • Lister les paiements d'une obligation avec bond_cashflows25 pts
  • Actualiser les paiements en un prix avec bond_price25 pts
  • Comparer le coupon au prix avec current_yield25 pts
  • Voir l'effet d'un mouvement de taux avec price_change25 pts
Explorateur · lab-67-1
▾ lab-67-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-67-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-67-1 revient aux fichiers de départ et le terminal est vidé. Votre solution actuelle sera perdue : cette action est irréversible.

Sera réinitialisé
  • main.py → code de départ
  • Historique du terminal et sortie des tests
  • Modifications non soumises
Sera conservé
  • Meilleur score du lab
  • Votre XP et votre série
  • Progression du cours et notes

Connectez-vous pour exécuter votre code.

Tous les cours sont gratuits à lire. Créez un compte ou connectez-vous pour exécuter du code, lancer les tests et soumettre ce lab.

  • IDE dans le navigateur avec terminal bash — rien à installer
  • Correction instantanée, XP et votre place dans le parcours
Créer un compteSe connecter
OU
Continuer avec GitHub