σμ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 8 · Modèles stochastiques et apprentissage automatique›Sujet 8.5·~1 hAvancé

Options à barrière

Position dans le parcoursModule 8 sur 11 · Sujet 5 sur 10
8.5 Options à barrière
—Score du lab— / 100XP total—Série—

Des options avec une trappe

Une option à barrière est un call ou un put avec un interrupteur. Une option désactivante (knock-out) cesse d'exister si le prix touche la barrière ; une option activante (knock-in) n'existe que s'il la touche. La barrière peut être au-dessus du départ (up) ou en dessous (down). Comme l'acheteur renonce à une partie du gain, ces options coûtent moins cher que les options simples, d'où leur popularité, notamment dans les produits structurés. Comme les options asiatiques, elles dépendent de toute la trajectoire, et l'outil naturel est la simulation.

import numpy as np

paths = np.array([[100.0, 105.0, 112.0, 108.0],      # stays below 115
                  [100.0,  98.0, 103.0, 110.0],      # stays below 115
                  [100.0, 120.0,  95.0,  90.0]])     # touches 115 on day 1, then falls

hit_up = paths.max(axis=1) >= 115.0                  # the maximum of each path against the barrier
print(hit_up)                                        # [False False  True]
print(paths.min(axis=1) <= 96.0)                     # a down barrier at 96: [False False  True]
# hit_barrier for both directions, with an error for anything else
import numpy as np

def hit_barrier(paths, barrier, direction):
    if direction == "up":
        return paths.max(axis=1) >= barrier
    if direction == "down":
        return paths.min(axis=1) <= barrier
    raise ValueError("direction must be 'up' or 'down'")

paths = np.array([[100.0, 110.0, 105.0], [100.0, 109.9, 105.0]])
print(hit_barrier(paths, 110.0, "up"))                  # [ True False]: touching the barrier exactly counts
try:
    hit_barrier(paths, 110.0, "sideways")
except ValueError as error:
    print("rejected:", error)

Désactivante, activante, et leur somme

Sur chaque trajectoire, calculez le gain du call simple max(S_T - K, 0) et l'indicateur de barrière. Le call désactivant garde le gain là où la barrière n'a *pas* été touchée ; le call activant le garde là où elle l'a été. Chaque trajectoire est dans l'un ou l'autre cas, donc *activant + désactivant = call simple*, trajectoire par trajectoire, une identité pratique : valorisez le plus simple et obtenez l'autre gratuitement, et servez-vous-en pour tester votre code. np.where(hit, a, b) choisit entre tableaux élément par élément.

import numpy as np

paths = np.array([[100.0, 105.0, 112.0, 108.0],
                  [100.0,  98.0, 103.0, 110.0],
                  [100.0, 120.0,  95.0, 130.0]])
strike, barrier = 100.0, 115.0

vanilla = np.maximum(paths[:, -1] - strike, 0.0)
hit = paths.max(axis=1) >= barrier
knock_out = np.where(hit, 0.0, vanilla)
knock_in = np.where(hit, vanilla, 0.0)

print(vanilla, knock_out, knock_in)                      # [ 8. 10. 30.] [ 8. 10.  0.] [ 0.  0. 30.]
print(np.allclose(knock_in + knock_out, vanilla))        # True

Valorisation, et biais de suivi discret

Le prix est le gain moyen actualisé, comme avant. Mais attention au test de barrière : une simulation ne regarde le prix qu'aux dates de sa grille, alors qu'une vraie barrière continue est aussi franchie *entre* elles. Avec peu de pas, les trajectoires simulées franchissent souvent la barrière sans qu'on le voie, si bien qu'un call désactivant paraît trop cher et un activant trop bon marché. Davantage de pas réduisent le biais, de même que des corrections qui décalent la barrière d'environ 0.5826 * sigma * sqrt(dt).

import numpy as np

def simulate_paths(s0, rate, sigma, t, steps, n_paths, rng):
    dt = t / steps
    z = rng.standard_normal((n_paths, steps))
    moves = (rate - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z
    return s0 * np.exp(np.hstack([np.zeros((n_paths, 1)), np.cumsum(moves, axis=1)]))

def knock_out_price(paths, strike, barrier, rate, t):
    vanilla = np.maximum(paths[:, -1] - strike, 0.0)
    payoff = np.where(paths.max(axis=1) >= barrier, 0.0, vanilla)
    return float(np.exp(-rate * t) * payoff.mean())

rng = np.random.default_rng(1)
for steps in (12, 52, 252):
    paths = simulate_paths(100.0, 0.05, 0.25, 1.0, steps, 40000, rng)
    print(steps, round(knock_out_price(paths, 100.0, 130.0, 0.05, 1.0), 3))        # the price falls as monitoring gets finer
Le risque de barrière est un risque de sautPrès de la barrière, le delta et le gamma d'une option à barrière sautent, et un infime mouvement de prix peut activer ou désactiver tout le gain. La couverture d'une option désactivante près de sa barrière est donc difficile, et les desks la facturent. Méfiez-vous aussi de celui qui contrôle le prix près de la barrière : les contrats définissent précisément quels prix comptent (un cours de clôture, une impression de bourse), car la tentation de pousser le marché est réelle.
  • Une barrière est touchée si le maximum (up) ou le minimum (down) de la trajectoire l'atteint à une date quelconque ; la désactivante meurt au contact, l'activante naît au contact.
  • Par trajectoire, activante + désactivante = le gain du call simple ; np.where(hit, a, b) choisit ; les options à barrière sont moins chères que les simples.
  • Simuler sur une grille manque les franchissements entre les dates, donc les désactivantes paraissent trop chères : utilisez plus de pas ou décalez la barrière ; attention au risque de saut près de la barrière.

À suivre : les variables antithétiques, une façon d'obtenir la même précision Monte Carlo avec moins de simulations.

Lab : ce qui est noté

  • Détecter le contact avec hit_barrier25 pts
  • Tuer l'option avec knock_out_call_payoff25 pts
  • La faire naître avec knock_in_call_payoff25 pts
  • Transformer les gains en prix avec barrier_price25 pts
Explorateur · lab-106-1
▾ lab-106-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-106-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

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