├── LICENSE ├── Lecture 01- Introduction and Overview of Asset Classes └── Lecture 01- Introduction and Overview of Asset Classes.pdf ├── Lecture 02- Stock, Options and Stochastics └── Lecture 02- Stock, Options and Stochastics.pdf ├── Lecture 03- Option Pricing and Simulation in Python ├── Lecture 03- Option Pricing and Simulation in Python.pdf └── Materials │ ├── GBM_ABM_paths.py │ ├── GBM_ABM_paths_Martingale.py │ └── PathsUnderQandPmeasure.py ├── Lecture 04- Implied Volatility ├── Lecture 04- Implied Volatility.pdf └── Materials │ └── ImpliedVolatility.py ├── Lecture 05- Jump Processes ├── Lecture 05- Jump Processes.pdf └── Materials │ ├── MertonProcess_paths.py │ └── PoissonProcess_paths.py ├── Lecture 06- Affine Jump Diffusion Processes └── Lecture 06- Affine Jump Diffusion Processes.pdf ├── Lecture 07- Stochastic Volatility Models ├── Lecture 07- Stochastic Volatility Models.pdf └── Materials │ └── CorrelatedBM.py ├── Lecture 08- Fourier Transformation for Option Pricing ├── Lecture 08- Fourier Transformation for Option Pricing.pdf └── Materials │ ├── COS_LogNormal_Density_Recovery.py │ ├── COS_Normal_Density_Recovery.py │ ├── CallPut_COS_Method.py │ ├── CashOrNothing_COS_Method.py │ └── DensityRecoveryFFT.py ├── Lecture 09- Monte Carlo Simulation ├── Lecture 09- Monte Carlo Simulation.pdf └── Materials │ ├── DeterministicFunction.py │ ├── EulerConvergence_GBM.py │ ├── Exercise_1.py │ ├── Exercise_2.py │ ├── MilsteinConvergence_GBM.py │ ├── StochasticIntegrals.py │ └── dWdW.py ├── Lecture 10- Monte Carlo Simulation of the Heston Model ├── Lecture 10- Monte Carlo Simulation of the Heston Model.pdf └── Materials │ ├── CIR_ExactSimulation.py │ ├── CIR_paths_Exact.py │ ├── CIR_paths_boundary.py │ ├── HestonModelDiscretization.py │ └── OptionPrices_EulerAndMilstein.py ├── Lecture 11- Hedging and Monte Carlo Sensitivites ├── Lecture 11- Hedging and Monte Carlo Sensitivites.pdf └── Materials │ ├── BS_Hedging.py │ ├── HedgingWithJumps.py │ └── PathwiseSens_DeltaVega.py ├── Lecture 12- Forward Start Options and Model of Bates ├── Lecture 12- Forward Start Options and Model of Bates.pdf └── Materials │ ├── BatesImpliedVolatility.py │ └── HestonForwardStart2.py ├── Lecture 13- Exotic Derivatives ├── Lecture 13- Exotic Derivatives.pdf └── Materials │ ├── AsianOption.py │ └── DigitalPayoffs_CostReduction.py ├── Lecture 14- Summary of the Course └── Lecture 14- Summary of the Course.pdf ├── Questions-and-Answers └── Courses_Questions-Volume 1.pdf └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, leszek 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Lecture 01- Introduction and Overview of Asset Classes/Lecture 01- Introduction and Overview of Asset Classes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 01- Introduction and Overview of Asset Classes/Lecture 01- Introduction and Overview of Asset Classes.pdf -------------------------------------------------------------------------------- /Lecture 02- Stock, Options and Stochastics/Lecture 02- Stock, Options and Stochastics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 02- Stock, Options and Stochastics/Lecture 02- Stock, Options and Stochastics.pdf -------------------------------------------------------------------------------- /Lecture 03- Option Pricing and Simulation in Python/Lecture 03- Option Pricing and Simulation in Python.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 03- Option Pricing and Simulation in Python/Lecture 03- Option Pricing and Simulation in Python.pdf -------------------------------------------------------------------------------- /Lecture 03- Option Pricing and Simulation in Python/Materials/GBM_ABM_paths.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 27 2018 4 | Paths for the GBM and ABM 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | 11 | def GeneratePathsGBMABM(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 12 | 13 | # Fixing random seed 14 | np.random.seed(1) 15 | 16 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 17 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 18 | S = np.zeros([NoOfPaths, NoOfSteps+1]) 19 | time = np.zeros([NoOfSteps+1]) 20 | 21 | X[:,0] = np.log(S_0) 22 | 23 | dt = T / float(NoOfSteps) 24 | for i in range(0,NoOfSteps): 25 | # making sure that samples from normal have mean 0 and variance 1 26 | if NoOfPaths > 1: 27 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 28 | 29 | X[:,i+1] = X[:,i] + (r - 0.5 * sigma **2 ) * dt + sigma * np.power(dt, 0.5)*Z[:,i] 30 | time[i+1] = time[i] +dt 31 | 32 | #Compute exponent of ABM 33 | S = np.exp(X) 34 | paths = {"time":time,"X":X,"S":S} 35 | return paths 36 | 37 | def mainCalculation(): 38 | NoOfPaths = 25 39 | NoOfSteps = 500 40 | T = 1 41 | r = 0.05 42 | sigma = 0.4 43 | S_0 = 100 44 | 45 | Paths = GeneratePathsGBMABM(NoOfPaths,NoOfSteps,T,r,sigma,S_0) 46 | timeGrid = Paths["time"] 47 | X = Paths["X"] 48 | S = Paths["S"] 49 | 50 | plt.figure(1) 51 | plt.plot(timeGrid, np.transpose(X)) 52 | plt.grid() 53 | plt.xlabel("time") 54 | plt.ylabel("X(t)") 55 | 56 | plt.figure(2) 57 | plt.plot(timeGrid, np.transpose(S)) 58 | plt.grid() 59 | plt.xlabel("time") 60 | plt.ylabel("S(t)") 61 | 62 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 03- Option Pricing and Simulation in Python/Materials/GBM_ABM_paths_Martingale.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 27 2018 4 | Paths for the GBM and ABM 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | 11 | def GeneratePathsGBMABM(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 12 | 13 | # Fixing random seed 14 | np.random.seed(1) 15 | 16 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 17 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 18 | S = np.zeros([NoOfPaths, NoOfSteps+1]) 19 | time = np.zeros([NoOfSteps+1]) 20 | 21 | X[:,0] = np.log(S_0) 22 | 23 | dt = T / float(NoOfSteps) 24 | for i in range(0,NoOfSteps): 25 | # making sure that samples from normal have mean 0 and variance 1 26 | if NoOfPaths > 1: 27 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 28 | 29 | X[:,i+1] = X[:,i] + (r - 0.5 * sigma **2 ) * dt + sigma * np.power(dt, 0.5)*Z[:,i] 30 | time[i+1] = time[i] +dt 31 | 32 | #Compute exponent of ABM 33 | S = np.exp(X) 34 | paths = {"time":time,"X":X,"S":S} 35 | return paths 36 | 37 | def mainCalculation(): 38 | NoOfPaths = 100000 39 | NoOfSteps = 500 40 | 41 | T = 1 42 | r = 0.05 43 | sigma = 0.4 44 | S_0 = 100 45 | 46 | M = lambda r,t: np.exp(r*t) 47 | 48 | Paths = GeneratePathsGBMABM(NoOfPaths,NoOfSteps,T,r,sigma,S_0) 49 | timeGrid = Paths["time"] 50 | X = Paths["X"] 51 | S = Paths["S"] 52 | 53 | #plt.figure(1) 54 | #plt.plot(timeGrid, np.transpose(X)) 55 | #plt.grid() 56 | #plt.xlabel("time") 57 | #plt.ylabel("X(t)") 58 | 59 | #plt.figure(2) 60 | #plt.plot(timeGrid, np.transpose(S)) 61 | #plt.grid() 62 | #plt.xlabel("time") 63 | #plt.ylabel("S(t)") 64 | 65 | # checking martingale property 66 | ES = np.mean(S[:,-1]) 67 | print(ES) 68 | 69 | ESM = np.mean(S[:,-1]/M(r,T)) 70 | print(ESM) 71 | 72 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 03- Option Pricing and Simulation in Python/Materials/PathsUnderQandPmeasure.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 28 2018 4 | Generating Monte Carlo paths under P and Q measure 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 11 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 12 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 13 | S = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | time = np.zeros([NoOfSteps+1]) 15 | 16 | X[:,0] = np.log(S_0) 17 | 18 | dt = T / float(NoOfSteps) 19 | for i in range(0,NoOfSteps): 20 | # making sure that samples from normal have mean 0 and variance 1 21 | if NoOfPaths > 1: 22 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 23 | 24 | X[:,i+1] = X[:,i] + (r - 0.5 * sigma * sigma) * dt + sigma *\ 25 | np.power(dt, 0.5)*Z[:,i] 26 | time[i+1] = time[i] +dt 27 | 28 | #Compute exponent of ABM 29 | S = np.exp(X) 30 | paths = {"time":time,"S":S} 31 | return paths 32 | 33 | def MainCode(): 34 | NoOfPaths = 8 35 | NoOfSteps = 1000 36 | S_0 = 1 37 | r = 0.05 38 | mu = 0.15 39 | sigma = 0.1 40 | T = 10 41 | # Money savings account 42 | M = lambda t: np.exp(r * t) 43 | 44 | # Monte Carlo Paths 45 | 46 | pathsQ = GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0) 47 | S_Q = pathsQ["S"] 48 | pathsP = GeneratePathsGBM(NoOfPaths,NoOfSteps,T,mu,sigma,S_0) 49 | S_P = pathsP["S"] 50 | time= pathsQ["time"] 51 | 52 | # Discounted Stock paths 53 | S_Qdisc = np.zeros([NoOfPaths,NoOfSteps+1]) 54 | S_Pdisc = np.zeros([NoOfPaths,NoOfSteps+1]) 55 | i = 0 56 | for i, ti in enumerate(time): 57 | S_Qdisc[:, i] = S_Q[:,i]/M(ti) 58 | S_Pdisc[:, i] = S_P[:,i]/M(ti) 59 | 60 | # S(T)/M(T) with Stock growing with rate r 61 | plt.figure(1) 62 | plt.grid() 63 | plt.xlabel("time") 64 | plt.ylabel("S(t)") 65 | eSM_Q = lambda t: S_0 * np.exp(r *t) / M(t) 66 | plt.plot(time,eSM_Q(time),'r--') 67 | plt.plot(time, np.transpose(S_Qdisc),'blue') 68 | plt.legend(['E^Q[S(t)/M(t)]','paths S(t)/M(t)']) 69 | 70 | # S(T)/M(T) with Stock growing with rate mu 71 | plt.figure(2) 72 | plt.grid() 73 | plt.xlabel("time") 74 | plt.ylabel("S(t)") 75 | eSM_P = lambda t: S_0 * np.exp(mu *t) / M(t) 76 | plt.plot(time,eSM_P(time),'r--') 77 | plt.plot(time, np.transpose(S_Pdisc),'blue') 78 | plt.legend(['E^P[S(t)/M(t)]','paths S(t)/M(t)']) 79 | 80 | MainCode() -------------------------------------------------------------------------------- /Lecture 04- Implied Volatility/Lecture 04- Implied Volatility.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 04- Implied Volatility/Lecture 04- Implied Volatility.pdf -------------------------------------------------------------------------------- /Lecture 04- Implied Volatility/Materials/ImpliedVolatility.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Wed Oct 24 20:37:32 2018 4 | Black- Scholes implied volatility function 5 | 6 | @author: Lech A. Grzelak 7 | """ 8 | import numpy as np 9 | import scipy.stats as st 10 | # Initial parameters and market quotes 11 | V_market = 2 # market call option price 12 | K = 120 # strike 13 | tau = 1 # time-to-maturity 14 | r = 0.05 # interest rate 15 | S_0 = 100 # today's stock price 16 | sigmaInit = 0.25 # Initial implied volatility 17 | CP ="c" #C is call and P is put 18 | 19 | def ImpliedVolatility(CP,S_0,K,sigma,tau,r): 20 | error = 1e10; # initial error 21 | #Handy lambda expressions 22 | optPrice = lambda sigma: BS_Call_Option_Price(CP,S_0,K,sigma,tau,r) 23 | vega= lambda sigma: dV_dsigma(S_0,K,sigma,tau,r) 24 | 25 | # While the difference between the model and the arket price is large 26 | # follow the iteration 27 | n = 1.0 28 | while error>10e-10: 29 | g = optPrice(sigma) - V_market 30 | g_prim = vega(sigma) 31 | sigma_new = sigma - g / g_prim 32 | 33 | #error=abs(sigma_new-sigma) 34 | error=abs(g) 35 | sigma=sigma_new; 36 | 37 | print('iteration {0} with error = {1}'.format(n,error)) 38 | 39 | n= n+1 40 | return sigma 41 | 42 | # Vega, dV/dsigma 43 | def dV_dsigma(S_0,K,sigma,tau,r): 44 | #parameters and value of Vega 45 | d2 = (np.log(S_0 / float(K)) + (r - 0.5 * np.power(sigma,2.0)) * tau) / float(sigma * np.sqrt(tau)) 46 | value = K * np.exp(-r * tau) * st.norm.pdf(d2) * np.sqrt(tau) 47 | return value 48 | 49 | def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r): 50 | #Black-Scholes Call option price 51 | d1 = (np.log(S_0 / float(K)) + (r + 0.5 * np.power(sigma,2.0)) * tau) / float(sigma * np.sqrt(tau)) 52 | d2 = d1 - sigma * np.sqrt(tau) 53 | if str(CP).lower()=="c" or str(CP).lower()=="1": 54 | value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau) 55 | elif str(CP).lower()=="p" or str(CP).lower()=="-1": 56 | value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0 57 | return value 58 | 59 | sigma_imp = ImpliedVolatility(CP,S_0,K,sigmaInit,tau,r) 60 | message = '''Implied volatility for CallPrice= {}, strike K={}, 61 | maturity T= {}, interest rate r= {} and initial stock S_0={} 62 | equals to sigma_imp = {:.7f}'''.format(V_market,K,tau,r,S_0,sigma_imp) 63 | 64 | print(message) 65 | 66 | # Check! 67 | val = BS_Call_Option_Price(CP,S_0,K,sigma_imp,tau,r) 68 | print('Option Price for implied volatility of {0} is equal to {1}'.format(sigma_imp, val)) 69 | -------------------------------------------------------------------------------- /Lecture 05- Jump Processes/Lecture 05- Jump Processes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 05- Jump Processes/Lecture 05- Jump Processes.pdf -------------------------------------------------------------------------------- /Lecture 05- Jump Processes/Materials/MertonProcess_paths.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Jan 11 2019 4 | Paths for the Jump diffusion proces of Merton 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def GeneratePathsMerton(NoOfPaths,NoOfSteps,S0, T,xiP,muJ,sigmaJ,r,sigma): 11 | # Create empty matrices for Poisson process and for compensated Poisson process 12 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 13 | S = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | time = np.zeros([NoOfSteps+1]) 15 | 16 | dt = T / float(NoOfSteps) 17 | X[:,0] = np.log(S0) 18 | S[:,0] = S0 19 | 20 | # Expectation E(e^J) for J~N(muJ,sigmaJ^2) 21 | EeJ = np.exp(muJ + 0.5*sigmaJ*sigmaJ) 22 | 23 | ZPois = np.random.poisson(xiP*dt,[NoOfPaths,NoOfSteps]) 24 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 25 | 26 | J = np.random.normal(muJ,sigmaJ,[NoOfPaths,NoOfSteps]) 27 | 28 | for i in range(0,NoOfSteps): 29 | # making sure that samples from normal have mean 0 and variance 1 30 | if NoOfPaths > 1: 31 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 32 | # making sure that samples from normal have mean 0 and variance 1 33 | X[:,i+1] = X[:,i] + (r - xiP*(EeJ-1) - 0.5*sigma*sigma)*dt +sigma*np.sqrt(dt)* Z[:,i]\ 34 | + J[:,i] * ZPois[:,i] 35 | time[i+1] = time[i] +dt 36 | 37 | S = np.exp(X) 38 | paths = {"time":time,"X":X,"S":S} 39 | return paths 40 | 41 | def mainCalculation(): 42 | NoOfPaths = 25 43 | NoOfSteps = 500 44 | T = 5 45 | xiP = 1 46 | muJ = 0 47 | sigmaJ = 0.7 48 | sigma = 0.2 49 | 50 | S0 =100 51 | r=0.05 52 | Paths = GeneratePathsMerton(NoOfPaths,NoOfSteps,S0, T,xiP,muJ,sigmaJ,r,sigma) 53 | timeGrid = Paths["time"] 54 | X = Paths["X"] 55 | S = Paths["S"] 56 | 57 | plt.figure(1) 58 | plt.plot(timeGrid, np.transpose(X)) 59 | plt.grid() 60 | plt.xlabel("time") 61 | plt.ylabel("X(t)") 62 | 63 | plt.figure(2) 64 | plt.plot(timeGrid, np.transpose(S)) 65 | plt.grid() 66 | plt.xlabel("time") 67 | plt.ylabel("S(t)") 68 | 69 | 70 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 05- Jump Processes/Materials/PoissonProcess_paths.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Jan 11 2019 4 | Paths for the Poisson process and the compensated Poisson process 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def GeneratePathsPoisson(NoOfPaths,NoOfSteps,T,xiP): 11 | # Create empty matrices for Poisson process and for compensated Poisson process 12 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 13 | Xc = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | time = np.zeros([NoOfSteps+1]) 15 | 16 | dt = T / float(NoOfSteps) 17 | 18 | Z = np.random.poisson(xiP*dt,[NoOfPaths,NoOfSteps]) 19 | 20 | for i in range(0,NoOfSteps): 21 | # making sure that samples from normal have mean 0 and variance 1 22 | X[:,i+1] = X[:,i] + Z[:,i] 23 | Xc[:,i+1] = Xc[:,i] -xiP*dt + Z[:,i] 24 | time[i+1] = time[i] +dt 25 | 26 | paths = {"time":time,"X":X,"Xcomp":Xc} 27 | return paths 28 | 29 | def mainCalculation(): 30 | NoOfPaths = 25 31 | NoOfSteps = 500 32 | T = 30 33 | xiP= 1 34 | 35 | Paths = GeneratePathsPoisson(NoOfPaths,NoOfSteps,T,xiP) 36 | timeGrid = Paths["time"] 37 | X = Paths["X"] 38 | Xc = Paths["Xcomp"] 39 | 40 | plt.figure(1) 41 | plt.plot(timeGrid, np.transpose(X),'-b') 42 | plt.grid() 43 | plt.xlabel("time") 44 | plt.ylabel("X(t)") 45 | 46 | plt.figure(2) 47 | plt.plot(timeGrid, np.transpose(Xc),'-b') 48 | plt.grid() 49 | plt.xlabel("time") 50 | plt.ylabel("X(t)") 51 | 52 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 06- Affine Jump Diffusion Processes/Lecture 06- Affine Jump Diffusion Processes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 06- Affine Jump Diffusion Processes/Lecture 06- Affine Jump Diffusion Processes.pdf -------------------------------------------------------------------------------- /Lecture 07- Stochastic Volatility Models/Lecture 07- Stochastic Volatility Models.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 07- Stochastic Volatility Models/Lecture 07- Stochastic Volatility Models.pdf -------------------------------------------------------------------------------- /Lecture 07- Stochastic Volatility Models/Materials/CorrelatedBM.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Feb 09 2019 4 | Correlated Brownian motions 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | 11 | def GeneratePathsCorrelatedBM(NoOfPaths,NoOfSteps,T,rho): 12 | Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 13 | Z2 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 14 | W1 = np.zeros([NoOfPaths, NoOfSteps+1]) 15 | W2 = np.zeros([NoOfPaths, NoOfSteps+1]) 16 | 17 | dt = T / float(NoOfSteps) 18 | time = np.zeros([NoOfSteps+1]) 19 | for i in range(0,NoOfSteps): 20 | # making sure that samples from normal have mean 0 and variance 1 21 | if NoOfPaths > 1: 22 | Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i]) 23 | Z2[:,i] = (Z2[:,i] - np.mean(Z2[:,i])) / np.std(Z2[:,i]) 24 | 25 | # Correlate noises 26 | Z2[:,i]= rho * Z1[:,i] + np.sqrt(1.0 - rho**2) * Z2[:,i] 27 | 28 | W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i] 29 | W2[:,i+1] = W2[:,i] + np.power(dt, 0.5)*Z2[:,i] 30 | 31 | time[i+1] = time[i] +dt 32 | 33 | #Store the results 34 | paths = {"time":time,"W1":W1,"W2":W2} 35 | return paths 36 | 37 | def mainCalculation(): 38 | NoOfPaths = 1 39 | NoOfSteps = 500 40 | T = 1.0 41 | 42 | ############### Negative correlation ###################### 43 | rho =-0.9 44 | Paths = GeneratePathsCorrelatedBM(NoOfPaths,NoOfSteps,T,rho) 45 | timeGrid = Paths["time"] 46 | W1 = Paths["W1"] 47 | W2 = Paths["W2"] 48 | 49 | plt.figure(1) 50 | plt.plot(timeGrid, np.transpose(W1)) 51 | plt.plot(timeGrid, np.transpose(W2)) 52 | plt.grid() 53 | plt.xlabel("time") 54 | plt.ylabel("W(t)") 55 | 56 | ############### Positive correlation ###################### 57 | rho =0.9 58 | Paths = GeneratePathsCorrelatedBM(NoOfPaths,NoOfSteps,T,rho) 59 | timeGrid = Paths["time"] 60 | W1 = Paths["W1"] 61 | W2 = Paths["W2"] 62 | 63 | plt.figure(2) 64 | plt.plot(timeGrid, np.transpose(W1)) 65 | plt.plot(timeGrid, np.transpose(W2)) 66 | plt.grid() 67 | plt.xlabel("time") 68 | plt.ylabel("W(t)") 69 | 70 | ############### Zero correlation ###################### 71 | rho =0.0 72 | Paths = GeneratePathsCorrelatedBM(NoOfPaths,NoOfSteps,T,rho) 73 | timeGrid = Paths["time"] 74 | W1 = Paths["W1"] 75 | W2 = Paths["W2"] 76 | 77 | plt.figure(3) 78 | plt.plot(timeGrid, np.transpose(W1)) 79 | plt.plot(timeGrid, np.transpose(W2)) 80 | plt.grid() 81 | plt.xlabel("time") 82 | plt.ylabel("W(t)") 83 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 08- Fourier Transformation for Option Pricing/Lecture 08- Fourier Transformation for Option Pricing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 08- Fourier Transformation for Option Pricing/Lecture 08- Fourier Transformation for Option Pricing.pdf -------------------------------------------------------------------------------- /Lecture 08- Fourier Transformation for Option Pricing/Materials/COS_LogNormal_Density_Recovery.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | LogNormal density recovery using the COS method 4 | """ 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | import scipy.stats as st 8 | 9 | def COSDensity(cf,x,N,a,b): 10 | i = np.complex(0.0,1.0) #assigning i=sqrt(-1) 11 | k = np.linspace(0,N-1,N) 12 | u = np.zeros([1,N]) 13 | u = k * np.pi / (b-a) 14 | 15 | #F_k coefficients 16 | F_k = 2.0 / (b - a) * np.real(cf(u) * np.exp(-i * u * a)); 17 | F_k[0] = F_k[0] * 0.5; # adjustment for the first term 18 | 19 | #Final calculation 20 | f_X = np.matmul(F_k , np.cos(np.outer(u, x - a ))) 21 | 22 | # we output only the first row 23 | return f_X 24 | 25 | def mainCalculation(): 26 | 27 | 28 | i = np.complex(0.0, 1.0) #assigning i=sqrt(-1) 29 | 30 | # setting for the COS method 31 | a = -10 32 | b = 10 33 | 34 | #define the range for the expansion points 35 | N = [16, 64, 128] 36 | 37 | # setting for normal distribution 38 | mu = 0.5 39 | sigma = 0.2 40 | 41 | # Define characteristic function for the normal distribution 42 | cF = lambda u : np.exp(i * mu * u - 0.5 * np.power(sigma,2.0) * np.power(u,2.0)); 43 | 44 | # define domain for density 45 | y = np.linspace(0.05,5,1000) 46 | 47 | plt.figure(1) 48 | plt.grid() 49 | plt.xlabel("y") 50 | plt.ylabel("$f_Y(y)$") 51 | for n in N: 52 | f_Y = 1/y * COSDensity(cF,np.log(y),n,a,b) 53 | 54 | plt.plot(y,f_Y) 55 | plt.legend(["N=%.0f"%N[0],"N=%.0f"%N[1],"N=%.0f"%N[2]]) 56 | 57 | 58 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 08- Fourier Transformation for Option Pricing/Materials/COS_Normal_Density_Recovery.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Normal density recovery using the COS method 4 | """ 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | import scipy.stats as st 8 | 9 | def COSDensity(cf,x,N,a,b): 10 | i = np.complex(0.0,1.0) #assigning i=sqrt(-1) 11 | k = np.linspace(0,N-1,N) 12 | u = np.zeros([1,N]) 13 | u = k * np.pi / (b-a) 14 | 15 | #F_k coefficients 16 | F_k = 2.0 / (b - a) * np.real(cf(u) * np.exp(-i * u * a)); 17 | F_k[0] = F_k[0] * 0.5; # adjustment for the first term 18 | 19 | #Final calculation 20 | f_X = np.matmul(F_k , np.cos(np.outer(u, x - a ))) 21 | 22 | # we output only the first row 23 | return f_X 24 | 25 | def mainCalculation(): 26 | i = np.complex(0.0, 1.0) #assigning i=sqrt(-1) 27 | 28 | # setting for the COS method 29 | a = -10.0 30 | b = 10.0 31 | 32 | #define the range for the expansion points 33 | N = [2**x for x in range(2,7,1)] 34 | 35 | # setting for normal distribution 36 | mu = 0.0 37 | sigma = 1.0 38 | 39 | # Define characteristic function for the normal distribution 40 | cF = lambda u : np.exp(i * mu * u - 0.5 * np.power(sigma,2.0) * np.power(u,2.0)); 41 | 42 | # define domain for density 43 | x = np.linspace(-10.0,10,1000) 44 | f_XExact = st.norm.pdf(x,mu,sigma) 45 | 46 | plt.figure(1) 47 | plt.grid() 48 | plt.xlabel("x") 49 | plt.ylabel("$f_X(x)$") 50 | for n in N: 51 | f_X = COSDensity(cF,x,n,a,b) 52 | error = np.max(np.abs(f_X-f_XExact)) 53 | print("For {0} expanansion terms the error is {1}".format(n,error)) 54 | 55 | plt.plot(x,f_X) 56 | 57 | 58 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 08- Fourier Transformation for Option Pricing/Materials/CallPut_COS_Method.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 27 2018 4 | Pricing of European Call and Put options wit the COS method 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import time 11 | 12 | def CallPutOptionPriceCOSMthd(cf,CP,S0,r,tau,K,N,L): 13 | # cf - characteristic function as a functon, in the book denoted as \varphi 14 | # CP - C for call and P for put 15 | # S0 - Initial stock price 16 | # r - interest rate (constant) 17 | # tau - time to maturity 18 | # K - list of strikes 19 | # N - Number of expansion terms 20 | # L - size of truncation domain (typ.:L=8 or L=10) 21 | 22 | # reshape K to a column vector 23 | K = np.array(K).reshape([len(K),1]) 24 | 25 | #assigning i=sqrt(-1) 26 | i = np.complex(0.0,1.0) 27 | 28 | x0 = np.log(S0 / K) 29 | 30 | # truncation domain 31 | a = 0.0 - L * np.sqrt(tau) 32 | b = 0.0 + L * np.sqrt(tau) 33 | 34 | # sumation from k = 0 to k=N-1 35 | k = np.linspace(0,N-1,N).reshape([N,1]) 36 | u = k * np.pi / (b - a); 37 | 38 | # Determine coefficients for Put Prices 39 | H_k = CallPutCoefficients(CP,a,b,k) 40 | 41 | mat = np.exp(i * np.outer((x0 - a) , u)) 42 | 43 | temp = cf(u) * H_k 44 | temp[0] = 0.5 * temp[0] 45 | 46 | value = np.exp(-r * tau) * K * np.real(mat.dot(temp)) 47 | 48 | return value 49 | 50 | """ 51 | Determine coefficients for Put Prices 52 | """ 53 | def CallPutCoefficients(CP,a,b,k): 54 | if str(CP).lower()=="c" or str(CP).lower()=="1": 55 | c = 0.0 56 | d = b 57 | coef = Chi_Psi(a,b,c,d,k) 58 | Chi_k = coef["chi"] 59 | Psi_k = coef["psi"] 60 | if a < b and b < 0.0: 61 | H_k = np.zeros([len(k),1]) 62 | else: 63 | H_k = 2.0 / (b - a) * (Chi_k - Psi_k) 64 | 65 | elif str(CP).lower()=="p" or str(CP).lower()=="-1": 66 | c = a 67 | d = 0.0 68 | coef = Chi_Psi(a,b,c,d,k) 69 | Chi_k = coef["chi"] 70 | Psi_k = coef["psi"] 71 | H_k = 2.0 / (b - a) * (- Chi_k + Psi_k) 72 | 73 | return H_k 74 | 75 | def Chi_Psi(a,b,c,d,k): 76 | psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a)) 77 | psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi) 78 | psi[0] = d - c 79 | 80 | chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) 81 | expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d) - np.cos(k * np.pi 82 | * (c - a) / (b - a)) * np.exp(c) 83 | expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * 84 | (d - a) / (b - a)) - k * np.pi / (b - a) * np.sin(k 85 | * np.pi * (c - a) / (b - a)) * np.exp(c) 86 | chi = chi * (expr1 + expr2) 87 | 88 | value = {"chi":chi,"psi":psi } 89 | return value 90 | 91 | 92 | def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r): 93 | #Black-Scholes Call option price 94 | cp = str(CP).lower() 95 | K = np.array(K).reshape([len(K),1]) 96 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 97 | * tau) / float(sigma * np.sqrt(tau)) 98 | d2 = d1 - sigma * np.sqrt(tau) 99 | if cp == "c" or cp == "1": 100 | value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau) 101 | elif cp == "p" or cp =="-1": 102 | value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0 103 | return value 104 | 105 | def mainCalculation(): 106 | i = np.complex(0.0,1.0) 107 | 108 | CP = "c" 109 | S0 = 100.0 110 | r = 0.1 111 | tau = 0.1 112 | sigma = 0.25 113 | K = [80.0, 90.0, 100.0, 110, 120.0] 114 | N = 4*32 115 | L = 10 116 | 117 | # Definition of the characteristic function for the GBM, this is an input 118 | # for the COS method 119 | # Note that Chf does not include coefficient "+iuX(t_0)" this coefficient 120 | # is included internally in the evaluation 121 | # In the book we denote this function as \varphi(u) 122 | 123 | cf = lambda u: np.exp((r - 0.5 * np.power(sigma,2.0)) * i * u * tau - 0.5 124 | * np.power(sigma, 2.0) * np.power(u, 2.0) * tau) 125 | 126 | # Timing results 127 | NoOfIterations = 100 128 | time_start = time.time() 129 | for k in range(0,NoOfIterations,1): 130 | val_COS = CallPutOptionPriceCOSMthd(cf,CP,S0,r,tau,K,N,L) 131 | time_stop = time.time() 132 | print("It took {0} seconds to price.".format((time_stop-time_start)/float(NoOfIterations))) 133 | 134 | # evaluate analytical Black Scholes equation 135 | val_Exact = BS_Call_Option_Price(CP,S0,K,sigma,tau,r) 136 | plt.plot(K,val_COS) 137 | plt.plot(K,val_Exact,'--') 138 | plt.xlabel("strike, K") 139 | plt.ylabel("Option Price") 140 | plt.legend(["COS Price","BS model"]) 141 | plt.grid() 142 | 143 | #Error comuputation 144 | error = [] 145 | for i in range(0,len(K)): 146 | error.append(np.abs(val_COS[i]-val_Exact[i])[0]) 147 | print("Abs error for strike {0} is equal to {1:.2E}".format(K[i],error[i])) 148 | 149 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 08- Fourier Transformation for Option Pricing/Materials/CashOrNothing_COS_Method.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Jan 16 2019 4 | Pricing of Cash-or-Nothing options with the COS method 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import time 11 | 12 | def CashOrNothingPriceCOSMthd(cf,CP,S0,r,tau,K,N,L): 13 | # cf - characteristic function as a functon, in the book denoted as \varphi 14 | # CP - C for call and P for put 15 | # S0 - Initial stock price 16 | # r - interest rate (constant) 17 | # tau - time to maturity 18 | # K - list of strikes 19 | # N - Number of expansion terms 20 | # L - size of truncation domain (typ.:L=8 or L=10) 21 | 22 | # reshape K to a column vector 23 | K = np.array(K).reshape([len(K),1]) 24 | 25 | #assigning i=sqrt(-1) 26 | i = np.complex(0.0,1.0) 27 | 28 | x0 = np.log(S0 / K) 29 | 30 | # truncation domain 31 | a = 0.0 - L * np.sqrt(tau) 32 | b = 0.0 + L * np.sqrt(tau) 33 | 34 | # sumation from k = 0 to k=N-1 35 | k = np.linspace(0,N-1,N).reshape([N,1]) 36 | u = k * np.pi / (b - a); 37 | 38 | # Determine coefficients for Put Prices 39 | H_k = CashOrNothingCoefficients(CP,a,b,k)#CallPutCoefficients(CP,a,b,k) 40 | 41 | mat = np.exp(i * np.outer((x0 - a) , u)) 42 | 43 | temp = cf(u) * H_k 44 | temp[0] = 0.5 * temp[0] 45 | 46 | value = np.exp(-r * tau) *K * np.real(mat.dot(temp)) 47 | 48 | return value 49 | 50 | """ 51 | Determine coefficients for CashOrNothing 52 | """ 53 | def CashOrNothingCoefficients(CP,a,b,k): 54 | if str(CP).lower()=="c" or str(CP).lower()=="1": 55 | c = 0.0 56 | d = b 57 | coef = Chi_Psi(a,b,c,d,k) 58 | Psi_k = coef["psi"] 59 | if a < b and b < 0.0: 60 | H_k = np.zeros([len(k),1]) 61 | else: 62 | H_k = 2.0 / (b - a) * (Psi_k) 63 | 64 | elif str(CP).lower()=="p" or str(CP).lower()=="-1": 65 | c = a 66 | d = 0.0 67 | coef = Chi_Psi(a,b,c,d,k) 68 | Psi_k = coef["psi"] 69 | H_k = 2.0 / (b - a) * (Psi_k) 70 | 71 | return H_k 72 | 73 | def Chi_Psi(a,b,c,d,k): 74 | psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a)) 75 | psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi) 76 | psi[0] = d - c 77 | 78 | chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) 79 | expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d) - np.cos(k * np.pi 80 | * (c - a) / (b - a)) * np.exp(c) 81 | expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * 82 | (d - a) / (b - a)) - k * np.pi / (b - a) * np.sin(k 83 | * np.pi * (c - a) / (b - a)) * np.exp(c) 84 | chi = chi * (expr1 + expr2) 85 | 86 | value = {"chi":chi,"psi":psi } 87 | return value 88 | 89 | 90 | def BS_Cash_Or_Nothing_Price(CP,S_0,K,sigma,tau,r): 91 | #Black-Scholes Call option price 92 | cp = str(CP).lower() 93 | K = np.array(K).reshape([len(K),1]) 94 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 95 | * tau) / float(sigma * np.sqrt(tau)) 96 | d2 = d1 - sigma * np.sqrt(tau) 97 | if cp == "c" or cp == "1": 98 | value = K * np.exp(-r * tau) * st.norm.cdf(d2) 99 | elif cp == "p" or cp =="-1": 100 | value = K * np.exp(-r * tau) *(1.0 - st.norm.cdf(d2)) 101 | return value 102 | 103 | def mainCalculation(): 104 | i = np.complex(0.0,1.0) 105 | 106 | CP = "p" 107 | S0 = 100.0 108 | r = 0.05 109 | tau = 0.1 110 | sigma = 0.2 111 | K = [120] #np.linspace(10,S0*2.0,50)#[120.0]# 112 | N = [40, 60, 80, 100, 120, 140] 113 | L = 6 114 | 115 | # Definition of the characteristic function for the GBM, this is an input 116 | # for the COS method 117 | # Note that Chf does not include coefficient "+iuX(t_0)" this coefficient 118 | # is included internally in the evaluation 119 | # In the book we denote this function as \varphi(u) 120 | cf = lambda u: np.exp((r - 0.5 * np.power(sigma,2.0)) * i * u * tau - 0.5 121 | * np.power(sigma, 2.0) * np.power(u, 2.0) * tau) 122 | 123 | val_COS_Exact = CashOrNothingPriceCOSMthd(cf,CP,S0,r,tau,K,np.power(2,14),L); 124 | print("Reference value is equal to ={0}".format(val_COS_Exact[0][0])) 125 | # Timing results 126 | NoOfIterations = 1000 127 | 128 | for n in N: 129 | time_start = time.time() 130 | for k in range(0,NoOfIterations): 131 | val_COS = CashOrNothingPriceCOSMthd(cf,CP,S0,r,tau,K,n,L)[0] 132 | print("For N={0} the error is ={1}".format(n,val_COS[0]-val_COS_Exact[0])) 133 | time_stop = time.time() 134 | print("It took {0} seconds to price.".format((time_stop-time_start)/float(NoOfIterations))) 135 | 136 | 137 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 08- Fourier Transformation for Option Pricing/Materials/DensityRecoveryFFT.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Normal density recovery using FFT 4 | """ 5 | import numpy as np 6 | import matplotlib.pyplot as plt 7 | import scipy.stats as st 8 | import scipy.fft as fft 9 | import scipy.interpolate as interpolate 10 | 11 | def RecoverDensity(cf,x,N = 8192): 12 | i = np.complex(0.0,1.0) #assigning i=sqrt(-1) 13 | 14 | # specification of the grid for u 15 | u_max = 20.0 16 | du = u_max / N 17 | u = np.linspace(0,N-1,N) * du 18 | 19 | 20 | # grid for x 21 | b = np.min(x) 22 | dx = 2.0*np.pi / (N*du) 23 | x_i = b + np.linspace(0,N-1,N) * dx 24 | 25 | phi = np.exp(-i*b*u) * cf(u) 26 | 27 | gamma_1 = np.exp(-i*x_i*u[0])*cf(u[0]) 28 | gamma_2 = np.exp(-i*x_i*u[-1])*cf(u[-1]) 29 | 30 | phi_boundary = 0.5 * (gamma_1 + gamma_2) 31 | 32 | f_xi = du/np.pi * np.real(fft.fft(phi)- phi_boundary) 33 | 34 | f_xiInterp = interpolate.interp1d(x_i,f_xi,kind='cubic') 35 | 36 | return f_xiInterp(x) 37 | 38 | def mainCalculation(): 39 | i = np.complex(0.0, 1.0) #assigning i=sqrt(-1) 40 | 41 | # setting for normal distribution 42 | mu = 0.0 43 | sigma = 1.0 44 | 45 | # Define characteristic function for the normal distribution 46 | cF = lambda u : np.exp(i * mu * u - 0.5 * sigma**2.0 * u**2.0); 47 | 48 | # define domain for density 49 | x = np.linspace(-8.0,8.0,100) 50 | f_XExact = st.norm.pdf(x,mu,sigma) 51 | 52 | # recovered density 53 | f_XR = RecoverDensity(cF,x,2**8) 54 | 55 | plt.figure(1) 56 | plt.grid() 57 | plt.xlabel("x") 58 | plt.ylabel("$f_X(x)$") 59 | plt.plot(x,f_XExact,'-r') 60 | plt.plot(x,f_XR,'--b') 61 | 62 | 63 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 09- Monte Carlo Simulation/Lecture 09- Monte Carlo Simulation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 09- Monte Carlo Simulation/Lecture 09- Monte Carlo Simulation.pdf -------------------------------------------------------------------------------- /Lecture 09- Monte Carlo Simulation/Materials/DeterministicFunction.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 27 2018 4 | Integrated Brownian motion 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def ComputeIntegral1(NoOfSamples,a,b,c,d,g): 11 | x_i = np.random.uniform(a,b,NoOfSamples) 12 | y_i = np.random.uniform(c,d,NoOfSamples) 13 | 14 | p_i = g(x_i) > y_i 15 | p = np.sum(p_i) / NoOfSamples 16 | 17 | integral = p * (b-a)*(d-c) 18 | 19 | plt.figure(1) 20 | plt.plot(x_i,y_i,'.r') 21 | plt.plot(x_i,g(x_i),'.b') 22 | return integral 23 | 24 | 25 | def ComputeIntegral2(NoOfSamples,a,b,g): 26 | x_i = np.random.uniform(a,b,NoOfSamples) 27 | 28 | p = (b-a)*np.mean(g(x_i)) 29 | 30 | return p 31 | 32 | NoOfSamples = 100000 33 | 34 | a = 0.0 35 | b = 1.0 36 | c = 0.0 37 | d = 3.0 38 | 39 | g = lambda x: np.exp(x) 40 | 41 | output = ComputeIntegral1(NoOfSamples,a,b,c,d,g) 42 | 43 | print('Integral from Monte Carlo 1 is {0}'.format(output)) 44 | 45 | output2 = ComputeIntegral2(NoOfSamples,a,b,g) 46 | 47 | print('Integral from Monte Carlo 2 is {0}'.format(output2)) 48 | print('Integral computed analytically = {0}'.format(np.exp(b)-np.exp(a))) 49 | -------------------------------------------------------------------------------- /Lecture 09- Monte Carlo Simulation/Materials/EulerConvergence_GBM.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Jan 20 2019 4 | Euler discretization of the GBM 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | 11 | def GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 12 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 13 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | 15 | # Approximation 16 | S1 = np.zeros([NoOfPaths, NoOfSteps+1]) 17 | S1[:,0] =S_0 18 | 19 | # Exact 20 | S2 = np.zeros([NoOfPaths, NoOfSteps+1]) 21 | S2[:,0] =S_0 22 | 23 | time = np.zeros([NoOfSteps+1]) 24 | 25 | dt = T / float(NoOfSteps) 26 | for i in range(0,NoOfSteps): 27 | # making sure that samples from normal have mean 0 and variance 1 28 | if NoOfPaths > 1: 29 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 30 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 31 | 32 | S1[:,i+1] = S1[:,i] + r * S1[:,i]* dt + sigma * S1[:,i] * (W[:,i+1] - W[:,i]) 33 | S2[:,i+1] = S2[:,i] * np.exp((r - 0.5*sigma**2.0) *dt + sigma * (W[:,i+1] - W[:,i])) 34 | time[i+1] = time[i] +dt 35 | 36 | # Retun S1 and S2 37 | paths = {"time":time,"S1":S1,"S2":S2} 38 | return paths 39 | 40 | def mainCalculation(): 41 | NoOfPaths = 25 42 | NoOfSteps = 25 43 | T = 1 44 | r = 0.06 45 | sigma = 0.3 46 | S_0 = 50 47 | 48 | # Simulated paths 49 | Paths = GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S_0) 50 | timeGrid = Paths["time"] 51 | S1 = Paths["S1"] 52 | S2 = Paths["S2"] 53 | 54 | plt.figure(1) 55 | plt.plot(timeGrid, np.transpose(S1),'k') 56 | plt.plot(timeGrid, np.transpose(S2),'--r') 57 | plt.grid() 58 | plt.xlabel("time") 59 | plt.ylabel("S(t)") 60 | 61 | # Weak and strong convergence 62 | NoOfStepsV = range(1,500,1) 63 | NoOfPaths = 250 64 | errorWeak = np.zeros([len(NoOfStepsV),1]) 65 | errorStrong = np.zeros([len(NoOfStepsV),1]) 66 | dtV = np.zeros([len(NoOfStepsV),1]) 67 | for idx, NoOfSteps in enumerate(NoOfStepsV): 68 | Paths = GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S_0) 69 | # Get the paths at T 70 | S1_atT = Paths["S1"][:,-1] 71 | S2_atT = Paths["S2"][:,-1] 72 | 73 | errorWeak[idx] = np.abs(np.mean(S1_atT)-np.mean(S2_atT)) 74 | 75 | errorStrong[idx] = np.mean(np.abs(S1_atT-S2_atT)) 76 | dtV[idx] = T/NoOfSteps 77 | 78 | print(errorStrong) 79 | plt.figure(2) 80 | plt.plot(dtV,errorWeak) 81 | plt.plot(dtV,errorStrong,'--r') 82 | plt.grid() 83 | plt.legend(['weak conv.','strong conv.']) 84 | 85 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 09- Monte Carlo Simulation/Materials/Exercise_1.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 27 2018 4 | Integrated Brownian motion 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def ComputeIntegrals(NoOfPaths,NoOfSteps,T,g): 11 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 12 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 13 | I1 = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | time = np.zeros([NoOfSteps+1]) 15 | 16 | dt = T / float(NoOfSteps) 17 | for i in range(0,NoOfSteps): 18 | # making sure that samples from normal have mean 0 and variance 1 19 | if NoOfPaths > 1: 20 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 21 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 22 | 23 | I1[:,i+1] = I1[:,i] + g(W[:,i]) *(W[:,i+1]-W[:,i]) 24 | time[i+1] = time[i] +dt 25 | 26 | paths = {"time":time,"W":W,"I1":I1} 27 | return paths 28 | 29 | 30 | NoOfPaths = 100000 31 | NoOfSteps = 1000 32 | T = 2 33 | 34 | g = lambda t: t 35 | 36 | output = ComputeIntegrals(NoOfPaths,NoOfSteps,T , g) 37 | timeGrid = output["time"] 38 | G_T = output["I1"] 39 | 40 | plt.figure(1) 41 | plt.grid() 42 | plt.hist(G_T[:,-1],50) 43 | plt.xlabel("time") 44 | plt.ylabel("value") 45 | plt.title("Stochastic Integral") 46 | 47 | EX = np.mean(G_T[:,-1]) 48 | Var = np.var(G_T[:,-1]) 49 | print('Mean = {0} and variance ={1}'.format(EX,Var)) 50 | 51 | -------------------------------------------------------------------------------- /Lecture 09- Monte Carlo Simulation/Materials/Exercise_2.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 27 2018 4 | Integrated Brownian motion 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def ComputeIntegrals(NoOfPaths,NoOfSteps,T,g): 11 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 12 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 13 | I1 = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | time = np.zeros([NoOfSteps+1]) 15 | 16 | dt = T / float(NoOfSteps) 17 | for i in range(0,NoOfSteps): 18 | # making sure that samples from normal have mean 0 and variance 1 19 | if NoOfPaths > 1: 20 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 21 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 22 | 23 | I1[:,i+1] = I1[:,i] + g(W[:,i]) * (W[:,i+1]-W[:,i]) 24 | time[i+1] = time[i] +dt 25 | 26 | paths = {"time":time,"W":W,"I1":I1} 27 | return paths 28 | 29 | NoOfPaths = 10000 30 | NoOfSteps = 1000 31 | T = 2 32 | 33 | g = lambda t: t 34 | 35 | output = ComputeIntegrals(NoOfPaths,NoOfSteps,T , g) 36 | timeGrid = output["time"] 37 | G_T = output["I1"] 38 | 39 | plt.figure(1) 40 | plt.grid() 41 | plt.hist(G_T[:,-1],50) 42 | plt.xlabel("time") 43 | plt.ylabel("value") 44 | plt.title("Stochastic Integral") 45 | 46 | EX = np.mean(G_T[:,-1]) 47 | Var = np.var(G_T[:,-1]) 48 | print('Mean = {0} and variance ={1}'.format(EX,Var)) 49 | 50 | -------------------------------------------------------------------------------- /Lecture 09- Monte Carlo Simulation/Materials/MilsteinConvergence_GBM.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Jan 20 2019 4 | Milstein discretization of the GBM 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | 11 | def GeneratePathsGBMMilstein(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 12 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 13 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | 15 | # Approximation 16 | S1 = np.zeros([NoOfPaths, NoOfSteps+1]) 17 | S1[:,0] =S_0 18 | 19 | # Exact 20 | S2 = np.zeros([NoOfPaths, NoOfSteps+1]) 21 | S2[:,0] =S_0 22 | 23 | time = np.zeros([NoOfSteps+1]) 24 | 25 | dt = T / float(NoOfSteps) 26 | for i in range(0,NoOfSteps): 27 | # making sure that samples from normal have mean 0 and variance 1 28 | if NoOfPaths > 1: 29 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 30 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 31 | 32 | S1[:,i+1] = S1[:,i] + r * S1[:,i]* dt + sigma * S1[:,i] * (W[:,i+1] - W[:,i]) \ 33 | + 0.5 * sigma**2 * S1[:,i] * (np.power((W[:,i+1] - W[:,i]),2) - dt) 34 | 35 | S2[:,i+1] = S2[:,i] * np.exp((r - 0.5*sigma*sigma) *dt + sigma * (W[:,i+1] - W[:,i])) 36 | time[i+1] = time[i] +dt 37 | 38 | # Retun S1 and S2 39 | paths = {"time":time,"S1":S1,"S2":S2} 40 | return paths 41 | 42 | def mainCalculation(): 43 | NoOfPaths = 25 44 | NoOfSteps = 25 45 | T = 1 46 | r = 0.06 47 | sigma = 0.3 48 | S_0 = 50 49 | 50 | # Simulated paths 51 | Paths = GeneratePathsGBMMilstein(NoOfPaths,NoOfSteps,T,r,sigma,S_0) 52 | timeGrid = Paths["time"] 53 | S1 = Paths["S1"] 54 | S2 = Paths["S2"] 55 | 56 | plt.figure(1) 57 | plt.plot(timeGrid, np.transpose(S1),'k') 58 | plt.plot(timeGrid, np.transpose(S2),'--r') 59 | plt.grid() 60 | plt.xlabel("time") 61 | plt.ylabel("S(t)") 62 | 63 | # Weak and strong convergence 64 | NoOfStepsV = range(1,500,1) 65 | NoOfPaths = 100 66 | errorWeak = np.zeros([len(NoOfStepsV),1]) 67 | errorStrong = np.zeros([len(NoOfStepsV),1]) 68 | dtV = np.zeros([len(NoOfStepsV),1]) 69 | for idx, NoOfSteps in enumerate(NoOfStepsV): 70 | Paths = GeneratePathsGBMMilstein(NoOfPaths,NoOfSteps,T,r,sigma,S_0) 71 | # Get the paths at T 72 | S1_atT = Paths["S1"][:,-1] 73 | S2_atT = Paths["S2"][:,-1] 74 | errorWeak[idx] = np.abs(np.mean(S1_atT)-np.mean(S2_atT)) 75 | errorStrong[idx] = np.mean(np.abs(S1_atT-S2_atT)) 76 | dtV[idx] = T/NoOfSteps 77 | 78 | print(errorStrong) 79 | plt.figure(2) 80 | plt.plot(dtV,errorWeak) 81 | plt.plot(dtV,errorStrong,'--r') 82 | plt.grid() 83 | plt.legend(['weak conv.','strong conv.']) 84 | 85 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 09- Monte Carlo Simulation/Materials/StochasticIntegrals.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 27 2018 4 | Integrated Brownian motion- three cases, W(t), and I(t) 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def ComputeIntegrals(NoOfPaths,NoOfSteps,T): 11 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 12 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 13 | I1 = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | I2 = np.zeros([NoOfPaths, NoOfSteps+1]) 15 | time = np.zeros([NoOfSteps+1]) 16 | 17 | dt = T / float(NoOfSteps) 18 | for i in range(0,NoOfSteps): 19 | # making sure that samples from normal have mean 0 and variance 1 20 | if NoOfPaths > 1: 21 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 22 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 23 | I1[:,i+1] = I1[:,i] + W[:,i]*dt 24 | I2[:,i+1] = I2[:,i] + W[:,i]*(W[:,i+1]-W[:,i]) 25 | time[i+1] = time[i] +dt 26 | 27 | paths = {"time":time,"W":W,"I1":I1,"I2":I2} 28 | return paths 29 | 30 | NoOfPaths = 1 31 | NoOfSteps = 1000 32 | T = 1 33 | 34 | W_t = ComputeIntegrals(NoOfPaths,NoOfSteps,1) 35 | timeGrid = W_t["time"] 36 | Ws = W_t["W"] 37 | intWsds = W_t["I1"] 38 | intWsdWs = W_t["I2"] 39 | 40 | plt.figure(1) 41 | plt.plot(timeGrid, np.transpose(Ws)) 42 | plt.plot(timeGrid, np.transpose(intWsds),'r') 43 | plt.plot(timeGrid, np.transpose(intWsdWs),'k') 44 | plt.grid() 45 | plt.xlabel("time") 46 | plt.ylabel("value") 47 | plt.title("Integrated Brownian Motion paths") 48 | -------------------------------------------------------------------------------- /Lecture 09- Monte Carlo Simulation/Materials/dWdW.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Jan 16 2019 4 | Convergence of E(W(t_i+1)-W(t_i))^2 and Var(W(t_i+1)-W(t_i))^2 with respect to 5 | the number of the discretization intervals 6 | @author: Lech A. Grzelak 7 | """ 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | 11 | def mainCalculation(): 12 | NoOfPaths = 50000 13 | 14 | mV = [] 15 | vV = [] 16 | T=1.0 17 | for m in range(2,60,1): 18 | t1 = 1.0*T/m; 19 | t2 = 2.0*T/m; 20 | W_t1 = np.sqrt(t1)* np.random.normal(0.0,1.0,[NoOfPaths,1]) 21 | W_t2 = W_t1 + np.sqrt(t2-t1)* np.random.normal(0.0,1.0,[NoOfPaths,1]) 22 | X = np.power(W_t2-W_t1,2.0); 23 | mV.append(np.mean(X)) 24 | vV.append(np.var(X)) 25 | 26 | plt.figure(1) 27 | plt.plot(range(2,60,1), mV) 28 | plt.plot(range(2,60,1), vV,'--r') 29 | plt.grid() 30 | plt.legend(['E(W(t_i+1)-W(t_i))^2','Var(W(t_i+1)-W(t_i))^2']) 31 | 32 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 10- Monte Carlo Simulation of the Heston Model/Lecture 10- Monte Carlo Simulation of the Heston Model.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 10- Monte Carlo Simulation of the Heston Model/Lecture 10- Monte Carlo Simulation of the Heston Model.pdf -------------------------------------------------------------------------------- /Lecture 10- Monte Carlo Simulation of the Heston Model/Materials/CIR_ExactSimulation.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Feb 11 2019 4 | The Heston model discretization, Euler scheme vs. AES scheme 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import enum 11 | 12 | def CIR_Sample(NoOfPaths,kappa,gamma,vbar,s,t,v_s): 13 | delta = 4.0 *kappa*vbar/gamma/gamma 14 | c= 1.0/(4.0*kappa)*gamma*gamma*(1.0-np.exp(-kappa*(t-s))) 15 | kappaBar = 4.0*kappa*v_s*np.exp(-kappa*(t-s))/(gamma*gamma*(1.0-np.exp(-kappa*(t-s)))) 16 | sample = c* np.random.noncentral_chisquare(delta,kappaBar,NoOfPaths) 17 | return sample 18 | 19 | def GeneratePathsHestonAES(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0): 20 | Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 21 | W1 = np.zeros([NoOfPaths, NoOfSteps+1]) 22 | V = np.zeros([NoOfPaths, NoOfSteps+1]) 23 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 24 | V[:,0]=v0 25 | X[:,0]=np.log(S_0) 26 | 27 | time = np.zeros([NoOfSteps+1]) 28 | 29 | dt = T / float(NoOfSteps) 30 | for i in range(0,NoOfSteps): 31 | # making sure that samples from normal have mean 0 and variance 1 32 | if NoOfPaths > 1: 33 | Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i]) 34 | W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i] 35 | 36 | # Exact samles for the variance process 37 | V[:,i+1] = CIR_Sample(NoOfPaths,kappa,gamma,vbar,0,dt,V[:,i]) 38 | k0 = (r -rho/gamma*kappa*vbar)*dt 39 | k1 = (rho*kappa/gamma -0.5)*dt - rho/gamma 40 | k2 = rho / gamma 41 | X[:,i+1] = X[:,i] + k0 + k1*V[:,i] + k2 *V[:,i+1] + np.sqrt((1.0-rho**2)*V[:,i])*(W1[:,i+1]-W1[:,i]) 42 | time[i+1] = time[i] +dt 43 | 44 | #Compute exponent 45 | S = np.exp(X) 46 | paths = {"time":time,"S":S} 47 | return paths 48 | 49 | 50 | def mainCalculation(): 51 | NoOfPaths = 1000 52 | NoOfSteps = 500 53 | 54 | # Heston model parameters 55 | gamma = 1.0 56 | kappa = 0.5 57 | vbar = 0.04 58 | rho = -0.9 59 | v0 = 0.04 60 | T = 1.0 61 | S_0 = 100.0 62 | r = 0.1 63 | CP = OptionType.CALL 64 | 65 | # First we define a range of strikes and check the convergence 66 | K = np.linspace(0.1,S_0*2.0,30) 67 | 68 | # Exact solution with the COS method 69 | cf = ChFHestonModel(r,T,kappa,gamma,vbar,v0,rho) 70 | 71 | # The COS method 72 | optValueExact = CallPutOptionPriceCOSMthd(cf, CP, S_0, r, T, K, 1000, 8) 73 | 74 | # Euler simulation 75 | pathsEULER = GeneratePathsHestonEuler(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0) 76 | S_Euler = pathsEULER["S"] 77 | 78 | # Almost exact simulation 79 | pathsAES = GeneratePathsHestonAES(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0) 80 | S_AES = pathsAES["S"] 81 | 82 | 83 | OptPrice_EULER = EUOptionPriceFromMCPathsGeneralized(CP,S_Euler[:,-1],K,T,r) 84 | OptPrice_AES = EUOptionPriceFromMCPathsGeneralized(CP,S_AES[:,-1],K,T,r) 85 | 86 | plt.figure(1) 87 | plt.plot(K,optValueExact,'-r') 88 | plt.plot(K,OptPrice_EULER,'--k') 89 | plt.plot(K,OptPrice_AES,'.b') 90 | plt.legend(['Exact (COS)','Euler','AES']) 91 | plt.grid() 92 | plt.xlabel('strike, K') 93 | plt.ylabel('option price') 94 | 95 | # Here we will analyze the convergence for particular dt 96 | dtV = np.array([1.0, 1.0/4.0, 1.0/8.0,1.0/16.0,1.0/32.0,1.0/64.0]) 97 | NoOfStepsV = [int(T/x) for x in dtV] 98 | 99 | # Specify strike for analysis 100 | K = np.array([100.0]) 101 | 102 | # Exact 103 | optValueExact = CallPutOptionPriceCOSMthd(cf, CP, S_0, r, T, K, 1000, 8) 104 | errorEuler = np.zeros([len(dtV),1]) 105 | errorAES = np.zeros([len(dtV),1]) 106 | 107 | for (idx,NoOfSteps) in enumerate(NoOfStepsV): 108 | # Euler 109 | np.random.seed(3) 110 | pathsEULER = GeneratePathsHestonEuler(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0) 111 | S_Euler = pathsEULER["S"] 112 | OptPriceEULER = EUOptionPriceFromMCPathsGeneralized(CP,S_Euler[:,-1],K,T,r) 113 | errorEuler[idx] = OptPriceEULER-optValueExact 114 | # AES 115 | np.random.seed(3) 116 | pathsAES = GeneratePathsHestonAES(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0) 117 | S_AES = pathsAES["S"] 118 | OptPriceAES = EUOptionPriceFromMCPathsGeneralized(CP,S_AES[:,-1],K,T,r) 119 | errorAES[idx] = OptPriceAES-optValueExact 120 | 121 | # Print the results 122 | for i in range(0,len(NoOfStepsV)): 123 | print("Euler Scheme, K ={0}, dt = {1} = {2}".format(K,dtV[i],errorEuler[i])) 124 | 125 | for i in range(0,len(NoOfStepsV)): 126 | print("AES Scheme, K ={0}, dt = {1} = {2}".format(K,dtV[i],errorAES[i])) 127 | 128 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 10- Monte Carlo Simulation of the Heston Model/Materials/CIR_paths_Exact.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Jan 20 2019 4 | Paths for the CIR process- two different boundary conditions considered 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def CIR_Sample(NoOfPaths,kappa,gamma,vbar,s,t,v_s): 11 | delta = 4.0 *kappa*vbar/gamma/gamma 12 | c= 1.0/(4.0*kappa)*gamma*gamma*(1.0-np.exp(-kappa*(t-s))) 13 | kappaBar = 4.0*kappa*v_s*np.exp(-kappa*(t-s))/(gamma*gamma*(1.0-np.exp(-kappa*(t-s)))) 14 | sample = c* np.random.noncentral_chisquare(delta,kappaBar,NoOfPaths) 15 | return sample 16 | 17 | def GeneratePathsCIRExact(NoOfPaths,NoOfSteps,T,kappa,v0,vbar,gamma): 18 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 19 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 20 | V = np.zeros([NoOfPaths, NoOfSteps+1]) 21 | V[:,0]=v0 22 | 23 | time = np.zeros([NoOfSteps+1]) 24 | 25 | dt = T / float(NoOfSteps) 26 | for i in range(0,NoOfSteps): 27 | # making sure that samples from normal have mean 0 and variance 1 28 | if NoOfPaths > 1: 29 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 30 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 31 | 32 | # Exact simualtion 33 | V[:,i+1] = CIR_Sample(NoOfPaths,kappa,gamma,vbar,0,dt,V[:,i]) 34 | 35 | time[i+1] = time[i] +dt 36 | 37 | # Outputs 38 | paths = {"time":time,"VExact":V} 39 | return paths 40 | 41 | def mainCalculation(): 42 | NoOfPaths = 1 43 | NoOfSteps = 2000 44 | T = 1 45 | kappa =0.7 46 | v0 =0.1 47 | vbar =0.1 48 | gamma =0.7 49 | 50 | np.random.seed(10) 51 | Paths = GeneratePathsCIRExact(NoOfPaths,NoOfSteps,T,kappa,v0,vbar,gamma) 52 | timeGrid = Paths["time"] 53 | V_exact = Paths["VExact"] 54 | 55 | plt.figure(1) 56 | plt.plot(timeGrid, np.transpose(V_exact),'b') 57 | plt.xlabel("time") 58 | plt.ylabel("V(t)") 59 | plt.legend(['exact']) 60 | 61 | mainCalculation() 62 | 63 | -------------------------------------------------------------------------------- /Lecture 10- Monte Carlo Simulation of the Heston Model/Materials/CIR_paths_boundary.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Jan 20 2019 4 | Paths for the CIR process- two different boundary conditions considered 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | def GeneratePathsCIREuler2Schemes(NoOfPaths,NoOfSteps,T,kappa,v0,vbar,gamma): 11 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 12 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 13 | V1 = np.zeros([NoOfPaths, NoOfSteps+1]) 14 | V2 = np.zeros([NoOfPaths, NoOfSteps+1]) 15 | V1[:,0]=v0 16 | V2[:,0]=v0 17 | time = np.zeros([NoOfSteps+1]) 18 | 19 | dt = T / float(NoOfSteps) 20 | for i in range(0,NoOfSteps): 21 | # making sure that samples from normal have mean 0 and variance 1 22 | if NoOfPaths > 1: 23 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 24 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 25 | # Truncated boundary condition 26 | V1[:,i+1] = V1[:,i] + kappa*(vbar - V1[:,i]) * dt + gamma* np.sqrt(V1[:,i]) * (W[:,i+1]-W[:,i]) 27 | V1[:,i+1] = np.maximum(V1[:,i+1],0.0) 28 | 29 | # Reflecting boundary condition 30 | V2[:,i+1] = V2[:,i] + kappa*(vbar - V2[:,i]) * dt + gamma* np.sqrt(V2[:,i]) * (W[:,i+1]-W[:,i]) 31 | V2[:,i+1] = np.absolute(V2[:,i+1]) 32 | time[i+1] = time[i] +dt 33 | 34 | # Outputs 35 | paths = {"time":time,"Vtruncated":V1,"Vreflected":V2} 36 | return paths 37 | 38 | def mainCalculation(): 39 | NoOfPaths = 1 40 | NoOfSteps = 20 41 | T = 1 42 | kappa =0.5 43 | v0 =0.1 44 | vbar =0.1 45 | gamma =0.8 46 | np.random.seed(210) 47 | Paths = GeneratePathsCIREuler2Schemes(NoOfPaths,NoOfSteps,T,kappa,v0,vbar,gamma) 48 | timeGrid = Paths["time"] 49 | V_truncated = Paths["Vtruncated"] 50 | V_reflected = Paths["Vreflected"] 51 | 52 | plt.figure(1) 53 | plt.plot(timeGrid, np.transpose(V_truncated),'b') 54 | plt.plot(timeGrid, np.transpose(V_reflected),'--r') 55 | plt.grid() 56 | plt.xlabel("time") 57 | plt.ylabel("V(t)") 58 | plt.legend(['truncated scheme','reflecting scheme']) 59 | 60 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 10- Monte Carlo Simulation of the Heston Model/Materials/HestonModelDiscretization.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Feb 11 2019 4 | The Heston model discretization, Euler scheme vs. AES scheme 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import enum 11 | 12 | # This class defines puts and calls 13 | class OptionType(enum.Enum): 14 | CALL = 1.0 15 | PUT = -1.0 16 | 17 | def CallPutOptionPriceCOSMthd(cf,CP,S0,r,tau,K,N,L): 18 | # cf - characteristic function as a functon, in the book denoted as \varphi 19 | # CP - C for call and P for put 20 | # S0 - Initial stock price 21 | # r - interest rate (constant) 22 | # tau - time to maturity 23 | # K - list of strikes 24 | # N - Number of expansion terms 25 | # L - size of truncation domain (typ.:L=8 or L=10) 26 | 27 | # reshape K to a column vector 28 | if K is not np.array: 29 | K = np.array(K).reshape([len(K),1]) 30 | 31 | #assigning i=sqrt(-1) 32 | i = np.complex(0.0,1.0) 33 | x0 = np.log(S0 / K) 34 | 35 | # truncation domain 36 | a = 0.0 - L * np.sqrt(tau) 37 | b = 0.0 + L * np.sqrt(tau) 38 | 39 | # sumation from k = 0 to k=N-1 40 | k = np.linspace(0,N-1,N).reshape([N,1]) 41 | u = k * np.pi / (b - a); 42 | 43 | # Determine coefficients for Put Prices 44 | H_k = CallPutCoefficients(CP,a,b,k) 45 | mat = np.exp(i * np.outer((x0 - a) , u)) 46 | temp = cf(u) * H_k 47 | temp[0] = 0.5 * temp[0] 48 | value = np.exp(-r * tau) * K * np.real(mat.dot(temp)) 49 | return value 50 | 51 | def Chi_Psi(a,b,c,d,k): 52 | psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a)) 53 | psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi) 54 | psi[0] = d - c 55 | 56 | chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) 57 | expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d) - np.cos(k * np.pi 58 | * (c - a) / (b - a)) * np.exp(c) 59 | expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * 60 | (d - a) / (b - a)) - k * np.pi / (b - a) * np.sin(k 61 | * np.pi * (c - a) / (b - a)) * np.exp(c) 62 | chi = chi * (expr1 + expr2) 63 | 64 | value = {"chi":chi,"psi":psi } 65 | return value 66 | 67 | # Determine coefficients for Put Prices 68 | def CallPutCoefficients(CP,a,b,k): 69 | if CP==OptionType.CALL: 70 | c = 0.0 71 | d = b 72 | coef = Chi_Psi(a,b,c,d,k) 73 | Chi_k = coef["chi"] 74 | Psi_k = coef["psi"] 75 | if a < b and b < 0.0: 76 | H_k = np.zeros([len(k),1]) 77 | else: 78 | H_k = 2.0 / (b - a) * (Chi_k - Psi_k) 79 | elif CP==OptionType.PUT: 80 | c = a 81 | d = 0.0 82 | coef = Chi_Psi(a,b,c,d,k) 83 | Chi_k = coef["chi"] 84 | Psi_k = coef["psi"] 85 | H_k = 2.0 / (b - a) * (- Chi_k + Psi_k) 86 | return H_k 87 | 88 | def ChFHestonModel(r,tau,kappa,gamma,vbar,v0,rho): 89 | i = np.complex(0.0,1.0) 90 | D1 = lambda u: np.sqrt(np.power(kappa-gamma*rho*i*u,2)+(u*u+i*u)*gamma*gamma) 91 | g = lambda u: (kappa-gamma*rho*i*u-D1(u))/(kappa-gamma*rho*i*u+D1(u)) 92 | C = lambda u: (1.0-np.exp(-D1(u)*tau))/(gamma*gamma*(1.0-g(u)*np.exp(-D1(u)*tau)))\ 93 | *(kappa-gamma*rho*i*u-D1(u)) 94 | # Note that we exclude the term -r*tau, as the discounting is performed in the COS method 95 | A = lambda u: r * i*u *tau + kappa*vbar*tau/gamma/gamma *(kappa-gamma*rho*i*u-D1(u))\ 96 | - 2*kappa*vbar/gamma/gamma*np.log((1.0-g(u)*np.exp(-D1(u)*tau))/(1.0-g(u))) 97 | # Characteristic function for the Heston's model 98 | cf = lambda u: np.exp(A(u) + C(u)*v0) 99 | return cf 100 | 101 | def EUOptionPriceFromMCPathsGeneralized(CP,S,K,T,r): 102 | # S is a vector of Monte Carlo samples at T 103 | result = np.zeros([len(K),1]) 104 | if CP == OptionType.CALL: 105 | for (idx,k) in enumerate(K): 106 | result[idx] = np.exp(-r*T)*np.mean(np.maximum(S-k,0.0)) 107 | elif CP == OptionType.PUT: 108 | for (idx,k) in enumerate(K): 109 | result[idx] = np.exp(-r*T)*np.mean(np.maximum(k-S,0.0)) 110 | return result 111 | 112 | def GeneratePathsHestonEuler(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0): 113 | Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 114 | Z2 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 115 | W1 = np.zeros([NoOfPaths, NoOfSteps+1]) 116 | W2 = np.zeros([NoOfPaths, NoOfSteps+1]) 117 | V = np.zeros([NoOfPaths, NoOfSteps+1]) 118 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 119 | V[:,0]=v0 120 | X[:,0]=np.log(S_0) 121 | 122 | time = np.zeros([NoOfSteps+1]) 123 | 124 | dt = T / float(NoOfSteps) 125 | for i in range(0,NoOfSteps): 126 | # making sure that samples from normal have mean 0 and variance 1 127 | if NoOfPaths > 1: 128 | Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i]) 129 | Z2[:,i] = (Z2[:,i] - np.mean(Z2[:,i])) / np.std(Z2[:,i]) 130 | Z2[:,i] = rho * Z1[:,i] + np.sqrt(1.0-rho**2)*Z2[:,i] 131 | 132 | W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i] 133 | W2[:,i+1] = W2[:,i] + np.power(dt, 0.5)*Z2[:,i] 134 | 135 | # Truncated boundary condition 136 | V[:,i+1] = V[:,i] + kappa*(vbar - V[:,i]) * dt + gamma* np.sqrt(V[:,i]) * (W1[:,i+1]-W1[:,i]) 137 | V[:,i+1] = np.maximum(V[:,i+1],0.0) 138 | 139 | X[:,i+1] = X[:,i] + (r - 0.5*V[:,i])*dt + np.sqrt(V[:,i])*(W2[:,i+1]-W2[:,i]) 140 | time[i+1] = time[i] +dt 141 | 142 | #Compute exponent 143 | S = np.exp(X) 144 | paths = {"time":time,"S":S} 145 | return paths 146 | 147 | def CIR_Sample(NoOfPaths,kappa,gamma,vbar,s,t,v_s): 148 | delta = 4.0 *kappa*vbar/gamma/gamma 149 | c= 1.0/(4.0*kappa)*gamma*gamma*(1.0-np.exp(-kappa*(t-s))) 150 | kappaBar = 4.0*kappa*v_s*np.exp(-kappa*(t-s))/(gamma*gamma*(1.0-np.exp(-kappa*(t-s)))) 151 | sample = c* np.random.noncentral_chisquare(delta,kappaBar,NoOfPaths) 152 | return sample 153 | 154 | def GeneratePathsHestonAES(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0): 155 | Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 156 | W1 = np.zeros([NoOfPaths, NoOfSteps+1]) 157 | V = np.zeros([NoOfPaths, NoOfSteps+1]) 158 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 159 | V[:,0]=v0 160 | X[:,0]=np.log(S_0) 161 | 162 | time = np.zeros([NoOfSteps+1]) 163 | 164 | dt = T / float(NoOfSteps) 165 | for i in range(0,NoOfSteps): 166 | # making sure that samples from normal have mean 0 and variance 1 167 | if NoOfPaths > 1: 168 | Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i]) 169 | W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i] 170 | 171 | # Exact samles for the variance process 172 | V[:,i+1] = CIR_Sample(NoOfPaths,kappa,gamma,vbar,0,dt,V[:,i]) 173 | k0 = (r -rho/gamma*kappa*vbar)*dt 174 | k1 = (rho*kappa/gamma -0.5)*dt - rho/gamma 175 | k2 = rho / gamma 176 | X[:,i+1] = X[:,i] + k0 + k1*V[:,i] + k2 *V[:,i+1] + np.sqrt((1.0-rho**2)*V[:,i])*(W1[:,i+1]-W1[:,i]) 177 | time[i+1] = time[i] +dt 178 | 179 | #Compute exponent 180 | S = np.exp(X) 181 | paths = {"time":time,"S":S} 182 | return paths 183 | 184 | # Black-Scholes Call option price 185 | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,t,T,r): 186 | #print('Maturity T={0} and t={1}'.format(T,t)) 187 | #print(float(sigma * np.sqrt(T-t))) 188 | #print('strike K ={0}'.format(K)) 189 | K = np.array(K).reshape([len(K),1]) 190 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 191 | * (T-t)) / (sigma * np.sqrt(T-t)) 192 | d2 = d1 - sigma * np.sqrt(T-t) 193 | if CP == OptionType.CALL: 194 | value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * (T-t)) 195 | # print(value) 196 | elif CP == OptionType.PUT: 197 | value = st.norm.cdf(-d2) * K * np.exp(-r * (T-t)) - st.norm.cdf(-d1)*S_0 198 | # print(value) 199 | return value 200 | 201 | def mainCalculation(): 202 | NoOfPaths = 2500 203 | NoOfSteps = 500 204 | 205 | # Heston model parameters 206 | gamma = 1.0 207 | kappa = 0.5 208 | vbar = 0.04 209 | rho = -0.9 210 | v0 = 0.04 211 | T = 1.0 212 | S_0 = 100.0 213 | r = 0.1 214 | CP = OptionType.CALL 215 | 216 | # First we define a range of strikes and check the convergence 217 | K = np.linspace(80,S_0*1.5,30) 218 | 219 | # Exact solution with the COS method 220 | cf = ChFHestonModel(r,T,kappa,gamma,vbar,v0,rho) 221 | 222 | # The COS method 223 | optValueExact = CallPutOptionPriceCOSMthd(cf, CP, S_0, r, T, K, 1000, 8) 224 | 225 | # Euler simulation 226 | pathsEULER = GeneratePathsHestonEuler(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0) 227 | S_Euler = pathsEULER["S"] 228 | 229 | # Almost exact simulation 230 | pathsAES = GeneratePathsHestonAES(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0) 231 | S_AES = pathsAES["S"] 232 | 233 | 234 | OptPrice_EULER = EUOptionPriceFromMCPathsGeneralized(CP,S_Euler[:,-1],K,T,r) 235 | OptPrice_AES = EUOptionPriceFromMCPathsGeneralized(CP,S_AES[:,-1],K,T,r) 236 | 237 | plt.figure(1) 238 | plt.plot(K,optValueExact,'-r') 239 | plt.plot(K,OptPrice_EULER,'--k') 240 | plt.plot(K,OptPrice_AES,'.b') 241 | plt.legend(['Exact (COS)','Euler','AES']) 242 | plt.grid() 243 | plt.xlabel('strike, K') 244 | plt.ylabel('option price') 245 | 246 | # Here we will analyze the convergence for particular dt 247 | dtV = np.array([1.0, 1.0/4.0, 1.0/8.0,1.0/16.0,1.0/32.0,1.0/64.0]) 248 | NoOfStepsV = [int(T/x) for x in dtV] 249 | 250 | # Specify strike for analysis 251 | K = np.array([140.0]) 252 | 253 | # Exact 254 | optValueExact = CallPutOptionPriceCOSMthd(cf, CP, S_0, r, T, K, 1000, 8) 255 | errorEuler = np.zeros([len(dtV),1]) 256 | errorAES = np.zeros([len(dtV),1]) 257 | 258 | for (idx,NoOfSteps) in enumerate(NoOfStepsV): 259 | # Euler 260 | np.random.seed(3) 261 | pathsEULER = GeneratePathsHestonEuler(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0) 262 | S_Euler = pathsEULER["S"] 263 | OptPriceEULER = EUOptionPriceFromMCPathsGeneralized(CP,S_Euler[:,-1],K,T,r) 264 | errorEuler[idx] = OptPriceEULER-optValueExact 265 | # AES 266 | np.random.seed(3) 267 | pathsAES = GeneratePathsHestonAES(NoOfPaths,NoOfSteps,T,r,S_0,kappa,gamma,rho,vbar,v0) 268 | S_AES = pathsAES["S"] 269 | OptPriceAES = EUOptionPriceFromMCPathsGeneralized(CP,S_AES[:,-1],K,T,r) 270 | errorAES[idx] = OptPriceAES-optValueExact 271 | 272 | # Print the results 273 | for i in range(0,len(NoOfStepsV)): 274 | print("Euler Scheme, K ={0}, dt = {1} = {2}".format(K,dtV[i],errorEuler[i])) 275 | 276 | for i in range(0,len(NoOfStepsV)): 277 | print("AES Scheme, K ={0}, dt = {1} = {2}".format(K,dtV[i],errorAES[i])) 278 | 279 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 10- Monte Carlo Simulation of the Heston Model/Materials/OptionPrices_EulerAndMilstein.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Jan 20 2019 4 | Convergence of option prices for Euler and Milsten schemes 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import scipy.stats as st 9 | import enum 10 | 11 | # set i= imaginary number 12 | i = np.complex(0.0,1.0) 13 | 14 | # This class defines puts and calls 15 | class OptionType(enum.Enum): 16 | CALL = 1.0 17 | PUT = -1.0 18 | 19 | # Black-Scholes Call option price 20 | def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r): 21 | 22 | K = np.array(K).reshape([len(K),1]) 23 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 24 | * tau) / float(sigma * np.sqrt(tau)) 25 | d2 = d1 - sigma * np.sqrt(tau) 26 | if CP == OptionType.CALL: 27 | value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau) 28 | elif CP == OptionType.PUT: 29 | value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0 30 | return value 31 | 32 | def GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 33 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 34 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 35 | 36 | # Euler Approximation 37 | S1 = np.zeros([NoOfPaths, NoOfSteps+1]) 38 | S1[:,0] =S_0 39 | 40 | time = np.zeros([NoOfSteps+1]) 41 | 42 | dt = T / float(NoOfSteps) 43 | for i in range(0,NoOfSteps): 44 | # making sure that samples from normal have mean 0 and variance 1 45 | if NoOfPaths > 1: 46 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 47 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 48 | 49 | S1[:,i+1] = S1[:,i] + r * S1[:,i]* dt + sigma * S1[:,i] * (W[:,i+1] - W[:,i]) 50 | time[i+1] = time[i] +dt 51 | 52 | # Retun S1 and S2 53 | paths = {"time":time,"S":S1} 54 | return paths 55 | 56 | def BS_Cash_Or_Nothing_Price(CP,S_0,K,sigma,tau,r): 57 | #Black-Scholes for cash or nothing option 58 | K = np.array(K).reshape([len(K),1]) 59 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 60 | * tau) / float(sigma * np.sqrt(tau)) 61 | d2 = d1 - sigma * np.sqrt(tau) 62 | if CP == OptionType.CALL: 63 | value = K * np.exp(-r * tau) * st.norm.cdf(d2) 64 | if CP == OptionType.PUT: 65 | value = K * np.exp(-r * tau) *(1.0 - st.norm.cdf(d2)) 66 | return value 67 | 68 | def GeneratePathsGBMMilstein(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 69 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 70 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 71 | 72 | # Milstein Approximation 73 | S1 = np.zeros([NoOfPaths, NoOfSteps+1]) 74 | S1[:,0] =S_0 75 | 76 | time = np.zeros([NoOfSteps+1]) 77 | 78 | dt = T / float(NoOfSteps) 79 | for i in range(0,NoOfSteps): 80 | # making sure that samples from normal have mean 0 and variance 1 81 | if NoOfPaths > 1: 82 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 83 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 84 | 85 | S1[:,i+1] = S1[:,i] + r * S1[:,i]* dt + sigma * S1[:,i] * (W[:,i+1] - W[:,i]) \ 86 | + 0.5 * sigma **2.0 * S1[:,i] * (np.power((W[:,i+1] - W[:,i]),2) - dt) 87 | time[i+1] = time[i] +dt 88 | 89 | # Retun S1 and S2 90 | paths = {"time":time,"S":S1} 91 | return paths 92 | 93 | def EUOptionPriceFromMCPaths(CP,S,K,T,r): 94 | # S is a vector of Monte Carlo samples at T 95 | if CP == OptionType.CALL: 96 | return np.exp(-r*T)*np.mean(np.maximum(S-K,0.0)) 97 | elif CP == OptionType.PUT: 98 | return np.exp(-r*T)*np.mean(np.maximum(K-S,0.0)) 99 | 100 | def CashofNothingPriceFromMCPaths(CP,S,K,T,r): 101 | # S is a vector of Monte Carlo samples at T 102 | if CP == OptionType.CALL: 103 | return np.exp(-r*T)*K*np.mean((S>K)) 104 | elif CP == OptionType.PUT: 105 | return np.exp(-r*T)*K*np.mean((S<=K)) 106 | 107 | def mainCalculation(): 108 | CP= OptionType.CALL 109 | T = 1 110 | r = 0.06 111 | sigma = 0.3 112 | S_0 = 5 113 | K = [S_0] 114 | NoOfSteps =1000 115 | 116 | # Simulated paths 117 | NoOfPathsV = [100,1000,5000,10000] 118 | 119 | # Call price 120 | exactPrice = BS_Call_Option_Price(CP,S_0,K,sigma,T,r)[0] 121 | print("EUROPEAN OPTION PRICING") 122 | print("Exact option price = {0}".format(exactPrice)) 123 | for NoOfPathsTemp in NoOfPathsV: 124 | np.random.seed(1) 125 | PathsEuler = GeneratePathsGBMEuler(NoOfPathsTemp,NoOfSteps,T,r,sigma,S_0) 126 | np.random.seed(1) 127 | PathsMilstein = GeneratePathsGBMMilstein(NoOfPathsTemp,NoOfSteps,T,r,sigma,S_0) 128 | S_Euler = PathsEuler["S"] 129 | S_Milstein = PathsMilstein["S"] 130 | priceEuler = EUOptionPriceFromMCPaths(CP,S_Euler[:,-1],K,T,r) 131 | priceMilstein = EUOptionPriceFromMCPaths(CP,S_Milstein[:,-1],K,T,r) 132 | print("For N = {0} Euler scheme yields option price = {1} and Milstein {2}"\ 133 | .format(NoOfPathsTemp,priceEuler,priceMilstein)) 134 | print("For N = {0} Euler error = {1} and Milstein error {2}"\ 135 | .format(NoOfPathsTemp,priceEuler-exactPrice,priceMilstein-exactPrice)) 136 | 137 | # Cash or nothing price 138 | print("CASH OR NOTHING PRICING") 139 | exactPrice = BS_Cash_Or_Nothing_Price(CP,S_0,K,sigma,T,r) 140 | print("Exact option price = {0}".format(exactPrice)) 141 | for NoOfPathsTemp in NoOfPathsV: 142 | np.random.seed(1) 143 | PathsEuler = GeneratePathsGBMEuler(NoOfPathsTemp,NoOfSteps,T,r,sigma,S_0) 144 | np.random.seed(1) 145 | PathsMilstein = GeneratePathsGBMMilstein(NoOfPathsTemp,NoOfSteps,T,r,sigma,S_0) 146 | S_Euler = PathsEuler["S"] 147 | S_Milstein = PathsMilstein["S"] 148 | priceEuler = CashofNothingPriceFromMCPaths(CP,S_Euler[:,-1],K[0],T,r) 149 | priceMilstein = CashofNothingPriceFromMCPaths(CP,S_Milstein[:,-1],K[0],T,r) 150 | print("For N = {0} Euler scheme yields option price = {1} and Milstein {2}"\ 151 | .format(NoOfPathsTemp,priceEuler,priceMilstein)) 152 | print("For N = {0} Euler error = {1} and Milstein error {2}"\ 153 | .format(NoOfPathsTemp,priceEuler-exactPrice,priceMilstein-exactPrice)) 154 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 11- Hedging and Monte Carlo Sensitivites/Lecture 11- Hedging and Monte Carlo Sensitivites.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 11- Hedging and Monte Carlo Sensitivites/Lecture 11- Hedging and Monte Carlo Sensitivites.pdf -------------------------------------------------------------------------------- /Lecture 11- Hedging and Monte Carlo Sensitivites/Materials/BS_Hedging.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Dec 12 2018 4 | Hedging with the Black Scholes model 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import enum 11 | from mpl_toolkits import mplot3d 12 | from scipy.interpolate import RegularGridInterpolator 13 | 14 | # This class defines puts and calls 15 | class OptionType(enum.Enum): 16 | CALL = 1.0 17 | PUT = -1.0 18 | 19 | def GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 20 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 21 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 22 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 23 | time = np.zeros([NoOfSteps+1]) 24 | 25 | X[:,0] = np.log(S_0) 26 | 27 | dt = T / float(NoOfSteps) 28 | for i in range(0,NoOfSteps): 29 | # making sure that samples from normal have mean 0 and variance 1 30 | if NoOfPaths > 1: 31 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 32 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 33 | X[:,i+1] = X[:,i] + (r - 0.5 * sigma * sigma) * dt + sigma * (W[:,i+1]-W[:,i]) 34 | time[i+1] = time[i] +dt 35 | 36 | #Compute exponent of ABM 37 | S = np.exp(X) 38 | paths = {"time":time,"S":S} 39 | return paths 40 | 41 | # Black-Scholes Call option price 42 | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,t,T,r): 43 | K = np.array(K).reshape([len(K),1]) 44 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 45 | * (T-t)) / (sigma * np.sqrt(T-t)) 46 | d2 = d1 - sigma * np.sqrt(T-t) 47 | if CP == OptionType.CALL: 48 | value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * (T-t)) 49 | elif CP == OptionType.PUT: 50 | value = st.norm.cdf(-d2) * K * np.exp(-r * (T-t)) - st.norm.cdf(-d1)*S_0 51 | return value 52 | 53 | def BS_Delta(CP,S_0,K,sigma,t,T,r): 54 | # when defining a time-grid it may happen that the last grid point 55 | # is slightly after the maturity 56 | if t-T>10e-20 and T-t<10e-7: 57 | t=T 58 | K = np.array(K).reshape([len(K),1]) 59 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * \ 60 | (T-t)) / (sigma * np.sqrt(T-t)) 61 | if CP == OptionType.CALL: 62 | value = st.norm.cdf(d1) 63 | elif CP == OptionType.PUT: 64 | value = st.norm.cdf(d1)-1.0 65 | return value 66 | 67 | 68 | def mainCalculation(): 69 | NoOfPaths = 5000 70 | NoOfSteps = 1000 71 | 72 | T = 1.0 73 | r = 0.1 74 | sigma = 0.2 75 | s0 = 1.0 76 | K = [0.95] 77 | CP = OptionType.CALL 78 | 79 | np.random.seed(1) 80 | Paths = GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,s0) 81 | time = Paths["time"] 82 | S = Paths["S"] 83 | 84 | # Setting up some handy lambdas 85 | C = lambda t,K,S0: BS_Call_Put_Option_Price(CP,S0,K,sigma,t,T,r) 86 | Delta = lambda t,K,S0: BS_Delta(CP,S0,K,sigma,t,T,r) 87 | 88 | # Setting up initial portfolio 89 | PnL = np.zeros([NoOfPaths,NoOfSteps+1]) 90 | delta_init= Delta(0.0,K,s0) 91 | PnL[:,0] = C(0.0,K,s0) - delta_init * s0 92 | 93 | CallM = np.zeros([NoOfPaths,NoOfSteps+1]) 94 | CallM[:,0] = C(0.0,K,s0) 95 | DeltaM = np.zeros([NoOfPaths,NoOfSteps+1]) 96 | DeltaM[:,0] = Delta(0,K,s0) 97 | 98 | for i in range(1,NoOfSteps+1): 99 | dt = time[i] - time[i-1] 100 | delta_old = Delta(time[i-1],K,S[:,i-1]) 101 | delta_curr = Delta(time[i],K,S[:,i]) 102 | 103 | PnL[:,i] = PnL[:,i-1]*np.exp(r*dt) - (delta_curr-delta_old)*S[:,i] # PnL 104 | CallM[:,i] = C(time[i],K,S[:,i]) 105 | DeltaM[:,i] = delta_curr 106 | 107 | # Final transaction, payment of the option (if in the money) and selling the hedge 108 | PnL[:,-1] = PnL[:,-1] -np.maximum(S[:,-1]-K,0) + DeltaM[:,-1]*S[:,-1] 109 | 110 | # We plot only one path at the time 111 | path_id = 13 112 | plt.figure(1) 113 | plt.plot(time,S[path_id,:]) 114 | plt.plot(time,CallM[path_id,:]) 115 | plt.plot(time,DeltaM[path_id,:]) 116 | plt.plot(time,PnL[path_id,:]) 117 | plt.legend(['Stock','CallPrice','Delta','PnL']) 118 | plt.grid() 119 | 120 | # Plot the histogram of PnL 121 | plt.figure(2) 122 | plt.hist(PnL[:,-1],50) 123 | plt.grid() 124 | plt.xlim([-0.1,0.1]) 125 | plt.title('histogram of P&L') 126 | 127 | # Analysis for each path 128 | for i in range(0,NoOfPaths): 129 | print('path_id = {0:2d}, PnL(t_0)={1:0.4f}, PnL(Tm-1) ={2:0.4f},S(t_m) = {3:0.4f}, max(S(tm)-K,0)= {4:0.4f}, PnL(t_m) = {5:0.4f}'.format(i,PnL[0,0], 130 | PnL[i,-2],S[i,-1],np.max(S[i,-1]-K,0),PnL[i,-1])) 131 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 11- Hedging and Monte Carlo Sensitivites/Materials/HedgingWithJumps.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Dec 12 2018 4 | Hedging Jumps with the Black Scholes model 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import enum 11 | from mpl_toolkits import mplot3d 12 | from scipy.interpolate import RegularGridInterpolator 13 | 14 | # This class defines puts and calls 15 | class OptionType(enum.Enum): 16 | CALL = 1.0 17 | PUT = -1.0 18 | 19 | def GeneratePathsMerton(NoOfPaths,NoOfSteps,S0, T,xiP,muJ,sigmaJ,r,sigma): 20 | # Create empty matrices for Poisson process and for compensated Poisson process 21 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 22 | S = np.zeros([NoOfPaths, NoOfSteps+1]) 23 | time = np.zeros([NoOfSteps+1]) 24 | 25 | dt = T / float(NoOfSteps) 26 | X[:,0] = np.log(S0) 27 | S[:,0] = S0 28 | 29 | # Expectation E(e^J) for J~N(muJ,sigmaJ^2) 30 | EeJ = np.exp(muJ + 0.5*sigmaJ*sigmaJ) 31 | ZPois = np.random.poisson(xiP*dt,[NoOfPaths,NoOfSteps]) 32 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 33 | J = np.random.normal(muJ,sigmaJ,[NoOfPaths,NoOfSteps]) 34 | for i in range(0,NoOfSteps): 35 | # making sure that samples from normal have mean 0 and variance 1 36 | if NoOfPaths > 1: 37 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 38 | # making sure that samples from normal have mean 0 and variance 1 39 | X[:,i+1] = X[:,i] + (r - xiP*(EeJ-1) - 0.5*sigma*sigma)*dt +sigma*np.sqrt(dt)* Z[:,i]\ 40 | + J[:,i] * ZPois[:,i] 41 | time[i+1] = time[i] +dt 42 | 43 | S = np.exp(X) 44 | paths = {"time":time,"X":X,"S":S} 45 | return paths 46 | 47 | def GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 48 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 49 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 50 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 51 | time = np.zeros([NoOfSteps+1]) 52 | 53 | X[:,0] = np.log(S_0) 54 | 55 | dt = T / float(NoOfSteps) 56 | for i in range(0,NoOfSteps): 57 | # making sure that samples from normal have mean 0 and variance 1 58 | if NoOfPaths > 1: 59 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 60 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 61 | X[:,i+1] = X[:,i] + (r - 0.5 * sigma * sigma) * dt + sigma * (W[:,i+1]-W[:,i]) 62 | time[i+1] = time[i] +dt 63 | 64 | #Compute exponent of ABM 65 | S = np.exp(X) 66 | paths = {"time":time,"S":S} 67 | return paths 68 | 69 | # Black-Scholes Call option price 70 | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,t,T,r): 71 | K = np.array(K).reshape([len(K),1]) 72 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 73 | * (T-t)) / (sigma * np.sqrt(T-t)) 74 | d2 = d1 - sigma * np.sqrt(T-t) 75 | if CP == OptionType.CALL: 76 | value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * (T-t)) 77 | elif CP == OptionType.PUT: 78 | value = st.norm.cdf(-d2) * K * np.exp(-r * (T-t)) - st.norm.cdf(-d1)*S_0 79 | return value 80 | 81 | def BS_Delta(CP,S_0,K,sigma,t,T,r): 82 | # when defining a time-grid it may happen that the last grid point 83 | # is slightly after the maturity 84 | if t-T>10e-20 and T-t<10e-7: 85 | t=T 86 | K = np.array(K).reshape([len(K),1]) 87 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * \ 88 | (T-t)) / (sigma * np.sqrt(T-t)) 89 | if CP == OptionType.CALL: 90 | value = st.norm.cdf(d1) 91 | elif CP == OptionType.PUT: 92 | value = st.norm.cdf(d1)-1.0 93 | return value 94 | 95 | 96 | def mainCalculation(): 97 | NoOfPaths = 1000 98 | NoOfSteps = 5000 99 | T = 1.0 100 | r = 0.1 101 | sigma = 0.2 102 | xiP = 1.0 103 | muJ = 0.0 104 | sigmaJ = 0.25 105 | s0 = 1.0 106 | K = [0.95] 107 | CP = OptionType.CALL 108 | 109 | np.random.seed(7) 110 | Paths = GeneratePathsMerton(NoOfPaths,NoOfSteps,s0, T,xiP,muJ,sigmaJ,r,sigma) 111 | time = Paths["time"] 112 | S = Paths["S"] 113 | 114 | # Setting up some handy lambdas 115 | C = lambda t,K,S0: BS_Call_Put_Option_Price(CP,S0,K,sigma,t,T,r) 116 | Delta = lambda t,K,S0: BS_Delta(CP,S0,K,sigma,t,T,r) 117 | 118 | # Setting up initial portfolio 119 | PnL = np.zeros([NoOfPaths,NoOfSteps+1]) 120 | delta_init= Delta(0.0,K,s0) 121 | PnL[:,0] = C(0.0,K,s0) - delta_init * s0 122 | 123 | CallM = np.zeros([NoOfPaths,NoOfSteps+1]) 124 | CallM[:,0] = C(0.0,K,s0) 125 | DeltaM = np.zeros([NoOfPaths,NoOfSteps+1]) 126 | DeltaM[:,0] = Delta(0,K,s0) 127 | 128 | for i in range(1,NoOfSteps+1): 129 | dt = time[i] - time[i-1] 130 | delta_old = Delta(time[i-1],K,S[:,i-1]) 131 | delta_curr = Delta(time[i],K,S[:,i]) 132 | 133 | PnL[:,i] = PnL[:,i-1]*np.exp(r*dt) - (delta_curr-delta_old)*S[:,i] # PnL 134 | CallM[:,i] = C(time[i],K,S[:,i]) 135 | DeltaM[:,i] =Delta(time[i],K,S[:,i]) 136 | 137 | # Final transaction, payment of the option (if in the money) and selling the hedge 138 | PnL[:,-1] = PnL[:,-1] -np.maximum(S[:,-1]-K,0) + DeltaM[:,-1]*S[:,-1] 139 | 140 | # We plot only one path at the time 141 | path_id = 10 142 | plt.figure(1) 143 | plt.plot(time,S[path_id,:]) 144 | plt.plot(time,CallM[path_id,:]) 145 | plt.plot(time,DeltaM[path_id,:]) 146 | plt.plot(time,PnL[path_id,:]) 147 | plt.legend(['Stock','CallPrice','Delta','PnL']) 148 | plt.grid() 149 | 150 | # Plot the histogram of PnL 151 | plt.figure(2) 152 | plt.hist(PnL[:,-1],100) 153 | plt.grid() 154 | plt.xlim([-0.1,0.1]) 155 | 156 | # Table result for a given path 157 | print("path no {0}, S0={1}, PnL(Tm-1)={2}, S(tm)={3}, max(S(Tm)-K,0)={4}, \ 158 | PnL(Tm)={5}".format(path_id,s0,PnL[path_id,-2],S[path_id,-1], np.maximum(S[path_id,-1]-K,0.0),PnL[path_id,-1])) 159 | 160 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 11- Hedging and Monte Carlo Sensitivites/Materials/PathwiseSens_DeltaVega.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on 08 Mar 2019 4 | Pathwise estimation for delta and vega for the Black-Scholes model 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import enum 11 | 12 | 13 | # This class defines puts and calls 14 | class OptionType(enum.Enum): 15 | CALL = 1.0 16 | PUT = -1.0 17 | 18 | # Black-Scholes Call option price 19 | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,t,T,r): 20 | K = np.array(K).reshape([len(K),1]) 21 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 22 | * (T-t)) / (sigma * np.sqrt(T-t)) 23 | d2 = d1 - sigma * np.sqrt(T-t) 24 | if CP == OptionType.CALL: 25 | value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * (T-t)) 26 | elif CP == OptionType.PUT: 27 | value = st.norm.cdf(-d2) * K * np.exp(-r * (T-t)) - st.norm.cdf(-d1)*S_0 28 | return value 29 | 30 | def BS_Delta(CP,S_0,K,sigma,t,T,r): 31 | K = np.array(K).reshape([len(K),1]) 32 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * \ 33 | (T-t)) / (sigma * np.sqrt(T-t)) 34 | if CP == OptionType.CALL: 35 | value = st.norm.cdf(d1) 36 | elif CP == OptionType.PUT: 37 | value = st.norm.cdf(d1)-1 38 | return value 39 | 40 | def BS_Gamma(S_0,K,sigma,t,T,r): 41 | K = np.array(K).reshape([len(K),1]) 42 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * \ 43 | (T-t)) / (sigma * np.sqrt(T-t)) 44 | return st.norm.pdf(d1) / (S_0 * sigma * np.sqrt(T-t)) 45 | 46 | def BS_Vega(S_0,K,sigma,t,T,r): 47 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * \ 48 | (T-t)) / (sigma * np.sqrt(T-t)) 49 | return S_0*st.norm.pdf(d1)*np.sqrt(T-t) 50 | 51 | def GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 52 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 53 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 54 | 55 | # Approximation 56 | S = np.zeros([NoOfPaths, NoOfSteps+1]) 57 | S[:,0] =S_0 58 | 59 | X = np.zeros([NoOfPaths, NoOfSteps+1]) 60 | X[:,0] =np.log(S_0) 61 | 62 | time = np.zeros([NoOfSteps+1]) 63 | 64 | dt = T / float(NoOfSteps) 65 | for i in range(0,NoOfSteps): 66 | # making sure that samples from normal have mean 0 and variance 1 67 | if NoOfPaths > 1: 68 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 69 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 70 | 71 | X[:,i+1] = X[:,i] + (r -0.5*sigma**2.0)* dt + sigma * (W[:,i+1] - W[:,i]) 72 | time[i+1] = time[i] +dt 73 | 74 | # Retun S 75 | paths = {"time":time,"S":np.exp(X)} 76 | return paths 77 | 78 | def EUOptionPriceFromMCPathsGeneralized(CP,S,K,T,r): 79 | # S is a vector of Monte Carlo samples at T 80 | result = np.zeros([len(K),1]) 81 | if CP == OptionType.CALL: 82 | for (idx,k) in enumerate(K): 83 | result[idx] = np.exp(-r*T)*np.mean(np.maximum(S-k,0.0)) 84 | elif CP == OptionType.PUT: 85 | for (idx,k) in enumerate(K): 86 | result[idx] = np.exp(-r*T)*np.mean(np.maximum(k-S,0.0)) 87 | return result 88 | 89 | def PathwiseDelta(S0,S,K,r,T): 90 | temp1 = S[:,-1]>K 91 | return np.exp(-r*T)*np.mean(S[:,-1]/S0*temp1) 92 | 93 | def PathwiseVega(S0,S,sigma,K,r,T): 94 | temp1 = S[:,-1]>K 95 | temp2 = 1.0/sigma* S[:,-1]*(np.log(S[:,-1]/S0)-(r+0.5*sigma**2.0)*T) 96 | return np.exp(-r*T)*np.mean(temp1*temp2) 97 | 98 | 99 | def mainCalculation(): 100 | CP = OptionType.CALL 101 | S0 = 1 102 | r = 0.06 103 | sigma = 0.3 104 | T = 1 105 | K = np.array([S0]) 106 | t = 0.0 107 | 108 | NoOfSteps = 1000 109 | delta_Exact = BS_Delta(CP,S0,K,sigma,t,T,r) 110 | vega_Exact = BS_Vega(S0,K,sigma,t,T,r) 111 | 112 | NoOfPathsV = np.round(np.linspace(5,1000,50)) 113 | deltaPathWiseV = np.zeros(len(NoOfPathsV)) 114 | vegaPathWiseV = np.zeros(len(NoOfPathsV)) 115 | 116 | for (idx,nPaths) in enumerate(NoOfPathsV): 117 | print('Running simulation with {0} paths'.format(nPaths)) 118 | np.random.seed(3) 119 | paths1 = GeneratePathsGBMEuler(int(nPaths),NoOfSteps,T,r,sigma,S0) 120 | S = paths1["S"] 121 | delta_pathwise = PathwiseDelta(S0,S,K,r,T) 122 | deltaPathWiseV[idx]= delta_pathwise 123 | 124 | vega_pathwise = PathwiseVega(S0,S,sigma,K,r,T) 125 | vegaPathWiseV[idx] =vega_pathwise 126 | 127 | plt.figure(1) 128 | plt.grid() 129 | plt.plot(NoOfPathsV,deltaPathWiseV,'.-r') 130 | plt.plot(NoOfPathsV,delta_Exact*np.ones([len(NoOfPathsV),1])) 131 | plt.xlabel('number of paths') 132 | plt.ylabel('Delta') 133 | plt.title('Convergence of pathwise delta w.r.t number of paths') 134 | plt.legend(['pathwise est','exact']) 135 | 136 | plt.figure(2) 137 | plt.grid() 138 | plt.plot(NoOfPathsV,vegaPathWiseV,'.-r') 139 | plt.plot(NoOfPathsV,vega_Exact*np.ones([len(NoOfPathsV),1])) 140 | plt.xlabel('number of paths') 141 | plt.ylabel('Vega') 142 | plt.title('Convergence of pathwise vega w.r.t number of paths') 143 | plt.legend(['pathwise est','exact']) 144 | 145 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 12- Forward Start Options and Model of Bates/Lecture 12- Forward Start Options and Model of Bates.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 12- Forward Start Options and Model of Bates/Lecture 12- Forward Start Options and Model of Bates.pdf -------------------------------------------------------------------------------- /Lecture 12- Forward Start Options and Model of Bates/Materials/BatesImpliedVolatility.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Nov 30 2018 4 | The Bates Model and implied volatilities obtained with the COS method 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import enum 11 | import scipy.optimize as optimize 12 | 13 | # set i= imaginary number 14 | i = np.complex(0.0,1.0) 15 | 16 | # This class defines puts and calls 17 | class OptionType(enum.Enum): 18 | CALL = 1.0 19 | PUT = -1.0 20 | 21 | def CallPutOptionPriceCOSMthd(cf,CP,S0,r,tau,K,N,L): 22 | # cf - characteristic function as a functon, in the book denoted as \varphi 23 | # CP - C for call and P for put 24 | # S0 - Initial stock price 25 | # r - interest rate (constant) 26 | # tau - time to maturity 27 | # K - list of strikes 28 | # N - Number of expansion terms 29 | # L - size of truncation domain (typ.:L=8 or L=10) 30 | 31 | # reshape K to a column vector 32 | if K is not np.array: 33 | K = np.array(K).reshape([len(K),1]) 34 | 35 | #assigning i=sqrt(-1) 36 | i = np.complex(0.0,1.0) 37 | x0 = np.log(S0 / K) 38 | 39 | # truncation domain 40 | a = 0.0 - L * np.sqrt(tau) 41 | b = 0.0 + L * np.sqrt(tau) 42 | 43 | # sumation from k = 0 to k=N-1 44 | k = np.linspace(0,N-1,N).reshape([N,1]) 45 | u = k * np.pi / (b - a); 46 | 47 | # Determine coefficients for Put Prices 48 | H_k = CallPutCoefficients(CP,a,b,k) 49 | mat = np.exp(i * np.outer((x0 - a) , u)) 50 | temp = cf(u) * H_k 51 | temp[0] = 0.5 * temp[0] 52 | value = np.exp(-r * tau) * K * np.real(mat.dot(temp)) 53 | return value 54 | 55 | # Determine coefficients for Put Prices 56 | def CallPutCoefficients(CP,a,b,k): 57 | if CP==OptionType.CALL: 58 | c = 0.0 59 | d = b 60 | coef = Chi_Psi(a,b,c,d,k) 61 | Chi_k = coef["chi"] 62 | Psi_k = coef["psi"] 63 | if a < b and b < 0.0: 64 | H_k = np.zeros([len(k),1]) 65 | else: 66 | H_k = 2.0 / (b - a) * (Chi_k - Psi_k) 67 | elif CP==OptionType.PUT: 68 | c = a 69 | d = 0.0 70 | coef = Chi_Psi(a,b,c,d,k) 71 | Chi_k = coef["chi"] 72 | Psi_k = coef["psi"] 73 | H_k = 2.0 / (b - a) * (- Chi_k + Psi_k) 74 | 75 | return H_k 76 | 77 | def Chi_Psi(a,b,c,d,k): 78 | psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a)) 79 | psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi) 80 | psi[0] = d - c 81 | 82 | chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) 83 | expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d) - np.cos(k * np.pi 84 | * (c - a) / (b - a)) * np.exp(c) 85 | expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * 86 | (d - a) / (b - a)) - k * np.pi / (b - a) * np.sin(k 87 | * np.pi * (c - a) / (b - a)) * np.exp(c) 88 | chi = chi * (expr1 + expr2) 89 | 90 | value = {"chi":chi,"psi":psi } 91 | return value 92 | 93 | # Black-Scholes Call option price 94 | def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r): 95 | if K is list: 96 | K = np.array(K).reshape([len(K),1]) 97 | d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 98 | * tau) / (sigma * np.sqrt(tau)) 99 | d2 = d1 - sigma * np.sqrt(tau) 100 | if CP == OptionType.CALL: 101 | value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau) 102 | elif CP == OptionType.PUT: 103 | value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0 104 | return value 105 | 106 | # Implied volatility method 107 | def ImpliedVolatility(CP,marketPrice,K,T,S_0,r): 108 | # To determine initial volatility we interpolate define a grid for sigma 109 | # and interpolate on the inverse 110 | sigmaGrid = np.linspace(0,2,200) 111 | optPriceGrid = BS_Call_Option_Price(CP,S_0,K,sigmaGrid,T,r) 112 | sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid) 113 | print("Initial volatility = {0}".format(sigmaInitial)) 114 | 115 | # Use determined input for the local-search (final tuning) 116 | func = lambda sigma: np.power(BS_Call_Option_Price(CP,S_0,K,sigma,T,r) - marketPrice, 1.0) 117 | impliedVol = optimize.newton(func, sigmaInitial, tol=1e-10) 118 | print("Final volatility = {0}".format(impliedVol)) 119 | return impliedVol 120 | 121 | def ChFBatesModel(r,tau,kappa,gamma,vbar,v0,rho,xiP,muJ,sigmaJ): 122 | i = np.complex(0.0,1.0) 123 | D1 = lambda u: np.sqrt(np.power(kappa-gamma*rho*i*u,2)+(u*u+i*u)*gamma*gamma) 124 | g = lambda u: (kappa-gamma*rho*i*u-D1(u))/(kappa-gamma*rho*i*u+D1(u)) 125 | C = lambda u: (1.0-np.exp(-D1(u)*tau))/(gamma*gamma*(1.0-g(u)*\ 126 | np.exp(-D1(u)*tau)))*(kappa-gamma*rho*i*u-D1(u)) 127 | # Note that we exclude the term -r*tau, as the discounting is performed in the COS method 128 | AHes= lambda u: r * i*u *tau + kappa*vbar*tau/gamma/gamma *(kappa-gamma*\ 129 | rho*i*u-D1(u)) - 2*kappa*vbar/gamma/gamma*np.log((1.0-g(u)*np.exp(-D1(u)*tau))/(1.0-g(u))) 130 | 131 | A = lambda u: AHes(u) - xiP * i * u * tau *(np.exp(muJ+0.5*sigmaJ*sigmaJ) - 1.0) + \ 132 | xiP * tau * (np.exp(i*u*muJ - 0.5 * sigmaJ * sigmaJ * u * u) - 1.0) 133 | 134 | # Characteristic function for the Heston's model 135 | cf = lambda u: np.exp(A(u) + C(u)*v0) 136 | return cf 137 | 138 | def mainCalculation(): 139 | CP = OptionType.CALL 140 | S0 = 100.0 141 | r = 0.0 142 | tau = 1.0 143 | 144 | K = np.linspace(40,180,10) 145 | K = np.array(K).reshape([len(K),1]) 146 | 147 | N = 1000 148 | L = 6 149 | 150 | kappa = 1.2 151 | gamma = 0.05 152 | vbar = 0.05 153 | rho = -0.75 154 | v0 = vbar 155 | muJ = 0.0 156 | sigmaJ= 0.2 157 | xiP = 0.1 158 | 159 | # effect of xiP 160 | plt.figure(1) 161 | plt.grid() 162 | plt.xlabel('strike, K') 163 | plt.ylabel('implied volatility') 164 | xiPV = [0.01, 0.1, 0.2, 0.3] 165 | legend = [] 166 | for xiPTemp in xiPV: 167 | # Evaluate the Bates model 168 | # Compute ChF for the Bates 169 | cf = ChFBatesModel(r,tau,kappa,gamma,vbar,v0,rho,xiPTemp,muJ,sigmaJ) 170 | 171 | # The COS method 172 | valCOS = CallPutOptionPriceCOSMthd(cf, CP, S0, r, tau, K, N, L) 173 | 174 | # Implied volatilities 175 | IV =np.zeros([len(K),1]) 176 | for idx in range(0,len(K)): 177 | IV[idx] = ImpliedVolatility(CP,valCOS[idx],K[idx],tau,S0,r) 178 | plt.plot(K,IV*100.0) 179 | legend.append('xiP={0}'.format(xiPTemp)) 180 | plt.legend(legend) 181 | 182 | # effect of muJ 183 | plt.figure(2) 184 | plt.grid() 185 | plt.xlabel('strike, K') 186 | plt.ylabel('implied volatility') 187 | muJPV = [-0.5, -0.25, 0, 0.25] 188 | legend = [] 189 | for muJTemp in muJPV: 190 | # Evaluate the Bates model 191 | # Compute ChF for the Bates 192 | cf = ChFBatesModel(r,tau,kappa,gamma,vbar,v0,rho,xiP,muJTemp,sigmaJ) 193 | 194 | # The COS method 195 | valCOS = CallPutOptionPriceCOSMthd(cf, CP, S0, r, tau, K, N, L) 196 | 197 | # Implied volatilities 198 | IV =np.zeros([len(K),1]) 199 | for idx in range(0,len(K)): 200 | IV[idx] = ImpliedVolatility(CP,valCOS[idx],K[idx],tau,S0,r) 201 | plt.plot(K,IV*100.0) 202 | legend.append('muJ={0}'.format(muJTemp)) 203 | plt.legend(legend) 204 | 205 | # effect of sigmaJ 206 | plt.figure(3) 207 | plt.grid() 208 | plt.xlabel('strike, K') 209 | plt.ylabel('implied volatility') 210 | sigmaJV = [0.01, 0.15, 0.2, 0.25] 211 | legend = [] 212 | for sigmaJTemp in sigmaJV: 213 | # Evaluate the Bates model 214 | # Compute ChF for the Bates 215 | cf = ChFBatesModel(r,tau,kappa,gamma,vbar,v0,rho,xiP,muJ,sigmaJTemp) 216 | 217 | # The COS method 218 | valCOS = CallPutOptionPriceCOSMthd(cf, CP, S0, r, tau, K, N, L) 219 | 220 | # Implied volatilities 221 | IV =np.zeros([len(K),1]) 222 | for idx in range(0,len(K)): 223 | IV[idx] = ImpliedVolatility(CP,valCOS[idx],K[idx],tau,S0,r) 224 | plt.plot(K,IV*100.0) 225 | legend.append('sigmaJ={0}'.format(sigmaJTemp)) 226 | plt.legend(legend) 227 | 228 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 12- Forward Start Options and Model of Bates/Materials/HestonForwardStart2.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Jan 2 2019 4 | The Heston model and pricing of forward start options 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import enum 11 | import scipy.optimize as optimize 12 | 13 | # set i= imaginary number 14 | i = np.complex(0.0,1.0) 15 | 16 | # This class defines puts and calls 17 | class OptionType(enum.Enum): 18 | CALL = 1.0 19 | PUT = -1.0 20 | 21 | def CallPutOptionPriceCOSMthd_FrwdStart(cf,CP,r,T1,T2,K,N,L): 22 | # cf - characteristic function as a functon, in the book denoted as \varphi 23 | # CP - C for call and P for put 24 | # S0 - Initial stock price 25 | # r - interest rate (constant) 26 | # K - list of strikes 27 | # N - Number of expansion terms 28 | # L - size of truncation domain (typ.:L=8 or L=10) 29 | 30 | tau = T2 - T1 31 | # reshape K to a column vector 32 | if K is not np.array: 33 | K = np.array(K).reshape([len(K),1]) 34 | 35 | # Adjust strike 36 | K = K + 1.0 37 | 38 | #assigning i=sqrt(-1) 39 | i = np.complex(0.0,1.0) 40 | x0 = np.log(1.0 / K) 41 | 42 | # truncation domain 43 | a = 0.0 - L * np.sqrt(tau) 44 | b = 0.0 + L * np.sqrt(tau) 45 | 46 | # sumation from k = 0 to k=N-1 47 | k = np.linspace(0,N-1,N).reshape([N,1]) 48 | u = k * np.pi / (b - a); 49 | 50 | # Determine coefficients for Put Prices 51 | H_k = CallPutCoefficients(CP,a,b,k) 52 | mat = np.exp(i * np.outer((x0 - a) , u)) 53 | temp = cf(u) * H_k 54 | temp[0] = 0.5 * temp[0] 55 | value = np.exp(-r * T2) * K * np.real(mat.dot(temp)) 56 | return value 57 | 58 | # Determine coefficients for Put and Call Prices 59 | def CallPutCoefficients(CP,a,b,k): 60 | if CP==OptionType.CALL: 61 | c = 0.0 62 | d = b 63 | coef = Chi_Psi(a,b,c,d,k) 64 | Chi_k = coef["chi"] 65 | Psi_k = coef["psi"] 66 | if a < b and b < 0.0: 67 | H_k = np.zeros([len(k),1]) 68 | else: 69 | H_k = 2.0 / (b - a) * (Chi_k - Psi_k) 70 | elif CP==OptionType.PUT: 71 | c = a 72 | d = 0.0 73 | coef = Chi_Psi(a,b,c,d,k) 74 | Chi_k = coef["chi"] 75 | Psi_k = coef["psi"] 76 | H_k = 2.0 / (b - a) * (- Chi_k + Psi_k) 77 | 78 | return H_k 79 | 80 | def Chi_Psi(a,b,c,d,k): 81 | psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a)) 82 | psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi) 83 | psi[0] = d - c 84 | 85 | chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) 86 | expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d) - np.cos(k * np.pi 87 | * (c - a) / (b - a)) * np.exp(c) 88 | expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * 89 | (d - a) / (b - a)) - k * np.pi / (b - a) * np.sin(k 90 | * np.pi * (c - a) / (b - a)) * np.exp(c) 91 | chi = chi * (expr1 + expr2) 92 | 93 | value = {"chi":chi,"psi":psi } 94 | return value 95 | 96 | # Forward start Black-Scholes option price 97 | def BS_Call_Option_Price_FrwdStart(K,sigma,T1,T2,r): 98 | if K is list: 99 | K = np.array(K).reshape([len(K),1]) 100 | K = K + 1.0 101 | tau = T2 - T1 102 | d1 = (np.log(1.0 / K) + (r + 0.5 * np.power(sigma,2.0))* tau) / (sigma * np.sqrt(tau)) 103 | d2 = d1 - sigma * np.sqrt(tau) 104 | value = np.exp(-r*T1) * st.norm.cdf(d1) - st.norm.cdf(d2) * K * np.exp(-r * T2) 105 | return value 106 | 107 | # Implied volatility for the forward start call option 108 | def ImpliedVolatility_FrwdStart(marketPrice,K,T1,T2,r): 109 | # To determine initial volatility we interpolate define a grid for sigma 110 | # and interpolate on the inverse 111 | sigmaGrid = np.linspace(0,2,200) 112 | optPriceGrid = BS_Call_Option_Price_FrwdStart(K,sigmaGrid,T1,T2,r) 113 | sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid) 114 | print("Initial volatility = {0}".format(sigmaInitial)) 115 | 116 | # Use determined input for the local-search (final tuning) 117 | func = lambda sigma: np.power(BS_Call_Option_Price_FrwdStart(K,sigma,T1,T2,r) - marketPrice, 1.0) 118 | impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15) 119 | print("Final volatility = {0}".format(impliedVol)) 120 | return impliedVol 121 | 122 | def ChFHestonModelForwardStart(r,T1,T2,kappa,gamma,vbar,v0,rho): 123 | i = np.complex(0.0,1.0) 124 | tau = T2 - T1 125 | D1 = lambda u: np.sqrt(np.power(kappa-gamma*rho*i*u,2)+(u*u+i*u)*gamma*gamma) 126 | g = lambda u: (kappa-gamma*rho*i*u-D1(u))/(kappa-gamma*rho*i*u+D1(u)) 127 | C = lambda u: (1.0-np.exp(-D1(u)*tau))/(gamma*gamma*(1.0-g(u)*np.exp(-D1(u)*tau)))\ 128 | *(kappa-gamma*rho*i*u-D1(u)) 129 | # Note that we exclude the term -r*tau, as the discounting is performed in the COS method 130 | A = lambda u: r*i*u*tau + kappa*vbar*tau/gamma/gamma *(kappa-gamma*rho*i*u-D1(u))\ 131 | - 2*kappa*vbar/gamma/gamma*np.log((1.0-g(u)*np.exp(-D1(u)*tau))/(1.0-g(u))) 132 | c_bar = lambda t1,t2: gamma*gamma/(4.0*kappa) * (1.0 - np.exp(-kappa*(t2-t1))) 133 | delta = 4.0*kappa*vbar/gamma/gamma 134 | kappa_bar = lambda t1, t2: 4.0*kappa*v0*np.exp(-kappa*(t2-t1))/(gamma*gamma*(1.0-np.exp(-kappa*(t2-t1)))) 135 | term1 = lambda u: A(u) + C(u)*c_bar(0.0,T1)*kappa_bar(0.0,T1)/(1.0-2.0*C(u)*c_bar(0.0,T1)) 136 | term2 = lambda u: np.power(1.0/(1.0-2.0*C(u)*c_bar(0.0,T1)),0.5*delta) 137 | cf = lambda u: np.exp(term1(u)) * term2(u) 138 | return cf 139 | 140 | def mainCalculation(): 141 | CP = OptionType.CALL 142 | r = 0.00 143 | 144 | TMat1=[[1.0,3.0],[2.0,4.0],[3.0, 5.0],[4.0, 6.0]] 145 | TMat2=[[1.0,2.0],[1.0,3.0],[1.0, 4.0],[1.0, 5.0]] 146 | 147 | K = np.linspace(-0.4,4.0,50) 148 | K = np.array(K).reshape([len(K),1]) 149 | 150 | N = 500 151 | L = 10 152 | 153 | # Heston model parameters 154 | kappa = 0.6 155 | gamma = 0.2 156 | vbar = 0.1 157 | rho = -0.5 158 | v0 = 0.05 159 | 160 | plt.figure(1) 161 | plt.grid() 162 | plt.xlabel('strike, K') 163 | plt.ylabel('implied volatility') 164 | legend = [] 165 | for T_pair in TMat1: 166 | T1= T_pair[0] 167 | T2= T_pair[1] 168 | cf = ChFHestonModelForwardStart(r,T1,T2,kappa,gamma,vbar,v0,rho) 169 | # Forward-start option from the COS method 170 | valCOS = CallPutOptionPriceCOSMthd_FrwdStart(cf,CP,r,T1,T2,K,N,L) 171 | # Implied volatilities 172 | IV =np.zeros([len(K),1]) 173 | for idx in range(0,len(K)): 174 | IV[idx] = ImpliedVolatility_FrwdStart(valCOS[idx],K[idx],T1,T2,r) 175 | plt.plot(K,IV*100.0) 176 | legend.append('T1={0} & T2={1}'.format(T1,T2)) 177 | plt.legend(legend) 178 | 179 | plt.figure(2) 180 | plt.grid() 181 | plt.xlabel('strike, K') 182 | plt.ylabel('implied volatility') 183 | legend = [] 184 | for T_pair in TMat2: 185 | T1= T_pair[0] 186 | T2= T_pair[1] 187 | cf = ChFHestonModelForwardStart(r,T1,T2,kappa,gamma,vbar,v0,rho) 188 | 189 | # Forward-start option from the COS method 190 | valCOS = CallPutOptionPriceCOSMthd_FrwdStart(cf,CP,r,T1,T2,K,N,L) 191 | 192 | # Implied volatilities 193 | IV =np.zeros([len(K),1]) 194 | for idx in range(0,len(K)): 195 | IV[idx] = ImpliedVolatility_FrwdStart(valCOS[idx],K[idx],T1,T2,r) 196 | plt.plot(K,IV*100.0) 197 | legend.append('T1={0} & T2={1}'.format(T1,T2)) 198 | 199 | plt.legend(legend) 200 | 201 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 13- Exotic Derivatives/Lecture 13- Exotic Derivatives.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 13- Exotic Derivatives/Lecture 13- Exotic Derivatives.pdf -------------------------------------------------------------------------------- /Lecture 13- Exotic Derivatives/Materials/AsianOption.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Jan 16 2019 4 | Pricing of Cash-or-Nothing options with the COS method 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | 9 | def PayoffValuation(S,T,r,payoff): 10 | # S is a vector of Monte Carlo samples at T 11 | return np.exp(-r*T) * np.mean(payoff(S)) 12 | 13 | def GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 14 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 15 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 16 | 17 | # Euler Approximation 18 | S1 = np.zeros([NoOfPaths, NoOfSteps+1]) 19 | S1[:,0] =S_0 20 | 21 | time = np.zeros([NoOfSteps+1]) 22 | 23 | dt = T / float(NoOfSteps) 24 | for i in range(0,NoOfSteps): 25 | # making sure that samples from normal have mean 0 and variance 1 26 | if NoOfPaths > 1: 27 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 28 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 29 | 30 | S1[:,i+1] = S1[:,i] + r * S1[:,i]* dt + sigma * S1[:,i] * (W[:,i+1] - W[:,i]) 31 | time[i+1] = time[i] +dt 32 | 33 | # Retun S1 and S2 34 | paths = {"time":time,"S":S1} 35 | return paths 36 | 37 | 38 | def mainCalculation(): 39 | NoOfPaths = 5000 40 | NoOfSteps = 250 41 | 42 | S0 = 100.0 43 | r = 0.05 44 | T = 5 45 | sigma = 0.2 46 | 47 | paths = GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S0) 48 | S_paths= paths["S"] 49 | S_T = S_paths[:,-1] 50 | 51 | # Payoff setting 52 | K = 100.0 53 | 54 | # Payoff specification 55 | payoff = lambda S: np.maximum(S-K,0.0) 56 | 57 | # Valuation 58 | val_t0 = PayoffValuation(S_T,T,r,payoff) 59 | print("Value of the contract at t0 ={0}".format(val_t0)) 60 | 61 | 62 | A_T= np.mean(S_paths,1) 63 | valAsian_t0 = PayoffValuation(A_T,T,r,payoff) 64 | print("Value of the Asian option at t0 ={0}".format(valAsian_t0)) 65 | 66 | print('variance of S(T) = {0}'.format(np.var(S_T))) 67 | print('variance of A(T) = {0}'.format(np.var(A_T))) 68 | 69 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 13- Exotic Derivatives/Materials/DigitalPayoffs_CostReduction.py: -------------------------------------------------------------------------------- 1 | #%% 2 | """ 3 | Created on Thu Jan 16 2019 4 | Pricing of Cash-or-Nothing options with the COS method 5 | @author: Lech A. Grzelak 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import scipy.stats as st 10 | import enum 11 | 12 | def DigitalPayoffValuation(S,T,r,payoff): 13 | # S is a vector of Monte Carlo samples at T 14 | return np.exp(-r*T) * np.mean(payoff(S)) 15 | 16 | def GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S_0): 17 | Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps]) 18 | W = np.zeros([NoOfPaths, NoOfSteps+1]) 19 | 20 | # Euler Approximation 21 | S1 = np.zeros([NoOfPaths, NoOfSteps+1]) 22 | S1[:,0] =S_0 23 | 24 | time = np.zeros([NoOfSteps+1]) 25 | 26 | dt = T / float(NoOfSteps) 27 | for i in range(0,NoOfSteps): 28 | # making sure that samples from normal have mean 0 and variance 1 29 | if NoOfPaths > 1: 30 | Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i]) 31 | W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i] 32 | 33 | S1[:,i+1] = S1[:,i] + r * S1[:,i]* dt + sigma * S1[:,i] * (W[:,i+1] - W[:,i]) 34 | time[i+1] = time[i] +dt 35 | 36 | # Retun S1 and S2 37 | paths = {"time":time,"S":S1} 38 | return paths 39 | 40 | def UpAndOutBarrier(S,T,r,payoff,Su): 41 | 42 | # handling of a barrier 43 | n1,n2 = S.shape 44 | barrier= np.zeros([n1,n2]) + Su 45 | 46 | hitM = S > barrier 47 | hitVec = np.sum(hitM, 1) 48 | hitVec = (hitVec == 0.0).astype(int) 49 | 50 | V_0 = np.exp(-r*T) * np.mean(payoff(S[:,-1]*hitVec)) 51 | 52 | 53 | return V_0 54 | 55 | def mainCalculation(): 56 | NoOfPaths = 10000 57 | NoOfSteps = 250 58 | 59 | S0 = 100.0 60 | r = 0.05 61 | T = 5 62 | sigma = 0.2 63 | Su = 150 64 | 65 | paths = GeneratePathsGBMEuler(NoOfPaths,NoOfSteps,T,r,sigma,S0) 66 | S_paths= paths["S"] 67 | S_T = S_paths[:,-1] 68 | 69 | # Payoff setting 70 | K = 100.0 71 | K2 = 140.0 72 | 73 | # Payoff specification 74 | payoff = lambda S: np.maximum(S-K,0.0)# - np.maximum(S-K2,0) 75 | 76 | #Plot 77 | S_T_grid = np.linspace(50,S0*1.5,200) 78 | 79 | plt.figure(1) 80 | plt.plot(S_T_grid,payoff(S_T_grid)) 81 | 82 | # Valuation 83 | val_t0 = DigitalPayoffValuation(S_T,T,r,payoff) 84 | print("Value of the contract at t0 ={0}".format(val_t0)) 85 | 86 | # barrier pricing 87 | barrier_price = UpAndOutBarrier(S_paths,T,r,payoff,Su) 88 | 89 | print("Value of the barrier contract at t0 ={0}".format(barrier_price)) 90 | 91 | 92 | mainCalculation() -------------------------------------------------------------------------------- /Lecture 14- Summary of the Course/Lecture 14- Summary of the Course.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Lecture 14- Summary of the Course/Lecture 14- Summary of the Course.pdf -------------------------------------------------------------------------------- /Questions-and-Answers/Courses_Questions-Volume 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LechGrzelak/Computational-Finance-Course/f2c192d64a939bcc8d628bc69a93cd8dfc71369a/Questions-and-Answers/Courses_Questions-Volume 1.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Computational-Finance-Course 2 | Here you will find materials for the course of Computational Finance 3 | 4 | YouTube lectures you can find at: 5 | https://www.youtube.com/ComputationsInFinance 6 | 7 | The course is based on the book: 8 | "Mathematical Modeling and Computation in Finance: With Exercises and Python and MATLAB Computer Codes", 9 | by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing, 2019. 10 | 11 | The website of the book is: 12 | https://LechGrzelak.com 13 | 14 | Content of the course: 15 | 16 | Lecture 1- Introduction and Overview of Asset Classes\ 17 | Lecture 2- Stock, Options and Stochastics\ 18 | Lecture 3- Option Pricing and Simulation in Python\ 19 | Lecture 4- Implied Volatility\ 20 | Lecture 5- Jump Processes\ 21 | Lecture 6- Affine Jump Diffusion Processes\ 22 | Lecture 7- Stochastic Volatility Models\ 23 | Lecture 8- Fourier Transformation for Option Pricing\ 24 | Lecture 9- Monte Carlo Simulation\ 25 | Lecture 10- Monte Carlo Simulation of the Heston Model\ 26 | Lecture 11- Hedging and Monte Carlo Greeks\ 27 | Lecture 12- Forward Start Options and Model of Bates\ 28 | Lecture 13- Exotic Derivatives\ 29 | Lecture 14- Summary\ 30 | --------------------------------------------------------------------------------