J'essaie de simuler un mouvement brownien géométrique en Python, afin de chiffrer une option d'appel européenne via une simulation Monte-Carlo. Je suis relativement nouveau en Python, et je reçois une réponse que je crois être fausse, car il est loin de converger vers le prix des stations de base, et les itérations semblent être à la tendance négative pour une raison quelconque. Toute aide serait appréciée.
import numpy as np
from matplotlib import pyplot as plt
S0 = 100 #initial stock price
K = 100 #strike price
r = 0.05 #risk-free interest rate
sigma = 0.50 #volatility in market
T = 1 #time in years
N = 100 #number of steps within each simulation
deltat = T/N #time step
i = 1000 #number of simulations
discount_factor = np.exp(-r*T) #discount factor
S = np.zeros([i,N])
t = range(0,N,1)
for y in range(0,i-1):
S[y,0]=S0
for x in range(0,N-1):
S[y,x+1] = S[y,x]*(np.exp((r-(sigma**2)/2)*deltat + sigma*deltat*np.random.normal(0,1)))
plt.plot(t,S[y])
plt.title('Simulations %d Steps %d Sigma %.2f r %.2f S0 %.2f' % (i, N, sigma, r, S0))
plt.xlabel('Steps')
plt.ylabel('Stock Price')
plt.show()
C = np.zeros((i-1,1), dtype=np.float16)
for y in range(0,i-1):
C[y]=np.maximum(S[y,N-1]-K,0)
CallPayoffAverage = np.average(C)
CallPayoff = discount_factor*CallPayoffAverage
print(CallPayoff)
Exemple de simulation Monte-Carlo (simulation de cours boursier)
J'utilise actuellement Python 3.6.1.
Merci d'avance pour votre aide.
Voici un peu de réécriture du code qui pourrait rendre la notation de S
plus intuitive et vous permettra de vérifier si votre réponse est raisonnable.
Points initiaux:
deltat
doit être remplacé par np.sqrt(deltat)
. Source ici (oui, je sais que ce n’est pas le plus officiel, mais les résultats ci-dessous devraient être rassurants).En premier lieu, voici une fonction générant un chemin GBM de Yves Hilpisch - Python for Finance , chapitre 11 . Les paramètres sont expliqués dans le lien mais la configuration est très similaire à la vôtre.
def gen_paths(S0, r, sigma, T, M, I):
dt = float(T) / M
paths = np.zeros((M + 1, I), np.float64)
paths[0] = S0
for t in range(1, M + 1):
Rand = np.random.standard_normal(I)
paths[t] = paths[t - 1] * np.exp((r - 0.5 * sigma ** 2) * dt +
sigma * np.sqrt(dt) * Rand)
return paths
Définissez vos valeurs initiales (en utilisant N=252
, le nombre de jours de bourse dans 1 an, comme le nombre d'incréments de temps):
S0 = 100.
K = 100.
r = 0.05
sigma = 0.50
T = 1
N = 252
deltat = T / N
i = 1000
discount_factor = np.exp(-r * T)
Puis générez les chemins:
np.random.seed(123)
paths = gen_paths(S0, r, sigma, T, N, i)
Maintenant, inspecter: paths[-1]
vous donne les dernières valeurs St
, à l'expiration:
np.average(paths[-1])
Out[44]: 104.47389541107971
Le gain, comme vous l'avez maintenant, sera le maximum de (St - K, 0
):
CallPayoffAverage = np.average(np.maximum(0, paths[-1] - K))
CallPayoff = discount_factor * CallPayoffAverage
print(CallPayoff)
20.9973601515
Si vous tracez ces chemins (il est facile d'utiliser simplement pd.DataFrame(paths).plot()
, vous verrez qu'ils ne sont plus orientés vers le bas, mais que les St
s sont approximativement distribués normalement.
Enfin, voici un contrôle de santé par BSM:
class Option(object):
"""Compute European option value, greeks, and implied volatility.
Parameters
==========
S0 : int or float
initial asset value
K : int or float
strike
T : int or float
time to expiration as a fraction of one year
r : int or float
continuously compounded risk free rate, annualized
sigma : int or float
continuously compounded standard deviation of returns
kind : str, {'call', 'put'}, default 'call'
type of option
Resources
=========
http://www.thomasho.com/mainpages/?download=&act=model&file=256
"""
def __init__(self, S0, K, T, r, sigma, kind='call'):
if kind.istitle():
kind = kind.lower()
if kind not in ['call', 'put']:
raise ValueError('Option type must be \'call\' or \'put\'')
self.kind = kind
self.S0 = S0
self.K = K
self.T = T
self.r = r
self.sigma = sigma
self.d1 = ((np.log(self.S0 / self.K)
+ (self.r + 0.5 * self.sigma ** 2) * self.T)
/ (self.sigma * np.sqrt(self.T)))
self.d2 = ((np.log(self.S0 / self.K)
+ (self.r - 0.5 * self.sigma ** 2) * self.T)
/ (self.sigma * np.sqrt(self.T)))
# Several greeks use negated terms dependent on option type
# For example, delta of call is N(d1) and delta put is N(d1) - 1
self.sub = {'call' : [0, 1, -1], 'put' : [-1, -1, 1]}
def value(self):
"""Compute option value."""
return (self.sub[self.kind][1] * self.S0
* norm.cdf(self.sub[self.kind][1] * self.d1, 0.0, 1.0)
+ self.sub[self.kind][2] * self.K * np.exp(-self.r * self.T)
* norm.cdf(self.sub[self.kind][1] * self.d2, 0.0, 1.0))
option.value()
Out[58]: 21.792604212866848
L'utilisation de valeurs plus élevées pour i
dans votre configuration GBM devrait entraîner une convergence plus étroite.
On dirait que vous utilisez la mauvaise formule.
Avoir dS_t = S_t (r dt + sigma dW_t)
de Wikipedia
Et dW_t ~ Normal(0, dt)
de Wikipedia
Alors S_(t+1) = S_t + S_t (r dt + sigma Normal(0, dt))
Donc, je crois que la ligne devrait plutôt être ceci:
S[y,x+1] = S[y,x]*(1 + r*deltat + sigma*np.random.normal(0,deltat))