web-dev-qa-db-fra.com

Simulation de mouvement brownien géométrique en Python

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.

5
tgood

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:

  • Dans votre code, le second 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).
  • Le commentaire concernant la non-annualisation de votre taux court et de vos valeurs sigma peut être incorrect. Cela n'a rien à voir avec la dérive que vous constatez. Vous devez garder ces taux annualisés. Ces taux seront toujours composés en permanence (constants).

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.  enter image description here

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 Sts 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.

6
Brad Solomon

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))
0
cdo256