3. Méthode de Monte Carlo#

Tout d’abord, on illustre numériquement les deux résultats probabilistes sur lesquels reposent la méthode dite de Monte Carlo:

  • la loi forte de grands nombres,

  • le théorème central limit (TCL).

On considère ensuite un premier exemple d’estimateur de Monte Carlo et l’importance de l’intervalle de confiance (IC) dans lequel se trouve la valeur recherchée avec probabilité grande (0.95).

Enfin on applique la méthode de Monte Carlo à un exemple multidimensionnel où on illustre l’efficacité de 2 méthodes de réduction de variance:

  • variables antithétiques,

  • variable de contrôle.

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme() 
from numpy.random import default_rng
rng = default_rng()

3.1. Illustration de la loi des grands nombres#

Soit \((X_n)_{n \ge 1}\) une suite de variables aléatoires i.i.d. de carré intégrable. On définit les suites \((m_n)_{n \ge 1}\) et \((\sigma_n^2)_{n \ge 2}\) (non définie pour \(n = 1\)) de la façon suivante

\[ m_n = \frac{1}{n} \sum_{k=1}^n X_k \qquad \text{et} \qquad \sigma_n^2 = \frac{1}{n-1} \sum_{k=1}^n (X_k - m_n)^2 \quad \text{pour} \; n \ge 2 \]

et on veut illustrer la Loi Forte des Grands Nombres et le Théorème Central Limite (étendu en utilisant le lemme de Slutsky pour remplacer \(\sigma^2 = \mathrm{var}(X_1)\) par l’estimateur \(\sigma_n^2\)) c’est à dire les convergences

\[ m_n \xrightarrow{p.s.} m \qquad \text{et} \qquad \sqrt{n} \Bigl(\frac{m_n - m}{\sigma_n}\Bigr) \xrightarrow{\mathcal{L}} \mathcal{N}(0, 1). \]

Plus précisément on construit l’intervalle de confiance (asymptotique) à 95% à partir du TCL c’est à dire

\[ \text{pour $n$ grand} \quad \mathbf{P} \biggl( m \in \biggl[ m_n - \frac{1.96 \sigma_n}{\sqrt{n}}, m_n + \frac{1.96 \sigma_n}{\sqrt{n}} \biggr] \biggr) \simeq 0.95 \]

3.1.1. Question: LFGN loi uniforme#

Reproduire le tracé suivant où les points (les croix ‘x’) sont les réalisations \(X_n\) (en fonction de \(n\)) de loi uniforme sur \([-4,8]\). La ligne bleue (couleur ‘C0’, première couleur de la palette utilisée) correspond à la moyenne \(m\), la courbe orangée (couleur ‘C1’) correspond à la suite \(m_n\) et les lignes grises correspondent aux bornes de l’intervalle de confiance. La zone de confiance en jaune s’obtient par la méthode fill_between de ax.

N = 300
sample = rng.uniform(size = N, low = -4, high = 8)

n = np.arange(1, N+1)
mn = np.cumsum(sample) / n
sum_squares = np.cumsum(sample**2)
# attention: les vecteurs vn, ic, upper et lower sont définis pour n >= 2
vn = (sum_squares - n*mn**2)[1:] / (n[1:]-1)  # on développe le carré
ci_size = 1.96*np.sqrt(vn / n[1:])
upper = mn[1:] + ci_size
lower = mn[1:] - ci_size

fig, ax = plt.subplots()
ax.scatter(n, sample, marker="x", color='lightgrey')
ax.axhline(y=2, color='C0', label="Valeur exacte")
ax.plot(n, mn, color='C1', label="Valeur de l'estimateur")
ax.fill_between(n[1:], lower, upper, facecolor='lightyellow', 
                edgecolor='grey', label="Zone de confiance à 95%")
ax.set(xlabel = "Itération $n$", 
       ylabel = "Valeur de l'estimateur et des bornes de confiance")
ax.legend(loc='upper right')
ax.set_ylim(-4, 8)
#plt.savefig('img/tcl_unif.png')
plt.show()
../_images/b054164b39e8736b904eca5c191370498dafbf739bfc8edf180cdad5b41cea41.png

3.1.2. Question: LFGN loi de Cauchy#

Reprendre rapidement l’exemple précédent en remplaçant la loi uniforme par la loi de Cauchy. On obtient des réalisations de la loi de Cauchy en utilisant la méthode standard_cauchy de l’objet rng. Répliquer plusieurs fois le tracé (avec l’axe des ordonnées restreint à \([-10,10]\)) pour différentes valeurs de \(n=100\,000\). Qu’en pensez-vous?

N = 100000
sample = rng.standard_cauchy(size = N)

n = np.arange(1, N+1)
mn = np.cumsum(sample) / n
sum_squares = np.cumsum(sample**2)
# attention: les vecteurs vn, ic, upper et lower sont définis pour n >= 2
vn = (sum_squares - n*mn**2)[1:] / (n[1:]-1)
ci_size = 1.96*np.sqrt(vn / n[1:])
upper = mn[1:] + ci_size
lower = mn[1:] - ci_size

fig, ax = plt.subplots()
ax.scatter(n, sample, marker="x", color='lightgrey')
ax.axhline(y=0, color='C0', label="Valeur exacte")
ax.plot(n, mn, color='C1', label="Valeur de l'estimateur")
ax.fill_between(n[1:], lower, upper, facecolor='lightyellow', 
                edgecolor='grey', label="Zone de confiance à 95%")
ax.set(xlabel = "Itération $n$", 
       ylabel = "Valeur de l'estimateur et des bornes de confiance")
ax.legend(loc='upper right')
ax.set_ylim((-10,10))
plt.show()
../_images/096a8d8384c21d5a7ad34dff3737ab156ecd20309291a0cb5012c42163436537.png

3.2. Illustration du TCL#

On veut illustrer la répartition de l’erreur renormalisée \(\displaystyle \varepsilon_n = \sqrt{n} \Bigl(\frac{m_n - m}{\sigma_n}\Bigr)\) pour différentes valeurs de \(n\). Lorsque \(n\) est grand cette erreur renormalisée est proche de la loi normale cenrée réduite, c’est ce qu’on veut vérifier numériquement. Pour illustrer cette répartition, il est nécessaire de répliquer un grand nombre de fois l’erreur c’est à dire de considérer un échantillon \((\varepsilon_n^{(j)})_{j=1,\dots,M}\) de taille \(M\) et de constuire l’histogramme de cet échantillon.

Attention: en pratique il n’est pas nécessaire de répliquer \(M\) fois l’estimateur \(m_n\) pour approcher \(m\). L’estimateur de la variance \(v_n\) suffit pour donner la zone de confiance autour de \(m_n\). C’est une information importante donnée par le TCL.

3.2.1. Question: TCL loi uniforme#

Dans le cas de la loi uniforme sur \([-4, 8]\) vérifier la répartition de l’erreur renormalisée \(\varepsilon_n\) pour \(n = 10\) puis \(n = 1\,000\) à partir d’un échantillon de taille \(M = 100\,000\).

def plot_error(ax, n, M = 100000):
    sample = rng.uniform(size = (M, n), low = -4, high = 8)

    means = np.mean(sample, axis = 1) 
    sigms = np.std(sample, axis = 1, ddof=1)
    errs = np.sqrt(n) * (means - 2) / sigms 

    ax.hist(errs, bins=50, density=True, label="distribution empirique")
    xx = np.linspace(-5, 5, 10000)
    ax.plot(xx, stats.norm.pdf(xx), label="densité gaussienne")
    ax.set(title = f"n = {n}")
    ax.legend()
    return ax

fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, sharey=True, 
                               figsize=(10,6), layout='tight')
fig.suptitle("Répartition de l'erreur renormalisée", fontsize=14)
plot_error(ax1, n=10)
plot_error(ax2, n=1000)
plt.show()
# pour N = 10 la répartition de l'erreur ne semble pas vraiment gaussienne
# pour N = 1000 le comportement semble gaussien
../_images/ab535ade9ba569afcaffbfe136cdce44de6fbd8bcc31c283adeda06e942119cc.png

3.3. Un premier exemple d’estimateur de Monte Carlo#

On va mettre en oeuvre un estimateur de Monte Carlo pour calculer

\[ I(\beta) = \mathbf{E}[\exp(\beta G)] \quad \text{où $G \sim \mathcal{N}(0,1)$ et $\beta \in \mathbf{R}$}. \]

La valeur exacte \(I(\beta) = \exp(\beta^2/2)\) est connue mais cet exemple permet d’illustrer l’importance des bornes de l’intervalle de confiance (et donc de l’estimation de la variance) dans une méthode de Monte Carlo. La seule valeur moyenne \(I_n = \frac{1}{n} \sum_{k=1}^n X_k\) n’est pas suffisante pour déterminer \(I\).

3.3.1. Question: fonction monte_carlo#

Ecrire une fonction monte_carlo(sample, proba=0.95) qui à partir d’un échantillon sample de réalisation indépendantes \((X_k)_{k=1,\dots,n}\) renvoie un tuple qui contient:

  • la moyenne de l’estimateur Monte Carlo de \(I = \mathbf{E}[X]\),

  • l’estimateur de la variance asymptotique apparaissant dans le TCL,

  • les bornes inférieures et supérieures de l’intervale de confiance de niveau de probabilité proba.

def monte_carlo(sample, proba = 0.95):
    """
    Computes the mean, variance, and a 95% confidence interval of a 
    given sample data set using the Monte Carlo method.
    Parameters:
    -----------
    sample : array-like
        The data set to be analyzed
    proba : float, optional
        The probability that the true mean of the population is 
        within the calculated interval. Default is 0.95
    Returns:
    --------
    tuple : float
        The mean, variance, lower bound of the 95% CI and upper bound of the 95% CI
    """
    mean = np.mean(sample)
    var = np.var(sample, ddof=1)
    alpha = 1 - proba 
    quantile = stats.norm.ppf(1 - alpha/2)  # fonction quantile 
    ci_size = quantile * np.sqrt(var / sample.size)
    return (mean, var, mean - ci_size, mean + ci_size)

3.3.2. Question: premier exemple#

En utilisant la fonction monte_carlo, reproduire le tableau suivant où chaque ligne représente un résultat pour une valeur de \(\beta \in \{0.2, 0.5, 1, 2, 3, 5\}\):

  • la première colonne est la valeur moyenne \(I_n\),

  • la deuxième colonne l’estimateur de la variance,

  • les colonnes 3 et 4 sont les bornes inférieures et supérieurs de l’IC à 95%,

  • la colonne 5 contient la valeur exacte \(\mathbf{E}[\exp(\beta G)] = \exp(\beta^2/2)\).

Ce tableau est obtenu pour \(n = 1\,000\,000\). Comment interpréter ce tableau?

import pandas as pd
df = pd.read_pickle("data/first_df.pkl")
df
mean var low high exact
0.2 1.020551 4.245536e-02 1.020147 1.020955 1.020201
0.5 1.134027 3.650418e-01 1.132843 1.135212 1.133148
1.0 1.651060 4.676691e+00 1.646821 1.655298 1.648721
2.0 7.402685 2.379946e+03 7.307068 7.498301 7.389056
3.0 87.915075 8.558333e+06 82.181273 93.648877 90.017131
5.0 121963.825619 6.439313e+14 72228.169429 171699.481809 268337.286521
n = int(1e6)
sample = rng.standard_normal(size=n)

betas = [0.2, 0.5, 1, 2, 3, 5]
result = [ monte_carlo(np.exp(beta * sample)) for beta in betas ]

# results est une liste de tuple on peut le convertir en DataFrame 
# pour manipuler plus facilement ce résultat 
import pandas as pd
res_df = pd.DataFrame(result, 
                      columns=["mean", "var", "lower", "upper"], 
                      index=betas)
res_df["exact"] = np.exp(0.5 * np.array(betas)**2)
res_df
#res_df.to_pickle("data/first_df.pkl")  
mean var lower upper exact
0.2 1.020116 4.253056e-02 1.019712 1.020521 1.020201
0.5 1.133027 3.648723e-01 1.131843 1.134211 1.133148
1.0 1.648622 4.657376e+00 1.644392 1.652852 1.648721
2.0 7.375326 2.842169e+03 7.270836 7.479815 7.389056
3.0 89.462984 2.113224e+07 80.453065 98.472903 90.017131
5.0 202132.358793 7.044511e+15 37629.473615 366635.243971 268337.286521

3.4. Option panier: un exemple multidimensionnel#

On considère \(d \ge 2\) actifs financiers dont la loi à l’instant \(T > 0\) est modélisée par une loi log-normale c’est à dire

\[\begin{equation*} \forall i \in \{1,\dots,d\}, \quad S^i_T = S^i_0 \exp\Bigl( \bigl(r-\frac{\sigma_i^2}{2}\bigr) T + \sigma_i \sqrt{T} \tilde G_i \Bigr) \end{equation*}\]

où le vecteur \((\tilde G_1,\dots, \tilde G_d)\) est gaussien centré de matrice de covariance \(\Sigma\) et les constantes \(r > 0\), \(\sigma_i > 0\) sont fixées. Il s’agit d’actifs financiers \((S^i_t)_{t \in [0,T]}\), \(1 \le i \le d\), modélisés par un processus de Black-Scholes multidimensionnel. On introduit la matrice \(L\) triangulaire inférieure obtenue par la décomposition de Cholesky de la matrice \(\Sigma = L L^\top\).

A l’aide de cette matrice \(L\), on définit la fonction \(\Phi:\mathbf{R}^d \to \mathbf{R}^d\) telle que

\[\begin{equation*} (S^1_T, \dots, S^d_T) = \Phi(G_1, \dots, G_d) \quad \text{ou encore} \quad S^i_T = \Phi_i(G_1, \dots, G_d) \end{equation*}\]

\((G_1, \dots, G_d) \sim \mathcal{N}(0, I_d)\) (l’égalité précédente est à considérer en loi).

On s’intéresse au prix d’une option européenne (aussi appelé produit dérivé européen) sur le panier de ces \(d\) actifs financiers, c’est à dire qu’on veut calculer

\[\begin{equation*} \mathbf{E} \bigl[ X \bigr] %\quad \text{avec} \quad g(x) = (x-K)_+ \quad \text{ou} \quad g(x) = (K-x)_+ \quad \text{avec} \quad X = \biggl(\frac{1}{d} \sum_{i=1}^d S^i_T - K\biggr)_+. \end{equation*}\]

3.4.1. Question: initialisation#

Définir les paramètres globaux \(d = 10\), \(T = 1\), \(r = 0.01\), \(S^i_0 =100\) (pour tous les actifs), \(\sigma_i = i / (2d)\) (on dit que certains actifs sont plus volatiles que d’autres) et la matrice de corrélation \(\Sigma\) définie par \(\Sigma_{i,i} = 1\) et \(\Sigma_{i,j} = \rho \in [0,1]\) pour \(i \neq j\), avec \(\rho = 0.2\).

Initialiser la matrice \(L\) en utilisant la fonction np.linalg.cholesky.

d = 10
T = 1
r = 0.01
S0 = np.full(d, 100)
sigma = np.arange(1,d+1)/(2*d)
mu = r - 0.5*sigma**2
rho = 0.2
correl = np.full((d,d), rho) + (1-rho)*np.eye(d) #ou np.diag(np.full(d, 1-rho))
mat_L = np.linalg.cholesky(correl)

3.4.2. Question: simulation d’un échantillon d’actifs#

Définir la fonction python phi qui transforme le vecteur \((G_1, \dots, G_d)\) en un vecteur \((S_T^1,\dots, S_T^d)\) (tous les paramètres sont des variables globales pour simplifier l’écriture du code). L’appel suivant doit fonctionner

G = rng.standard_normal(size=d)
phi(G)

Si on veut implémenter un estimateur Monte Carlo il faut travailler avec des échantillons i.i.d. \((S^{(j)}_T)_{j=1,\dots,n}\)\(S^{(j)}_T = \big(S_T^{(j),1}, \dots, S_T^{(j),d}\big) \in \mathbf{R}^d\). Modifier votre fonction phi pour création un tel échantillon à partir de l’appel suivant:

sample_G = rng.standard_normal(size=(d, n))
phi(sample_G)

(il faut utiliser la technique du broadcasting en numpy, c’est très important à connaitre en pratique).

# première version
def phi(G): 
    ST = S0 * np.exp(mu * T + sigma * np.sqrt(T) * mat_L @ G)
    return ST

G = rng.standard_normal(size=d)
print(phi(G))

# deuxième version pour obtenir un échantillon de taille `n`
def phi(sample_G): 
    sample_ST = S0[:,np.newaxis] * np.exp(mu[:,np.newaxis] * T 
                + sigma[:,np.newaxis] * np.sqrt(T) * mat_L @ sample_G)
    return sample_ST

n = 1000
sample_G = rng.standard_normal(size=(d, n))
print(phi(sample_G))
[ 99.96537568  94.96496898  83.93599816 108.26195125 112.36085887
 122.70948722 110.09862328  97.90383949  82.66180161  37.34874149]
[[103.83647287  96.18704383  91.99171427 ...  95.6963547  104.26602381
  104.38476592]
 [114.10794291  93.49561394  90.47202804 ... 115.87126181 103.20332559
  106.49403678]
 [ 99.3493601  115.6006325   85.44220322 ... 115.54121976  85.96743048
  101.98728916]
 ...
 [111.90772226  68.81640978 106.21356659 ...  83.58608907  97.48325646
   83.27915703]
 [245.02569538 139.68181239 100.0900696  ...  56.40187128  83.8112142
   98.50914844]
 [119.19497612  71.81858757  56.40710815 ...  68.9761752   72.72787206
   99.78569589]]

3.4.3. Question: estimateur Monte Carlo#

Définir une fonction \(\psi: \mathbf{R}^d \times \mathbf{R}_+ \to \mathbf{R}_+\) telle que

\[\begin{equation*} \psi(G_1, \dots, G_d, K) = \biggl(\frac{1}{d} \sum_{i=1}^d \Phi_i(G_1, \dots, G_d) - K\biggr)_+ \end{equation*}\]

dans une fonction python appelée psi. Cette fonction doit fonctionner avec un échantillon \((G^{(j)}_1, \dots, G^{(j)}_d)_{j=1,\dots,n}\).
Ecrire et programmer l’estimateur de Monte Carlo pour estimer la quantité \(\mathbf{E}[X] = \mathbf{E}[\psi(G_1, \dots, G_d, K)]\)\((G_1, \dots, G_d) \sim \mathcal{N}(0, I_d)\).

Pour différentes valeur de \(K \in \{80,90,100,110,120\}\) et \(n = 100\,000\) vous devez obtenir le tableau suivant:

df = pd.read_pickle("data/basket_mc.pkl")
df
mean var lower upper
80 21.394471 228.318772 21.300818 21.488123
90 12.860460 181.187947 12.777032 12.943889
100 6.655165 111.553749 6.589702 6.720627
110 2.998650 54.132985 2.953049 3.044252
120 1.204158 21.976278 1.175102 1.233213
def psi(sample_G, K): 
    return np.maximum(phi(sample_G).mean(axis=0) - K, 0)

n = int(1e5)
sample_G = rng.standard_normal(size=(d, n))
Ks = [80, 90, 100, 110, 120]
result = [ monte_carlo(psi(sample_G, K)) for K in Ks ]

df_mc = pd.DataFrame(result, 
                         columns=['mean', 'var', 'lower', 'upper'], 
                         index=Ks)
df_mc
#df_mc.to_pickle('data/basket_mc.pkl')
mean var lower upper
80 21.362045 230.097343 21.268029 21.456062
90 12.833163 182.898157 12.749342 12.916984
100 6.642218 113.121062 6.576297 6.708138
110 2.999236 55.676861 2.952988 3.045483
120 1.222169 23.308692 1.192246 1.252092

3.4.4. Question: variables antithétiques#

Sur le même modèle que précédemment, implémenter la méthode de Monte Carlo avec réduction de variance par variables antithétiques c’est à dire basée sur la représentation:

\[\begin{equation*} \mathbf{E}[X] = \mathbf{E} \Big[ \frac{1}{2} \bigl( \psi(G_1, \dots, G_d, K) + \psi(-G_1, \dots, -G_d, K) \bigr) \Big] \end{equation*}\]

Calculer le ratio de variance (variance de la méthode naïve divisée par variance par variables antithétiques) pour les différentes valeurs de \(K\).
Que signifie ce ratio de variance?

sample_G = rng.standard_normal(size=(d, n))
Ks = [80, 90, 100, 110, 120]
result = []
for K in Ks:
    sample = 0.5 * (psi(sample_G, K) + psi(-sample_G, K))
    result.append(monte_carlo(sample))

df_antith = pd.DataFrame(result, 
                         columns=['mean', 'var', 'lower', 'upper'], 
                         index=Ks)
df_antith
mean var lower upper
80 21.310824 14.260199 21.287419 21.334229
90 12.784481 25.969459 12.752896 12.816065
100 6.597766 34.161011 6.561541 6.633992
110 2.970247 22.789823 2.940659 2.999835
120 1.199370 10.488113 1.179297 1.219442
# le ratio des variances pour les différentes valeurs de K
df_mc["var"] / df_antith["var"]
80     16.135633
90      7.042817
100     3.311409
110     2.443058
120     2.222391
Name: var, dtype: float64

3.5. Option panier: une variable de contrôle#

Dans le cas de la dimension 1 (\(d=1\)), le prix est donnée par une formule fermée, on appelle cette formule la formule de Black-Scholes. Pour une option Basket (en dimension \(d \ge 2\)) on approche le prix par Monte Carlo mais on peut utiliser des approximations pour construire un problème unidimensionnel proche du produit Basket. Ces approximations servent de variables de contrôles: on ne rajoute pas une erreur, on retire de la variance.

On rappelle que, en posant \(\mu_i = r - \frac{1}{2}\sigma_i^2\), et \((\tilde G_1,\dots, \tilde G_d)\) un vecteur gaussien centré de matrice de covariance \(\Sigma\),

\[\begin{equation*} X = \biggl(\frac{1}{d} \sum_{i=1}^d S^i_0 e^{\mu_i T + \sigma_i \sqrt{T} \tilde G_i} - K\biggr)_+ \end{equation*}\]

et en introduisant \(a^i_0 = \frac{S^i_0}{\sum_{j=1}^d S^j_0}\) (t.q. \(\sum a^i_0 = 1\)) et \(\bar S_0 = \frac{1}{d} \sum_{i=1}^d S^i_0\) on a

\[\begin{equation*} X = \biggl(\bar S_0 \sum_{i=1}^d a^i_0 e^{\mu_i T + \sigma_i \sqrt{T} \tilde G_i} - K\biggr)_+. \end{equation*}\]

La variable de contrôle proposée est obtenue en échangeant l’exponentielle et la moyenne pondérée par les poids \(\big(a^i_0\big)_{i=1,\dots,d}\):

\[\begin{equation*} Y = \bigl(\bar S_0 e^Z - K\bigr)_+ \quad \text{avec} \quad Z = \sum_{i=1}^d a^i_0 \big(\mu_i T + \sigma_i \sqrt{T} \tilde G_i\big) \end{equation*}\]

La variable aléatoire \(Z\) suit une loi gaussienne \(Z \sim \mathcal{N}(m T, s^2 T)\) avec

\[\begin{equation*} m = \sum_{i=1}^d a^i_0 \mu_i \quad \text{et} \quad s^2 = \sum_{i=1}^d \Big( \sum_{j=1}^d a^i_0 \sigma_i L_{ij} \Big)^2. \end{equation*}\]

Ainsi l’espérance de la variable de contrôle \(Y\) est connue par la formule de Black-Scholes, car elle correspond au prix d’un call de strike \(K\) d’un actif Black-Scholes de dimension 1, de valeur initiale \(\bar S_0\), de taux \(\rho = m+\frac{1}{2} s^2\) et de volatilité \(s\) (à un facteur d’actualisation près… attention à ça). On a donc

\[\begin{equation*} e^{-\rho T} \mathbf{E} \big[ Y \big] = P_{\text{BS}}\big(\bar S_0, \rho, s, T, K\big), \end{equation*}\]

\[\begin{equation*} P_{\text{BS}}\big(x, r, \sigma, T, K\big) = x F_{\mathcal{N}(0,1)}(d_1) - K e^{-r T} F_{\mathcal{N}(0,1)}(d_2), \end{equation*}\]

avec \(F_{\mathcal{N}(0,1)}\) est la fonction de répartition de la loi normale centrée réduite et la notation

\[\begin{equation*} d_1 = \frac{1}{\sigma \sqrt{T}} \Big( \log\big( \frac{x}{K} \big) + \big(r + \frac{\sigma^2}{2}\big) T \Big) \quad \text{et} \quad d_2 = d_1 - \sigma \sqrt{T} \end{equation*}\]

3.5.1. Question: préliminaires pour la variable de contrôle#

  • Définir la fonction price_call_BS qui code la fonction \(P_{\text{BS}}\big(x, r, \sigma, T, K\big)\) définie ci-dessus.

  • Initialiser les paramètres \(\bar S_0\), \((a^i_0)_{i=1,\dots,d}\), \(m\), \(s^2\) et \(\rho\).

  • Calculer \(\mathbf{E}[Y]\) par la formule fermée.

  • Calculer \(\mathbf{E}[Y]\) par un estimateur Monte Carlo à partir de réalisations de \(( G_1^{(j)}, \dots, G_d^{(j)})\), \(j \in \{1, \dots, n\}\).

  • Vérifier que tout est cohérent.

def price_call_BS(x, r, sigma, T, K):
    d1 = (np.log(x / K) + T * (r + 0.5*sigma**2)) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    return x * stats.norm.cdf(d1) - K * np.exp(-r * T) * stats.norm.cdf(d2)
barS0 = S0.mean()
a = S0 / S0.sum()
m = (a * (r - 0.5*sigma**2)).sum()
s2 = (((a * sigma).T @ mat_L)**2).sum()
rho = m + 0.5*s2
# calcul par formule fermée
Y_mean = np.exp(rho*T) * price_call_BS(barS0, rho, np.sqrt(s2), T=T, K=K)
print("True value:", Y_mean)
True value: 0.6195106600831646
# calcul par Monte Carlo
n = int(1e5)
sample_G = rng.standard_normal(size=(d, n))
Z = np.sum(a[:,None] * (m*T + sigma[:,None] * np.sqrt(T) * mat_L @ sample_G),
           axis = 0)
Y = np.maximum(S0.mean() * np.exp(Z) - K, 0) 
monte_carlo(Y)
(0.629957466332255, 9.69301450326391, 0.6106610117193788, 0.6492539209451312)

3.5.2. Question: MC avec variable de contrôle#

Implémenter l’estimateur de Monte Carlo avec variable de contrôle pour le calcul de \(\mathbf{E}[X]\) c’est à dire

\[\begin{equation*} \mathbf{E}\big[ X \big] = \mathbf{E} \big[\psi(G_1,\dots,G_d,K) - (Y - \mathbf{E}[Y]) \big], \end{equation*}\]

\(Y\) est la variable de contrôle introduite précédemment et \(\mathbf{E}[Y]\) est calculée par la formule fermée.
Comparer les ratios de variance pour les différentes valeurs de \(K\).

Ks = [80, 90, 100, 110, 120]
result = []
for K in Ks:
    Z = np.sum(a[:,None] * (m*T + sigma[:,None]*np.sqrt(T)*mat_L@sample_G), 
               axis = 0)
    Y = np.maximum(S0.mean() * np.exp(Z) - K, 0) 
    Y_mean = np.exp(rho*T) * price_call_BS(barS0, rho, np.sqrt(s2), T=T, K=K)
    control_variate = Y - Y_mean
    sample = psi(sample_G, K) - control_variate
    result.append(monte_carlo(sample))

df_cv = pd.DataFrame(result, 
                     columns=['mean', 'var', 'lower', 'upper'], 
                     index=Ks)
df_cv
mean var lower upper
80 21.312330 6.587164 21.296423 21.328237
90 12.791771 7.791224 12.774470 12.809071
100 6.605860 8.057837 6.588266 6.623454
110 2.977065 6.668035 2.961060 2.993070
120 1.203720 4.468328 1.190618 1.216821
# le ratio des variances pour les différentes valeurs de K
df_mc["var"] / df_cv["var"]
80     34.931167
90     23.474894
100    14.038639
110     8.349815
120     5.216424
Name: var, dtype: float64