σμ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.2·~55 minAvancé

Mouvement brownien géométrique

Position dans le parcoursModule 8 sur 11 · Sujet 2 sur 10
8.2 Mouvement brownien géométrique
—Score du lab— / 100XP total—Série—

Les prix varient en pourcentage

Le mouvement brownien arithmétique ajoute le même bruit en dollars qu'une action coûte 5 ou 500, et il peut passer sous zéro. Une action se comporte autrement : un mouvement de 1 % reste 1 % à n'importe quel prix. Le mouvement brownien géométrique place l'aléa dans la variation en pourcentage : dS = mu * S * dt + sigma * S * dW. C'est le modèle de Black–Scholes. mu est le rendement espéré annuel et sigma la volatilité, la grandeur cotée sur le marché.

import math

def gbm_step(s, mu, sigma, dt, z):
    return s * math.exp((mu - 0.5 * sigma ** 2) * dt + sigma * math.sqrt(dt) * z)

print(round(gbm_step(100.0, 0.08, 0.2, 1 / 252, 0.0), 4))     # a calm day: only the (mu - sigma^2/2) drift acts
print(round(gbm_step(100.0, 0.08, 0.2, 1 / 252, 1.0), 4))     # a one-sigma up day
print(round(gbm_step(100.0, 0.08, 0.2, 1 / 252, -50.0), 4))   # an absurd fall: still positive

La solution exacte et le moins un demi sigma carré

La résolution de l'équation donne S(t) = S(0) * exp((mu - sigma**2 / 2) * t + sigma * W(t)). Pourquoi ce curieux - sigma**2 / 2 ? Parce que l'exponentielle est convexe : d'après l'inégalité de Jensen, le bruit augmente la moyenne de exp(bruit), et soustraire sigma**2 / 2 l'annule exactement, de sorte que le prix *espéré* croît au taux mu. Les *trajectoires* typiques croissent plus lentement, à mu - sigma**2 / 2, d'où une médiane inférieure à la moyenne. Le sujet suivant, le lemme d'Itô, établit ce terme.

import numpy as np

def gbm_path(s0, mu, sigma, dt, z):
    log_moves = (mu - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z
    return s0 * np.exp(np.concatenate([[0.0], np.cumsum(log_moves)]))

rng = np.random.default_rng(1)
z = rng.standard_normal(252)
path = gbm_path(100.0, 0.08, 0.2, 1 / 252, z)
print(path[:4].round(3), round(float(path[-1]), 3))

Beaucoup de trajectoires : la moyenne et la médiane

Simulez des milliers de trajectoires d'un an et regardez où elles finissent. Le prix final *moyen* est proche de S0 * exp(mu * T), la formule du prix espéré. La *médiane* est plus basse, S0 * exp((mu - sigma**2/2) * T), et avec une forte volatilité l'écart est grand : la plupart des trajectoires finissent sous la moyenne, tirée vers le haut par quelques-unes spectaculaires. Cette asymétrie à droite, une loi log-normale, est la forme des prix d'actions sur longs horizons.

import numpy as np

rng = np.random.default_rng(5)
s0, mu, sigma, T, steps, paths = 100.0, 0.08, 0.4, 1.0, 252, 20000
dt = T / steps
z = rng.standard_normal((paths, steps))
final = s0 * np.exp(np.sum((mu - 0.5 * sigma ** 2) * dt + sigma * np.sqrt(dt) * z, axis=1))

print(round(float(final.mean()), 1), round(s0 * np.exp(mu * T), 1))                      # close to each other: the expected price
print(round(float(np.median(final)), 1), round(s0 * np.exp((mu - 0.5 * sigma ** 2) * T), 1))   # the median is lower
print(round(float(np.mean(final < s0)), 3))                                              # the share of paths that lose money

Estimer mu et sigma

À partir de prix échantillonnés tous les dt ans, les rendements logarithmiques r = diff(log(prices)) sont des lois normales indépendantes de moyenne (mu - sigma**2/2) * dt et d'écart-type sigma * sqrt(dt). Donc sigma = std(r) / sqrt(dt) et mu = mean(r) / dt + sigma**2 / 2. L'estimation de la volatilité est bonne avec quelques centaines de points ; la dérive est notoirement bruitée, car son erreur standard ne diminue qu'avec le *temps* total, pas avec le nombre d'observations.

import numpy as np

def estimate_gbm(prices, dt):
    r = np.diff(np.log(prices))
    sigma = r.std(ddof=1) / np.sqrt(dt)
    return r.mean() / dt + 0.5 * sigma ** 2, sigma

rng = np.random.default_rng(8)
dt = 1 / 252
for years in (1, 10, 100):
    n = int(years * 252)
    log_moves = (0.10 - 0.5 * 0.25 ** 2) * dt + 0.25 * np.sqrt(dt) * rng.standard_normal(n)
    prices = 100 * np.exp(np.concatenate([[0.0], np.cumsum(log_moves)]))
    mu, sigma = estimate_gbm(prices, dt)
    print(years, "years:", round(float(mu), 3), round(float(sigma), 3))           # true mu 0.10 and sigma 0.25
Le MBG est un bon début, pas la véritéLes prix réels ont des queues épaisses, des sauts et une volatilité qui bouge (sujet GARCH), ce que le MBG ne contient pas. Son mérite est que tout y est explicite : les prix sont log-normaux, le prix espéré a une formule et les prix d'options en découlent. Les modèles qui corrigent ses défauts (sauts, volatilité stochastique) sont bâtis dessus.
  • MBG : S(t) = S(0) * exp((mu - sigma**2 / 2) * t + sigma * W(t)) ; les variations en pourcentage sont aléatoires et les prix restent positifs.
  • Le - sigma**2 / 2 annule la convexité de exp : le prix espéré est S0 * exp(mu * t), mais la trajectoire médiane croît à mu - sigma**2 / 2.
  • Estimez à partir des rendements log : sigma = std(r) / sqrt(dt), mu = mean(r) / dt + sigma**2 / 2 ; la volatilité est bien estimée, la dérive mal.

À suivre : le lemme d'Itô, la règle de dérivation composée qui explique d'où vient le - sigma**2 / 2.

Lab : ce qui est noté

  • Faire un pas exact avec gbm_step25 pts
  • Simuler une trajectoire de prix avec gbm_path25 pts
  • Moyenner tous les futurs avec gbm_expected25 pts
  • Relire les paramètres avec estimate_gbm25 pts
Explorateur · lab-103-1
▾ lab-103-1
…
bash · dans le navigateur
learner@quantcraft:~/lab-103-1$
Python pas encore chargéPythonLn 1, Col 1Spaces: 4UTF-8

Réinitialiser ce sujet ?

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