Payer sur la moyenne
Un call standard dépend du prix un seul jour, le dernier. Une option asiatique dépend du prix moyen sur un ensemble de dates : un call asiatique paie max(moyenne - K, 0). Les entreprises les utilisent pour couvrir un coût qu'elles supportent régulièrement, par exemple un achat mensuel de pétrole, et elles sont difficiles à manipuler à l'échéance. Elles *dépendent de la trajectoire*, donc il n'existe pas de formule de Black–Scholes simple pour la moyenne arithmétique, et l'outil habituel est la simulation de Monte Carlo.
import numpy as np
# Two simulated paths: the starting price, then monthly prices; the last column is expiry
paths = np.array([[100.0, 90.0, 110.0, 130.0],
[100.0, 70.0, 80.0, 90.0]])
average = paths[:, 1:].mean(axis=1) # the monitoring dates only, not the starting price
print(average) # [110. 80.]
print(np.maximum(average - 100.0, 0.0)) # Asian call payoffs: [10. 0.]
print(np.maximum(paths[:, -1] - 100.0, 0.0)) # European call payoffs on the same paths: [30. 0.]Simuler les trajectoires
Les trajectoires viennent du mouvement brownien géométrique, avec la dérive risque-neutre : le taux r remplace mu. On tire une matrice de lois normales centrées réduites, une ligne par trajectoire et une colonne par date de suivi, on la transforme en variations du log du prix, et on fait une somme cumulée le long de chaque ligne. Un seul tableau contient alors toutes les trajectoires, et chaque étape suivante (moyennes, gains) est une opération vectorisée le long des lignes, sans boucle sur les trajectoires.
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))
log_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(log_moves, axis=1)]))
rng = np.random.default_rng(1)
paths = simulate_paths(100.0, 0.05, 0.2, 1.0, 12, 50000, rng) # monthly monitoring for a year
print(paths.shape) # (50000, 13)
print(round(float(paths[:, -1].mean()), 2), round(100 * np.exp(0.05), 2)) # the risk-neutral mean of the final priceLe prix et sa barre d'erreur
Le prix de l'option est le gain moyen actualisé : exp(-r * T) * mean(payoffs). Un prix Monte Carlo est une estimation, donc rapportez toujours son erreur standard, std(payoffs) / sqrt(nombre de trajectoires) (actualisée), qui indique de combien le nombre peut s'écarter de la vérité : quatre fois plus de trajectoires la divisent par deux. Un call asiatique est moins cher qu'un call européen sur la même action, car la moyenne lisse le prix et abaisse la volatilité effective.
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))
log_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(log_moves, axis=1)]))
def asian_price(paths, strike, rate, t):
payoff = np.maximum(paths[:, 1:].mean(axis=1) - strike, 0.0)
discount = np.exp(-rate * t)
return discount * payoff.mean(), discount * payoff.std(ddof=1) / np.sqrt(len(payoff))
rng = np.random.default_rng(1)
paths = simulate_paths(100.0, 0.05, 0.2, 1.0, 12, 100000, rng)
price, error = asian_price(paths, 100.0, 0.05, 1.0)
european = np.exp(-0.05) * np.maximum(paths[:, -1] - 100.0, 0.0).mean()
print(round(float(price), 3), "+/-", round(float(error), 3))
print(round(float(european), 3), "European call on the same paths")# Geometric and arithmetic averages of the same monitoring prices
import numpy as np
prices = np.array([90.0, 110.0, 130.0])
arithmetic = prices.mean()
geometric = np.exp(np.log(prices).mean())
print(round(float(arithmetic), 3), round(float(geometric), 3)) # 110.0 108.774: the geometric average is never larger- Une option asiatique paie sur la moyenne des dates de suivi :
max(moyenne - K, 0); elle dépend de la trajectoire, donc pas de formule simple et Monte Carlo est l'outil. - Simulez toutes les trajectoires dans un seul tableau (lignes = trajectoires, colonnes = dates) avec la dérive risque-neutre ; moyennes et gains sont alors vectorisés par lignes.
- Prix =
exp(-r * T) * mean(payoffs)avec une erreur standardstd / sqrt(paths); la moyenne le rend moins cher qu'un call européen, et la version géométrique est une variable de contrôle.
À suivre : les options à barrière, qui naissent ou meurent quand le prix touche un niveau.
Lab : ce qui est noté
- Moyenner le long de chaque trajectoire avec
average_price25 pts - Essayer la moyenne géométrique avec
geometric_average25 pts - Payer sur la moyenne avec
asian_call_payoff25 pts - La valoriser, avec une barre d'erreur, avec
asian_price25 pts