σμ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.15·~55 minIntermédiaire

Arbres binomiaux

Position dans le parcoursModule 4 sur 11 · Sujet 15 sur 16
4.15 Arbres binomiaux
—Score du lab— / 100XP total—Série—

Une action qui ne peut que monter ou descendre

Black–Scholes est une formule ; un arbre binomial est une procédure, et elle peut valoriser ce que la formule ne peut pas. On découpe la durée jusqu'à l'échéance en petits pas. À chaque pas, l'action monte d'un facteur u ou baisse d'un facteur d, rien d'autre. Après n pas, il y a n + 1 prix possibles. Les gains sont connus à l'échéance, et chaque nœud antérieur vaut la moyenne actualisée des deux nœuds qu'il prolonge, ce qui permet de remplir l'arbre à rebours jusqu'au présent.

# One step: a stock at 100 goes to 110 or 90. What is a call with strike 100 worth?
import math

spot, strike, rate, dt = 100, 100, 0.05, 1.0
u, d = 1.10, 0.90
p = (math.exp(rate * dt) - d) / (u - d)             # the probability that makes the stock grow at the rate
up_value, down_value = max(spot * u - strike, 0), max(spot * d - strike, 0)
price = math.exp(-rate * dt) * (p * up_value + (1 - p) * down_value)
print(round(p, 4), round(up_value, 4), down_value)   # 0.7564 10.0 0
print(round(price, 4))                               # 7.1947

La probabilité n'est pas une prévision

Le nombre p est choisi pour que l'action croisse en moyenne au taux sans risque : p * u + (1 - p) * d = exp(rate * dt), d'où p = (exp(rate * dt) - d) / (u - d). C'est la probabilité risque-neutre : un artifice qui rend un portefeuille de réplication égal à son espérance actualisée, non une opinion sur l'évolution de l'action. Pour reproduire la volatilité observée, l'arbre de Cox–Ross–Rubinstein (CRR) prend u = exp(sigma * sqrt(dt)) et d = 1 / u ; une hausse suivie d'une baisse ramène au même prix et l'arbre se recombine : n pas ne donnent que n + 1 nœuds finaux.

# The CRR parameters and the prices at expiry of a 3-step tree
import math

def crr_parameters(rate, sigma, t, steps):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    return u, d, p

def terminal_prices(spot, u, d, steps):
    return [spot * u ** j * d ** (steps - j) for j in range(steps + 1)]

u, d, p = crr_parameters(0.05, 0.2, 1, 3)
print(round(u, 4), round(d, 4), round(p, 4))                     # 1.1224 0.8909 0.5438
print([round(price, 2) for price in terminal_prices(100, u, d, 3)])

Remonter à rebours

À l'échéance, chaque nœud n'est que le gain, max(S - K, 0) pour un call. Une colonne plus tôt, chaque valeur est exp(-rate * dt) * (p * value_up + (1 - p) * value_down), et la colonne perd un nœud. Après n balayages, il reste une seule valeur, le prix aujourd'hui. En Python une colonne est une liste, et un balayage est une compréhension de liste sur les paires voisines. Quand le nombre de pas augmente, le prix converge vers celui de Black–Scholes : l'arbre retrouve ce que dit la formule.

# The tree price of a European call approaches the Black-Scholes price as steps grow
import math

def norm_cdf(x):
    return 0.5 * (1 + math.erf(x / math.sqrt(2)))

def bs_call(spot, strike, rate, sigma, t):
    d1 = (math.log(spot / strike) + (rate + 0.5 * sigma ** 2) * t) / (sigma * math.sqrt(t))
    return spot * norm_cdf(d1) - strike * math.exp(-rate * t) * norm_cdf(d1 - sigma * math.sqrt(t))

def european_call_tree(spot, strike, rate, sigma, t, steps):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    disc = math.exp(-rate * dt)
    values = [max(spot * u ** j * d ** (steps - j) - strike, 0) for j in range(steps + 1)]
    for _ in range(steps):
        values = [disc * (p * values[j + 1] + (1 - p) * values[j]) for j in range(len(values) - 1)]
    return values[0]

print("Black-Scholes", round(bs_call(100, 100, 0.05, 0.2, 1), 4))
for steps in (1, 5, 25, 100, 400):
    print(steps, round(european_call_tree(100, 100, 0.05, 0.2, 1, steps), 4))

L'exercice anticipé

Le vrai atout de l'arbre est l'option que la formule ne sait pas valoriser : une option américaine peut être exercée à tout moment. À chaque nœud, le détenteur compare deux valeurs, l'espérance actualisée de la détention et le gain de l'exercice immédiat, et prend la plus grande. Ce seul max supplémentaire à chaque nœud est tout le changement de l'algorithme. Pour un call sur une action sans dividende, l'exercice anticipé ne rapporte jamais, mais pour un put il le peut : un put très dans la monnaie vaut plus en espèces aujourd'hui, donc le put américain coûte plus que l'européen.

# An American put against a European put: the early-exercise premium
import math

def tree(spot, strike, rate, sigma, t, steps, american):
    dt = t / steps
    u = math.exp(sigma * math.sqrt(dt))
    d = 1 / u
    p = (math.exp(rate * dt) - d) / (u - d)
    disc = math.exp(-rate * dt)
    values = [max(strike - spot * u ** j * d ** (steps - j), 0) for j in range(steps + 1)]
    for i in range(steps - 1, -1, -1):
        values = [disc * (p * values[j + 1] + (1 - p) * values[j]) for j in range(i + 1)]
        if american:
            values = [max(values[j], strike - spot * u ** j * d ** (i - j)) for j in range(i + 1)]
    return values[0]

for spot in (80, 100, 120):
    european = tree(spot, 100, 0.05, 0.2, 1, 200, False)
    american = tree(spot, 100, 0.05, 0.2, 1, 200, True)
    print(spot, round(european, 4), round(american, 4), "premium", round(american - european, 4))
Lent et général vaut mieux que rapide et spécialPour les options européennes simples, la formule fermée est instantanée et exacte : on n'utiliserait jamais un arbre. L'arbre trouve sa place quand le gain contient une décision (exercice anticipé), une barrière, un dividende discret : on ajoute la règle à chaque nœud. Le prix à payer est le temps : n pas coûtent environ n**2 / 2 mises à jour de nœuds, et la convergence n'est pas régulière (l'erreur oscille selon la position du prix d'exercice entre les nœuds) ; on vérifie donc un arbre en comparant plusieurs nombres de pas.
  • À chaque pas l'action monte de u ou baisse de d ; le choix CRR u = exp(sigma * sqrt(dt)), d = 1 / u recombine l'arbre, donc n pas ont n + 1 nœuds finaux.
  • Valoriser à rebours : gains à l'échéance, puis exp(-rate * dt) * (p * up + (1 - p) * down) par nœud ; p est la probabilité risque-neutre, pas une prévision.
  • Beaucoup de pas convergent vers Black–Scholes ; une option américaine ajoute un max(continuation, exercice) par nœud, et c'est la raison d'être des arbres.

À suivre : les méthodes de différences finies, valoriser en résolvant sur une grille l'équation aux dérivées partielles de l'option.

Lab : ce qui est noté

  • Fixer les mouvements haussier et baissier avec crr_parameters25 pts
  • Lister les prix à l'échéance avec terminal_prices25 pts
  • Remonter l'arbre à rebours avec european_call_tree25 pts
  • Autoriser l'exercice anticipé avec american_put_tree25 pts
Explorateur · lab-80-1
▾ lab-80-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-80-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

Tout le contenu de lab-80-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