Evaluation de contrat d'option¶
Nous allons illustrer l'évaluation des contrats d'option (call et put) par des méthodes de Monte-Carlo. Nous comparerons nos simulations numériques aux formules explicites obtenues pour les fonctions $c(t,x)$ et $p(t,x)$ (décrivant les prix du call et du put pour une valeur $x$ du sous-jacent au temps $t$) décrites dans le cours. Ensuite nous illustrerons une méthode de réduction de variance pour l'évaluation du prix des contrats.
# importation des librairies nécessaires
import numpy as np
import scipy.stats as st
import matplotlib.pyplot as plt
I) Méthode de Monte-Carlo¶
Commençons par décrire les paramètres du problème
t = 0 # temps auquel on évalue le prix du contrat
T = 0.5 # temps d'échéance des contrats
r = 0.04 # taux d'intérêt
sigma = 0.1 # volatilité
# K désignera le prix d'exercice de l'option
Définissons les fonctions associées aux formules explicites de $c$ et $p$.
# fonction d_+ et d_-, appellées ici d1 et d2
def d1(t, x, r, sigma, K):
return (np.log(x/K)+ (r+sigma**2/2)*t) / (sigma * np.sqrt(t))
def d2(t, x, r, sigma, K):
return d1(t, x, r, sigma, K) - sigma * np.sqrt(t)
# solutions des équations de Black-Scholes pour le call et le put
def c(t, x, T, r, sigma, K):
return x * st.norm(0,1).cdf(d1(T-t, x, r, sigma, K)) - np.exp(-r*(T-t)) * K * st.norm(0,1).cdf(d2(T-t, x, r, sigma, K))
def p(t, x, T, r, sigma, K):
return np.exp(-r*(T-t)) * K * st.norm(0,1).cdf(-d2(T-t, x, r, sigma, K)) -x * st.norm(0,1).cdf(-d1(T-t, x, r, sigma, K))
# grille des prix du sous-jacent pour illustrer c et p au temps t=0
x = np.linspace(60, 140)
Maintenant, introduisons les fonctions qui vont nous permettre d'évaluer les prix des contrats au temps $t=0$. On cherche à évaluer
$$ v(t=0, x) = \mathbb{E}^\ast[h( x e^{\sigma W^\ast_T +(r-\sigma^2/2)T} )] \simeq \frac{1}{N}\sum_{j=1}^N h( x e^{\sigma \sqrt{T} U_j +(r-\sigma^2/2)T} ), $$
pour $N\gg 1$ suffisamment grand grâce à la loi des grands nombres, et où $(U_j)_j$ est un échantillon de la loi $\mathcal{N}(0,1)$. Dans cette formule $v=c$ pour $h(y) = (y-K)^+$ (le call), et $v=p$ pour $h(y) = (K-y)^+$ (le put). On rappelle que $W^\ast$ est est mouvement Brownien sous $\mathbb{P}^\star$, et donc que $W^\ast_T \sim \mathcal{N}(0,T)$.
# Pour le call
def MC_call(t, x, T, r, sigma, K, N):
S = x * np.exp(sigma * np.sqrt(T-t) * np.random.randn(N) + (r-sigma**2/2) * (T-t) )
return np.mean( np.maximum(S-K, 0.) )
# Pour la variance du call
def MC_call_var(t, x, T, r, sigma, K, N):
S = x * np.exp(sigma * np.sqrt(T-t) * np.random.randn(N) + (r-sigma**2/2)*(T-t) )
M = np.mean( np.maximum(S-K, 0.) )
M2 = np.mean( np.maximum(S-K, 0.)**2 )
return M2 - M**2
# Pour le put
def MC_put(t, x, T, r, sigma, K, N):
S = x * np.exp(sigma * np.sqrt(T-t) * np.random.randn(N) + (r-sigma**2/2)*(T-t) )
return np.mean( np.maximum(K-S, 0.) )
# Pour la variance du put
def MC_put_var(t, x, T, r, sigma, K, N):
S = x * np.exp(sigma * np.sqrt(T-t) * np.random.randn(N) + (r-sigma**2/2)*(T-t) )
M = np.mean( np.maximum(K-S, 0.) )
M2 = np.mean( np.maximum(K-S, 0.)**2 )
return M2 - M**2
Dans la suite on utilisera $N=10000$ réalisations pour l'évaluation des espérances et des variances.
N = 10000
# illustration, pour plusieurs prix d'exercice, du prix du call c(0,x)
cmap = plt.get_cmap("tab10") # définie des couleurs
for j, K in enumerate(np.array([90, 100, 110])): # boucle sur les différents prix d'exercice
plt.plot(x, c(t, x, T, r, sigma, K), lw=3, color=cmap(j), label="K=%s" %K) # formule explicite
MC_c = np.array([MC_call(t, x, T, r, sigma, K, N) for x in x]) # évaluation par Monte-Carlo
plt.plot(x, MC_c, color=cmap(j), marker="o", label="Monte-Carlo")
plt.legend()
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Prix du contrat d'achat $c(0,x)$", fontsize=15)
plt.show()
# illustration, pour plusieurs prix d'exercice, du prix du put p(0,x)
cmap = plt.get_cmap("tab10") # définie des couleurs
for j, K in enumerate(np.array([90, 100, 110])): # boucle sur les différents prix d'exercice
plt.plot(x, p(t, x, T, r, sigma, K), lw=3, color=cmap(j), label="K=%s" %K) # formule explicite
MC_p = np.array([MC_put(t, x, T, r, sigma, K, N) for x in x]) # évaluation par Monte-Carlo
plt.plot(x, MC_p, color=cmap(j), marker="o", label="Monte-Carlo")
plt.legend()
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Prix du contrat de vente $p(0,x)$", fontsize=15)
plt.show()
Comme attendu, on a une bonne adéquation entre les formules explicites du prix des contrats et leurs simulations par une méthode de Monte-Carlo. Quand n'est il de la variance associée à ces estimations?
# illustration, pour plusieurs prix d'exercice, de la variance
# pour l'estimation du prix du call c(0,x)
for j, K in enumerate(np.array([90, 100, 110])):
MC_c = np.array([MC_call_var(t, x, T, r, sigma, K, N) for x in x])
plt.plot(x, MC_c, color=cmap(j), marker="o", label="K=%s" %K)
plt.legend()
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Variance du call", fontsize=15)
plt.show()
# illustration, pour plusieurs prix d'exercice, de la variance
# pour l'estimation du prix du put p(0,x)
for j, K in enumerate(np.array([90, 100, 110])):
MC_p = np.array([MC_put_var(t, x, T, r, sigma, K, N) for x in x])
plt.plot(x, MC_p, color=cmap(j), marker="o", label="K=%s" %K)
plt.legend()
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Variance du put", fontsize=15)
plt.show()
Sur ces graphiques, il apparait que la variance du put reste contenue, alors que pour le call a tendance à augmenter avec le prix du sous-jacent. Illustrons les évolutions de ces variances sur un même graphique.
# illustration des variances pour l'estimation des prix
# du call c(0,x) et du put p(0,x) pour plusieurs prix d'exercice
for j, K in enumerate(np.array([90, 100, 110])):
MC_c = np.array([MC_call_var(t, x, T, r, sigma, K, N) for x in x])
MC_p = np.array([MC_put_var(t, x, T, r, sigma, K, N) for x in x])
plt.plot(x, MC_c, color=cmap(j), marker="x", label="K=%s, call" %K)
plt.plot(x, MC_p, color=cmap(j), marker="o", label="put")
plt.legend()
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Variance du call", fontsize=15)
plt.show()
Il est intéressant de remarquer que suivant le prix du sous-jacent, il peut être intéressant d'évaluer le prix du put et d'en déduire celui du call grâce à la relation de parité entre les prix du put et du call (put-call parity), et inversement.
Put-Call parity¶
Illustrons cette relation en estimant le prix du call à partir de celui du put via la formule de parité.
cmap = plt.get_cmap("tab10") # définie des couleurs
for j, K in enumerate(np.array([90, 100, 110])):
plt.plot(x, c(t, x, T, r, sigma, K), lw=3, color=cmap(j), label="K=%s" %K)
MC_c = np.array([x - K*np.exp(-r*(T-t)) + MC_put(t, x, T, r, sigma, K, N) for x in x])
plt.plot(x, MC_c, color=cmap(j), marker="o", label="Monte-Carlo, put-call parity")
plt.legend()
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Prix du contrat d'achat $c(0,x)$", fontsize=15)
plt.show()
Comme attendu, on a une bonne adéquation entre la formule explicite du prix du call et la simulation par une méthode de Monte-Carlo à partir du prix du put via la formule de parité. Ainsi quand le prix du sous-jacent est suffisamment grand, on peut simuler numériquement le prix du call à partir du put avec une variance réduite.
II) Réduction de variance¶
Nous allons maintenant voir comment réduire la variance lors de l'estimation du put (et donc celle du call) de manière significative. On remarque que
$$ S_T = x e^{(r-\sigma^2/2)T} e^{\sigma W^\ast_T } = \lambda e^{\beta X} $$
avec
$$ \lambda = x e^{(r-\sigma^2/2)T}\qquad \beta = \sigma \sqrt{T}\qquad\text{et}\qquad X = W^\ast_T/\sqrt{T} \sim \mathcal{N}(0,1). $$
On pose aussi
$$ \phi(X) = (K-\lambda e^{\beta X})^+. $$
On cherche à évaluer $p(0,x) = \mathbb{E}[\phi(X)]$, qu'on peut réécrire à l'aide d'un changement de variable
\begin{align*} \mathbb{E}[\phi(X)] & = \int_{-\infty}^{+\infty} \phi(u)\frac{e^{-u^2/2}}{\sqrt{2\pi}}du \\ & = \int_{-\infty}^{+\infty} \phi(v+m)\frac{e^{-(v+m)^2/2}}{\sqrt{2\pi}}du \\ & = \int_{-\infty}^{+\infty} \phi(v+m)e^{-mv -m^2/2}\frac{e^{-v^2/2}}{\sqrt{2\pi}}du \\ & = \mathbb{E}[ \phi(X + m)e^{-mX - m^2/2} ] \\ & = \mathbb{E}[X_m], \end{align*}
en reprennant la notation de l'Exercice 5.3.2 (avec $d=1$). Au final, on peut écrire
\begin{align*} \mathbb{E}[\phi(X)] & = \mathbb{E}[X_m ] \\ & = \mathbb{E}[ \phi(X + m)e^{-m(X+m) + m^2/2} ] \\ & = \mathbb{E}[ \phi(Y)e^{-mY + m^2/2} ], \end{align*}
avec $Y\sim \mathcal{N}(m,1)$. Il s'agit donc d'une méthode d'échantillonnage préférentiel. Or l'Exercice 5.3.2 (pour $d=1$) nous invite à choisir
$$ m = \beta \lambda \frac{K-\lambda}{(\beta \lambda)^2} = \frac{K-\lambda}{ \beta \lambda}. $$
Attention, cette méthode a pour hypothèse $\lambda>K$! Mettons en oeuvre cette technique et illustrons la réduction de la variance.
# Pour le put, avec échantillonnage préférentiel
def MC_put_ep(t, x, T, r, sigma, K, N):
l = x * np.exp((r-sigma**2/2)*(T-t))
beta = sigma * np.sqrt(T-t)
m = (K-l)/(beta*l)
Y = st.norm(m,1).rvs(N)
S = l * np.exp(beta * Y)
return np.mean( np.maximum(K-S, 0.) * np.exp(-m*Y + m**2/2) )
# Estimation de la variance pour le put, avec échantillonnage préférentiel
def MC_put_ep_var(t, x, T, r, sigma, K, N):
l = x * np.exp((r-sigma**2/2)*(T-t))
beta = sigma * np.sqrt(T-t)
m = (K-l)/(beta*l)
Y = st.norm(m,1).rvs(N)
S = l * np.exp(beta * Y)
U = np.maximum(K-S, 0.) * np.exp(-m*Y + m**2/2)
M = np.mean(U)
M2 = np.mean(U**2)
return M2 - M**2
l = x * np.exp((r-sigma**2/2)*(T-t)) # évaluation de lambda pour les différents x.
# on remarquera que lambda est un mot clé pour python, il sert à definir des fonctions anonymes
# et ne peut pas être utilisé pour un nom de variable.
# illustration, pour plusieurs prix d'exercice, du prix du put p(0,x)
for j, K in enumerate(np.array([90, 100, 110])):
x_ep = x[l>K] # test de la validité de l'hypothèse
# on ne garde que les x qui la vérifie
plt.plot(x_ep, p(t, x_ep, T, r, sigma, K), lw=3, color=cmap(j), label="K=%s" %K)
MC_p = np.array([MC_put_ep(t, x, T, r, sigma, K, N) for x in x_ep])
plt.plot(x_ep, MC_p, color=cmap(j), marker="o", label="Monte-Carlo")
plt.legend()
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Prix du contrat de vente $p(0,x)$", fontsize=15)
plt.show()
Comme attendu, on a une bonne adéquation entre la formule explicite du prix du put et la simulation par une méthode de Monte-Carlo. Ce qui nous intéresse ici c'est la variance.
# illustration, de la variance pour l'estimation du prix du put p(0,x)
# pour plusieurs prix d'exercice,
for j, K in enumerate(np.array([90, 100, 110])):
x_ep = x[l>K]
MC_p = np.array([MC_put_var(t, x, T, r, sigma, K, N) for x in x_ep])
MC_p_ep = np.array([MC_put_ep_var(t, x, T, r, sigma, K, N) for x in x_ep])
plt.plot(x_ep, MC_p, color=cmap(j), marker="x", label="K=%s" %K)
plt.plot(x_ep, MC_p_ep, color=cmap(j), marker="o", label="échantillonnage préférentiel")
plt.legend()
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Variance du put", fontsize=15)
plt.show()
On voit déjà une bonne réduction de la variance pour le put. Quand n'est-il pour le call?
# illustration de la variance pour l'estimation du prix du call c(0,x) à partir du put
# pour plusieurs prix d'exercice,
for j, K in enumerate(np.array([90, 100, 110])):
x_ep = x[l>K]
MC_c = np.array([MC_call_var(t, x, T, r, sigma, K, N) for x in x_ep])
MC_p = np.array([MC_put_ep_var(t, x, T, r, sigma, K, N) for x in x_ep])
plt.plot(x_ep, MC_c, color=cmap(j), marker="x", label="K=%s" %K)
plt.plot(x_ep, MC_p, color=cmap(j), marker="o", label="put-call parity")
plt.legend(loc="upper left")
plt.xlabel("Prix de l'action $x$", fontsize=15)
plt.ylabel("Variance du call", fontsize=15)
plt.show()
On voit ici une très belle réduction de la variance provenant de deux facteurs. Tout d'abord l'évaluation du call à partir du put et de la formule de parité, mais aussi grâce à la méthode d'échantillonnage préférentiel.