用Python模拟几何布朗运动

11

我正在尝试使用Python模拟几何布朗运动,通过蒙特卡罗模拟来定价欧式看涨期权。我对Python相对陌生,并且得到的答案似乎是错误的,因为它远未收敛于BS价格,而且迭代似乎出现了负趋势。希望能得到帮助。

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)

蒙特卡罗模拟示例(股票价格模拟)

我目前使用的是Python 3.6.1。

谢谢您提前的帮助。

2个回答

15

以下是代码重写,这可能会使 S 的符号更直观,并允许您检查答案的合理性。

初始点:

  • 在您的代码中,第二个 deltat 应该改为 np.sqrt(deltat)。来源在这里(是的,我知道这不是最正式的,但下面的结果应该让人放心)。
  • 有关非年化短期利率和波动率值的注释可能是不正确的。这与您正在看到的下降漂移无关。您需要将这些保持在年化利率上。这些始终将是连续复利(常数)利率。

首先,这是一个来自 Yves Hilpisch 的 GBM 路径生成函数 -《Python 金融》第 11 章。参数在链接中解释,但设置非常类似于您的设置。 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

设置您的初始值(但使用N = 252,即1年中的交易日数,作为时间增量的数量):

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)

然后生成路径:
np.random.seed(123)
paths = gen_paths(S0, r, sigma, T, N, i)

现在,进行检查:paths[-1]可以获取到到期时的结束St值:
np.average(paths[-1])
Out[44]: 104.47389541107971

现在的回报将是 (St - K, 0) 中的最大值:

CallPayoffAverage = np.average(np.maximum(0, paths[-1] - K))
CallPayoff = discount_factor * CallPayoffAverage
print(CallPayoff)
20.9973601515

如果您绘制这些路径(可以使用pd.DataFrame(paths).plot()),您会发现它们不再是下降趋势,而是St大约呈对数正态分布。

最后,这里有一个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

在GBM设置中使用更高的i值应该会导致更接近收敛。


1

看起来你正在使用错误的公式。

维基百科得到dS_t = S_t (r dt + sigma dW_t)
维基百科得到dW_t ~ Normal(0, dt)
因此S_(t+1) = S_t + S_t (r dt + sigma Normal(0, dt))

所以我认为这行代码应该改成这样:

S[y,x+1] = S[y,x]*(1 + r*deltat + sigma*np.random.normal(0,deltat))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接