def priceWithPDE0(S0, K, r, T, sigma, M, N, Smax, is_call=True):
M, N = int(M), int(N)
dS = float(Smax) / M
dt = float(T) / N
k_values = np.arange(M)
j_values = np.arange(N)
grid = np.zeros(shape=(M+1, N+1))
S = np.linspace(0, Smax, M+1)
if is_call:
grid[:, -1] = np.maximum(S - K, 0)
grid[-1, :-1] = Smax - K* np.exp(-r *dt*(N-j_values))
else:
grid[:, -1] = np.maximum(K-S, 0)
grid[0, :-1] = K * np.exp(-r *dt *(N-j_values))
alpha = 0.25*dt*((sigma**2)*(k_values**2) -r*k_values)
beta = -dt*0.5*(
(sigma**2)*(k_values**2) + r)
gamma = 0.25*dt*((sigma**2)*(k_values**2) +r*k_values)
A1 = -np.diag(alpha[2:M], -1) + \
np.diag(1-beta[1:M]) - np.diag(gamma[1:M-1], 1)
A2 = np.diag(alpha[2:M], -1) + \
np.diag(1+beta[1:M]) + np.diag(gamma[1:M-1], 1)
print(f"{alpha.shape} {beta.shape} {gamma.shape}")
print(f"{A1.shape} {A2.shape}")
for j in reversed(range(N)):
B = A2 @ grid[1:M, j+1]
x2 = linalg.solve(A1, B)
grid[1:M, j] = x2
pv_0 = np.interp(S0, S, grid[:, 0])
return pv_0, grid, S
S0 = 80.; K = 85.; T = 1.0; r = 0.05; q = 0.0; sigma = 0.2
Smax = 160.
start = default_timer()
pv, grid, S = priceWithPDE0(S0=S0, K=K, r=r, T=T, sigma=sigma,M=100, N=100, Smax=Smax, is_call=True)
calcTime = default_timer() - start
print( "PV: %.5f, abs diff: %.5f, rel diff: %.5f" % (pv, ref_pv - pv, (ref_pv - pv)/ref_pv) )
print( "Calculation time %.5f" % calcTime )