main 6b898e5ae7f8 cached
36 files
275.6 KB
107.2k tokens
275 symbols
1 requests
Download .txt
Showing preview only (291K chars total). Download the full file or copy to clipboard to get everything.
Repository: LechGrzelak/FinancialEngineering_IR_xVA
Branch: main
Commit: 6b898e5ae7f8
Files: 36
Total size: 275.6 KB

Directory structure:
gitextract__8ev3d3k/

├── LICENSE
├── Lecture 02-Understanding of Filtrations and Measures/
│   └── Materials/
│       ├── Black_Scholes_Jumps.py
│       └── Martingale.py
├── Lecture 03-The HJM Framework/
│   └── Materials/
│       ├── CIR_IR_paths.py
│       ├── Ho-Lee-ZCBs.py
│       ├── Hull-White-Paths.py
│       └── Hull-White-ZCBs.py
├── Lecture 04-Yield Curve Dynamics under Short Rate/
│   └── Materials/
│       ├── Hull-White-CompRateSim.py
│       ├── Hull-White-ZCBs2.py
│       └── Hull_White_1F_2F_Comparison.py
├── Lecture 05-Interest Rate Products/
│   └── Materials/
│       ├── HW_Caplets.py
│       ├── HW_OptionsOnZCBs.py
│       └── Swaps_HW.py
├── Lecture 06-Construction of Yield Curve and Multi-Curve/
│   └── Materials/
│       ├── MultiCurveBuild.py
│       ├── YieldCurveBuildGreeks.py
│       └── YieldCurveBuild_Treasury.py
├── Lecture 07-Pricing of Swaptions and Negative Interest Rates/
│   └── Materials/
│       ├── HW_CapletsAndFloorlets.py
│       ├── JamshidianTrick.py
│       └── ShiftedLognormal.py
├── Lecture 08-Mortgages and Prepayments/
│   └── Materials/
│       ├── AnnuityMortgage.py
│       ├── BulletMortgage.py
│       ├── Incentives.py
│       └── StochasticAmortizingSwap.py
├── Lecture 09-Hybrid Models and Stochastic Interest Rates/
│   └── Materials/
│       ├── BSHW_Comparison.py
│       ├── BSHW_ImpliedVolatility.py
│       ├── H1_HW_COS_vs_MC.py
│       ├── SZHW_ImpliedVolatilities.py
│       └── SZHW_MonteCarlo_DiversificationProduct.py
├── Lecture 10-Foreign Exchange (FX) and Inflation/
│   └── Materials/
│       └── H1_HW_COS_vs_MC_FX.py
├── Lecture 11-Market Model and Convexity Adjustments/
│   └── Materials/
│       ├── ConvexityCorrection.py
│       └── DD_ImpliedVolatility.py
├── Lecture 12-Valuation Adjustments- xVA (CVA,BCVA and FVA)/
│   └── Materials/
│       └── Exposures_HW_Netting.py
├── Lecture 13-Value-at-Risk and Expected Shortfall/
│   └── Materials/
│       ├── HistoricalVaR_Calculation.py
│       ├── MonteCarloVaR.py
│       └── MrktData.xlsx
└── README.md

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
BSD 3-Clause License

Copyright (c) 2024, leszek

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: Lecture 02-Understanding of Filtrations and Measures/Materials/Black_Scholes_Jumps.py
================================================
#%%
"""
Created on July 05  2021
Impact of conditional expectation pricing (Black-Scholes with Jump volatility)

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import matplotlib.pyplot as plt
import enum
import scipy.stats as st

# This class defines puts and calls
class OptionType(enum.Enum):
    CALL = 1.0
    PUT = -1.0
    
def GeneratePaths(NoOfPaths,NoOfSteps,S0,T,muJ,sigmaJ,r):    
    # Create empty matrices for Poisson process and for compensated Poisson process
    X = np.zeros([NoOfPaths, NoOfSteps+1])
    S = np.zeros([NoOfPaths, NoOfSteps+1])
    time = np.zeros([NoOfSteps+1])
                
    dt = T / float(NoOfSteps)
    X[:,0] = np.log(S0)
    S[:,0] = S0
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    J = np.random.normal(muJ,sigmaJ,[NoOfPaths,NoOfSteps])
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
            
        X[:,i+1]  = X[:,i] + (r - 0.5*J[:,i]**2.0)*dt +J[:,i]*np.sqrt(dt)* Z[:,i]
        time[i+1] = time[i] +dt
        
    S = np.exp(X)
    paths = {"time":time,"X":X,"S":S,"J":J}
    return paths

def EUOptionPriceFromMCPaths(CP,S,K,T,r):
    # S is a vector of Monte Carlo samples at T
    if CP == OptionType.CALL:
        return np.exp(-r*T)*np.mean(np.maximum(S-K,0.0))
    elif CP == OptionType.PUT:
        return np.exp(-r*T)*np.mean(np.maximum(K-S,0.0))

def BS_Call_Put_Option_Price(CP,S_0,K,sigma,t,T,r):
    K = np.array(K).reshape([len(K),1])
    d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0))
    * (T-t)) / (sigma * np.sqrt(T-t))
    d2 = d1 - sigma * np.sqrt(T-t)
    if CP == OptionType.CALL:
        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * (T-t))
    elif CP == OptionType.PUT:
        value = st.norm.cdf(-d2) * K * np.exp(-r * (T-t)) - st.norm.cdf(-d1)*S_0
    return value

def CallOption_CondExpectation(NoOfPaths,T,S0,K,J,r):
    
    # Jumps at time T
    J_i = J[:,-1]
    
    result = np.zeros([NoOfPaths])
    
    for j in range(0,NoOfPaths):
        sigma = J_i[j]
        result[j] = BS_Call_Put_Option_Price(OptionType.CALL,S0,[K],sigma,0.0,T,r)
        
    return np.mean(result)

def mainCalculation():
    NoOfPaths = 25
    NoOfSteps = 500
    T = 5
    muJ = 0.3
    sigmaJ = 0.005
    
    S0 =100
    r  =0.00
    Paths = GeneratePaths(NoOfPaths,NoOfSteps,S0, T,muJ,sigmaJ,r)
    timeGrid = Paths["time"]
    X = Paths["X"]
    S = Paths["S"]
           
    plt.figure(1)
    plt.plot(timeGrid, np.transpose(X))   
    plt.grid()
    plt.xlabel("time")
    plt.ylabel("X(t)")
    
    plt.figure(2)
    plt.plot(timeGrid, np.transpose(S))   
    plt.grid()
    plt.xlabel("time")
    plt.ylabel("S(t)")
    
    # Check the convergence for a given strike
    K = 80
    CP =OptionType.CALL
    
    NGrid = range(100,10000,1000)
    NoOfRuns = len(NGrid)
    
    resultMC = np.zeros([NoOfRuns])
    resultCondExp = np.zeros([NoOfRuns])
       
    for (i,N) in enumerate(NGrid):
            print(N)
            Paths = GeneratePaths(N,NoOfSteps,S0, T,muJ,sigmaJ,r)
            timeGrid = Paths["time"]
            S = Paths["S"]
            resultMC[i] = EUOptionPriceFromMCPaths(CP,S[:,-1],K,T,r)
            
            J = Paths["J"]

            resultCondExp[i]=CallOption_CondExpectation(N,T,S0,K,J,r)
    
    plt.figure(3)
    plt.plot(NGrid,resultMC)  
    plt.plot(NGrid,resultCondExp)
    plt.legend(['MC','Conditional Expectation'])
    plt.title('Call Option Price- Convergence')
    plt.xlabel('Number of Paths')
    plt.ylabel('Option price for a given strike, K')
    plt.grid()
                       
mainCalculation()

================================================
FILE: Lecture 02-Understanding of Filtrations and Measures/Materials/Martingale.py
================================================
#%%
"""
Created on July 05  2021
Simulation of, E(W(t)|F(s)) = W(s) using nested Monte Carlo

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import matplotlib.pyplot as plt
t = 10 
s = 5
NoOfPaths=1000
NoOfSteps=10

# First part to caclulate E(W(t)|F(0)) = W(0)=0
def martingaleA():
    W_t = np.random.normal(0.0,pow(t,0.5),[NoOfPaths,1])
    E_W_t = np.mean(W_t)
    print("mean value equals to: %.2f while the expected value is W(0) =%0.2f " %(E_W_t,0.0))
    
# Second part requiring nested Monte Carlo simulation  E(W(t)|F(s)) = W(s)
def martingaleB():    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths,NoOfSteps+1])
        
    # time-step from [t0,s]
    dt1 = s / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + pow(dt1,0.5)*Z[:,i]
            
    #W_s is the last column of W
    W_s = W[:,-1]
    #for every path W(s) we perform sub-simulation until time t and calculate
    #the expectation
    # time-step from [s,t]
    dt2     = (t-s)/float(NoOfSteps);
    W_t     = np.zeros([NoOfPaths,NoOfSteps+1]);
    
    #Store the results
    E_W_t = np.zeros([NoOfPaths])
    Error=[]
    for i in range(0,NoOfPaths):
        #Sub-simulation from time "s" until "t"
        W_t[:,0] = W_s[i];
        Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
        for j in range(0,NoOfSteps):
            #this is a scaling that ensures that Z has mean 0 and variance 1
            Z[:,j] = (Z[:,j]-np.mean(Z[:,j])) / np.std(Z[:,j]);
            #path simulation, from "s" until "t"
            W_t[:,j+1] = W_t[:,j] + pow(dt2,0.5)*Z[:,j];        
            
        E_W_t[i]=np.mean(W_t[:,-1])
        Error.append(E_W_t[i]-W_s[i])
        
        #Generate a plot for the first path
        if i==0:
            plt.plot(np.linspace(0,s,NoOfSteps+1),W[0,:])
            for j in range(0,NoOfPaths):
                plt.plot(np.linspace(s,t,NoOfSteps+1),W_t[j,:])
            plt.xlabel("time")
            plt.ylabel("W(t)")
            plt.grid()
        
    print(Error)
    error = np.max(np.abs(E_W_t-W_s))
    print("The error is equal to: %.18f"%(error))
    
martingaleB()
    

================================================
FILE: Lecture 03-The HJM Framework/Materials/CIR_IR_paths.py
================================================
#%%
"""
Created on July 11 2021
Monte Carlo Paths for the CIR Process

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import matplotlib.pyplot as plt

def GeneratePathsCIREuler(NoOfPaths,NoOfSteps,T,lambd,r0,theta,gamma):    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta - R[:,i]) * dt + gamma* np.sqrt(R[:,i]) * (W[:,i+1]-W[:,i])
        
        # Truncated boundary condition
        R[:,i+1] = np.maximum(R[:,i+1],0.0)
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths
def mainCalculation():
    NoOfPaths = 1
    NoOfSteps = 500
    T         = 50.0
    lambd     = 0.1
    gamma     = 0.05
    r0        = 0.05
    theta     = 0.05
    
    # Effect of mean reversion lambda
    plt.figure(1) 
    legend = []
    lambdVec = [0.01, 0.2, 5.0]
    for lambdTemp in lambdVec:    
        np.random.seed(2)
        Paths = GeneratePathsCIREuler(NoOfPaths,NoOfSteps,T,lambdTemp,r0,theta,gamma)
        legend.append('lambda={0}'.format(lambdTemp))
        timeGrid = Paths["time"]
        R = Paths["R"]
        plt.plot(timeGrid, np.transpose(R))   
    plt.grid()
    plt.xlabel("time")
    plt.ylabel("R(t)")
    plt.legend(legend)
        
    # Effect of the volatility
    plt.figure(2)    
    legend = []
    gammaVec = [0.1, 0.2, 0.3]
    for gammaTemp in gammaVec:
        np.random.seed(2)
        Paths = GeneratePathsCIREuler(NoOfPaths,NoOfSteps,T,lambd,r0,theta,gammaTemp)
        legend.append('gamma={0}'.format(gammaTemp))
        timeGrid = Paths["time"]
        R = Paths["R"]
        plt.plot(timeGrid, np.transpose(R))   
    plt.grid()
    plt.xlabel("time")
    plt.ylabel("R(t)")
    plt.legend(legend)
    
mainCalculation()

================================================
FILE: Lecture 03-The HJM Framework/Materials/Ho-Lee-ZCBs.py
================================================
#%%
"""
Created on July 12 2021
Ho-Lee Model, Simulation of the Model + Computation of ZCBs, P(0,t)

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""

import numpy as np
import matplotlib.pyplot as plt

def f0T(t,P0T):
    # time-step needed for differentiation
    dt = 0.01    
    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    return expr

def GeneratePathsHoLeeEuler(NoOfPaths,NoOfSteps,T,P0T,sigma):    
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.01,P0T)
    theta = lambda t: (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + sigma**2.0*t 
     
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    M = np.zeros([NoOfPaths, NoOfSteps+1])
    M[:,0]= 1.0
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + theta(time[i]) * dt + sigma* (W[:,i+1]-W[:,i])
        M[:,i+1] = M[:,i] * np.exp((R[:,i+1]+R[:,i])*0.5*dt)
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R,"M":M}
    return paths

def mainCalculation():
    NoOfPaths = 25000
    NoOfSteps = 500
       
    sigma = 0.007
        
    # We define a ZCB curve (obtained from the market)
    P0T = lambda T: np.exp(-0.1*T)
       
    # In this experiment we compare ZCB from the Market and Monte Carlo
    "Monte Carlo part"   
    T = 40
    paths= GeneratePathsHoLeeEuler(NoOfPaths,NoOfSteps,T,P0T,sigma)
    M = paths["M"]
    ti = paths["time"]
        
    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    
    P_t = np.zeros([NoOfSteps+1])
    for i in range(0,NoOfSteps+1):
        P_t[i] = np.mean(1.0/M[:,i])
   
    plt.figure(1)
    plt.grid()
    plt.xlabel('T')
    plt.ylabel('P(0,T)')
    plt.plot(ti,P0T(ti))
    plt.plot(ti,P_t,'--r')
    plt.legend(['P(0,t) market','P(0,t) Monte Carlo'])
    plt.title('ZCBs from Ho-Lee Model')
    
mainCalculation()










================================================
FILE: Lecture 03-The HJM Framework/Materials/Hull-White-Paths.py
================================================
#%%
"""
Created on July 12 2021
Monte Carlo paths for the Hull-White model

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import matplotlib.pyplot as plt

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + \
    eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths

def mainCalculation():
    NoOfPaths = 1
    NoOfSteps = 5000
    T         = 50.0
    lambd     = 0.5
    eta       = 0.01
    
    # We define a ZCB curve (obtained from the market)
    P0T = lambda T: np.exp(-0.05*T) 

    # Effect of mean reversion lambda
    plt.figure(1) 
    legend = []
    lambdVec = [-0.01, 0.2, 5.0]
    for lambdTemp in lambdVec:    
        np.random.seed(2)
        Paths = GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambdTemp, eta)
        legend.append('lambda={0}'.format(lambdTemp))
        timeGrid = Paths["time"]
        R = Paths["R"]
        plt.plot(timeGrid, np.transpose(R))   
    plt.grid()
    plt.xlabel("time")
    plt.ylabel("R(t)")
    plt.legend(legend)
        
    # Effect of the volatility
    plt.figure(2)    
    legend = []
    etaVec = [0.1, 0.2, 0.3]
    for etaTemp in etaVec:
        np.random.seed(2)
        Paths = GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, etaTemp)
        legend.append('eta={0}'.format(etaTemp))
        timeGrid = Paths["time"]
        R = Paths["R"]
        plt.plot(timeGrid, np.transpose(R))   
    plt.grid()
    plt.xlabel("time")
    plt.ylabel("R(t)")
    plt.legend(legend)
    
mainCalculation()

================================================
FILE: Lecture 03-The HJM Framework/Materials/Hull-White-ZCBs.py
================================================
#%%
"""
Created on July 12 2021
Hull-White Model, Simulation of the Model

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""

import numpy as np
import matplotlib.pyplot as plt

def f0T(t,P0T):
    # time-step needed for differentiation
    dt = 0.01    
    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    return expr

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.01,P0T)
    theta = lambda t: 1.0/lambd * (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + f0T(t,P0T) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    M = np.zeros([NoOfPaths, NoOfSteps+1])
    M[:,0]= 1.0
    R[:,0]= r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        M[:,i+1] = M[:,i] * np.exp((R[:,i+1]+R[:,i])*0.5*dt)
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R,"M":M}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.01    
    theta = lambda t: 1.0/lambd * (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + f0T(t,P0T) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    #print("CHANGED THETA")
    return theta#lambda t: 0.1+t-t


def mainCalculation():
    NoOfPaths = 25000
    NoOfSteps = 25
       
    lambd = 0.02
    eta   = 0.02

    # We define a ZCB curve (obtained from the market)
    P0T = lambda T: np.exp(-0.1*T)
       
    # In this experiment we compare ZCB from the Market and Monte Carlo
    "Monte Carlo part"   
    T = 40
    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta)
    M = paths["M"]
    ti = paths["time"]
    #dt = timeGrid[1]-timeGrid[0]
    
    # Here we compare the price of an option on a ZCB from Monte Carlo the Market  
    P_tMC = np.zeros([NoOfSteps+1])
    for i in range(0,NoOfSteps+1):
        P_tMC[i] = np.mean(1.0/M[:,i])
  

    plt.figure(1)
    plt.grid()
    plt.xlabel('T')
    plt.ylabel('P(0,T)')
    plt.plot(ti,P0T(ti))
    plt.plot(ti,P_tMC,'--r')
    plt.legend(['P(0,t) market','P(0,t) Monte Carlo'])
    plt.title('ZCBs from Hull-White Model')
    
mainCalculation()

================================================
FILE: Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull-White-CompRateSim.py
================================================
#%%
"""
Created on July 12 2021
Yield Curve shapes and the Hull-White Model 1 Factor

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""

import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate
#from scipy.interpolate import interp1d
from scipy import interpolate

def f0T(t,P0T):
    # time-step needed for differentiation
    dt = 0.01    
    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    return expr

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.01,P0T)
    theta = lambda t: 1.0/lambd * (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + f0T(t,P0T) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.01    
    theta = lambda t: 1.0/lambd * (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + f0T(t,P0T) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    #print("CHANGED THETA")
    return theta#lambda t: 0.1+t-t
    
def HW_A(lambd,eta,P0T,T1,T2):
    tau = T2-T1
    zGrid = np.linspace(0.0,tau,250)
    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)
    theta = HW_theta(lambd,eta,P0T)    
    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)
    
    temp2 = eta*eta/(4.0*np.power(lambd,3.0)) * (np.exp(-2.0*lambd*tau)*(4*np.exp(lambd*tau)-1.0) -3.0) + eta*eta*tau/(2.0*lambd*lambd)
    
    return temp1 + temp2

def HW_B(lambd,eta,T1,T2):
    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)

def HW2F_ZCB(lambd1,lambd2,eta1,eta2,rho,P0T,T1,T2,xT1,yT1):
    V = lambda t,T: (eta1**2.0)/(lambd1**2.0)*((T2-T1)+2.0/lambd1*np.exp(-lambd1*(T2-T1))-1.0/(2.0*lambd1)*np.exp(-2.0*lambd1*(T2-T1))-3.0/(2.0*lambd1))+\
                   +(eta2**2.0)/(lambd2**2.0)*((T2-T1)+2.0/lambd2*np.exp(-lambd2*(T2-T1))-1.0/(2.0*lambd2)*np.exp(-2.0*lambd2*(T2-T1))-3.0/(2.0*lambd2))+\
            + 2.0*rho*eta1*eta2/(lambd1*lambd2)*(T2-T1 + 1.0/lambd1*(np.exp(-lambd1*(T2-T1))-1.0)+ 1.0/lambd2*(np.exp(-lambd2*(T2-T1))-1.0) - 1.0/(lambd1+lambd2)*(np.exp(-(lambd1+lambd2)*(T2-T1))-1.0))
            
    intPhi = - np.log(P0T(T2)/P0T(T1)*np.exp(-0.5*(V(0,T2)-V(0,T1))))        
    
    A = 1.0/lambd1 * (1.0-np.exp(-lambd1 * (T2-T1)))
    B = 1.0/lambd2 * (1.0-np.exp(-lambd2 * (T2-T1)))
    
    return np.exp(- intPhi -A * xT1 - B * yT1 + 0.5 * V(T1,T2))

def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    return np.exp(A_r + B_r *rT1)

def HW_r_0(P0T,lambd,eta):
    r0 = f0T(0.001,P0T)
    return r0


def mainCalculation():
    NoOfPaths = 20000
    NoOfSteps = 100
       
    #HW1F
    lambd     = 0.01
    eta       = 0.002
    
    # We define a ZCB curve (obtained from the market)
    ti = [0.0,0.00273972600000000,0.0876712330000000,0.172602740000000,0.257534247000000,0.342465753000000,0.427397260000000,0.512328767000000,0.597260274000000,0.682191781000000,0.767123288000000,0.852054795000000,0.936986301000000,1.02191780800000,1.10684931500000,1.19178082200000,1.27671232900000,1.36164383600000,1.44657534200000,1.53150684900000,1.61643835600000,1.70136986300000,1.78630137000000,1.87123287700000,1.95616438400000,2.04109589000000,2.12602739700000,2.21095890400000,2.29589041100000,2.38082191800000,2.46575342500000,2.55068493200000,2.63561643800000,2.72054794500000,2.80547945200000,2.89041095900000,2.97534246600000,3.06027397300000,3.14520547900000,3.23013698600000,3.31506849300000,3.40000000000000,3.48493150700000,3.56986301400000,3.65479452100000,3.73972602700000,3.82465753400000,3.90958904100000,3.99452054800000,4.07945205500000,4.16438356200000,4.24931506800000,4.33424657500000,4.41917808200000,4.50410958900000,4.58904109600000,4.67397260300000,4.75890411000000,4.84383561600000,4.92876712300000,5.01369863000000,5.09863013700000,5.18356164400000,5.26849315100000,5.35342465800000,5.43835616400000,5.52328767100000,5.60821917800000,5.69315068500000,5.77808219200000,5.86301369900000,5.94794520500000,6.03287671200000,6.11780821900000,6.20273972600000,6.28767123300000,6.37260274000000,6.45753424700000,6.54246575300000,6.62739726000000,6.71232876700000,6.79726027400000,6.88219178100000,6.96712328800000,7.05205479500000,7.13698630100000,7.22191780800000,7.30684931500000,7.39178082200000,7.47671232900000,7.56164383600000,7.64657534200000,7.73150684900000,7.81643835600000,7.90136986300000,7.98630137000000,8.07123287700000,8.15616438400000,8.24109589000000,8.32602739700000,8.41095890400000,8.49589041100000,8.58082191800000,8.66575342500000,8.75068493200000,8.83561643800000,8.92054794500000,9.00547945200000,9.09041095900000,9.17534246600000,9.26027397300000,9.34520547900000,9.43013698600000,9.51506849300000,9.60000000000000,9.68493150700000,9.76986301400000,9.85479452100000,9.93972602700000,10.0246575300000,10.1095890400000,10.1945205500000,10.2794520500000,10.3643835600000,10.4493150700000,10.5342465800000,10.6191780800000,10.7041095900000,10.7890411000000,10.8739726000000,10.9589041100000,11.0438356200000,11.1287671200000,11.2136986300000,11.2986301400000,11.3835616400000,11.4684931500000,11.5534246600000,11.6383561600000,11.7232876700000,11.8082191800000,11.8931506800000,11.9780821900000,12.0630137000000,12.1479452100000,12.2328767100000,12.3178082200000,12.4027397300000,12.4876712300000,12.5726027400000,12.6575342500000,12.7424657500000,12.8273972600000,12.9123287700000,12.9972602700000,13.0821917800000,13.1671232900000,13.2520547900000,13.3369863000000,13.4219178100000,13.5068493200000,13.5917808200000,13.6767123300000,13.7616438400000,13.8465753400000,13.9315068500000,14.0164383600000,14.1013698600000,14.1863013700000,14.2712328800000,14.3561643800000,14.4410958900000,14.5260274000000,14.6109589000000,14.6958904100000,14.7808219200000,14.8657534200000,14.9506849300000,15.0356164400000,15.1205479500000,15.2054794500000,15.2904109600000,15.3753424700000,15.4602739700000,15.5452054800000,15.6301369900000,15.7150684900000,15.8000000000000,15.8849315100000,15.9698630100000,16.0547945200000,16.1397260300000,16.2246575300000,16.3095890400000,16.3945205500000,16.4794520500000,16.5643835600000,16.6493150700000,16.7342465800000,16.8191780800000,16.9041095900000,16.9890411000000,17.0739726000000,17.1589041100000,17.2438356200000,17.3287671200000,17.4136986300000,17.4986301400000,17.5835616400000,17.6684931500000,17.7534246600000,17.8383561600000,17.9232876700000,18.0082191800000,18.0931506800000,18.1780821900000,18.2630137000000,18.3479452100000,18.4328767100000,18.5178082200000,18.6027397300000,18.6876712300000,18.7726027400000,18.8575342500000,18.9424657500000,19.0273972600000,19.1123287700000,19.1972602700000,19.2821917800000,19.3671232900000,19.4520547900000,19.5369863000000,19.6219178100000,19.7068493200000,19.7917808200000,19.8767123300000,19.9616438400000,20.0465753400000,20.1315068500000,20.2164383600000,20.3013698600000,20.3863013700000,20.4712328800000,20.5561643800000,20.6410958900000,20.7260274000000,20.8109589000000,20.8958904100000,20.9808219200000,21.0657534200000,21.1506849300000,21.2356164400000,21.3205479500000,21.4054794500000,21.4904109600000,21.5753424700000,21.6602739700000,21.7452054800000,21.8301369900000,21.9150684900000,22,22.0849315100000,22.1698630100000,22.2547945200000,22.3397260300000,22.4246575300000,22.5095890400000,22.5945205500000,22.6794520500000,22.7643835600000,22.8493150700000,22.9342465800000,23.0191780800000,23.1041095900000,23.1890411000000,23.2739726000000,23.3589041100000,23.4438356200000,23.5287671200000,23.6136986300000,23.6986301400000,23.7835616400000,23.8684931500000,23.9534246600000,24.0383561600000,24.1232876700000,24.2082191800000,24.2931506800000,24.3780821900000,24.4630137000000,24.5479452100000,24.6328767100000,24.7178082200000,24.8027397300000,24.8876712300000,24.9726027400000,25.0575342500000,25.1424657500000,25.2273972600000,25.3123287700000,25.3972602700000,25.4821917800000,25.5671232900000,25.6520547900000,25.7369863000000,25.8219178100000,25.9068493200000,25.9917808200000,26.0767123300000,26.1616438400000,26.2465753400000,26.3315068500000,26.4164383600000,26.5013698600000,26.5863013700000,26.6712328800000,26.7561643800000,26.8410958900000,26.9260274000000,27.0109589000000,27.0958904100000,27.1808219200000,27.2657534200000,27.3506849300000,27.4356164400000,27.5205479500000,27.6054794500000,27.6904109600000,27.7753424700000,27.8602739700000,27.9452054800000,28.0301369900000,28.1150684900000,28.2000000000000,28.2849315100000,28.3698630100000,28.4547945200000,28.5397260300000,28.6246575300000,28.7095890400000,28.7945205500000,28.8794520500000,28.9643835600000,29.0493150700000,29.1342465800000,29.2191780800000,29.3041095900000,29.3890411000000,29.4739726000000,29.5589041100000,29.6438356200000,29.7287671200000,29.8136986300000,29.8986301400000,29.9835616400000,30.0684931500000,30.1534246600000,30.2383561600000,30.3232876700000,30.4082191800000,30.4931506800000,30.5780821900000,30.6630137000000,30.7479452100000,30.8328767100000,30.9178082200000,31.0027397300000,31.0876712300000,31.1726027400000,31.2575342500000,31.3424657500000,31.4273972600000,31.5123287700000,31.5972602700000,31.6821917800000,31.7671232900000,31.8520547900000,31.9369863000000,32.0219178100000,32.1068493200000,32.1917808200000,32.2767123300000,32.3616438400000,32.4465753400000,32.5315068500000,32.6164383600000,32.7013698600000,32.7863013700000,32.8712328800000,32.9561643800000,33.0410958900000,33.1260274000000,33.2109589000000,33.2958904100000,33.3808219200000,33.4657534200000,33.5506849300000,33.6356164400000,33.7205479500000,33.8054794500000,33.8904109600000,33.9753424700000,34.0602739700000,34.1452054800000,34.2301369900000,34.3150684900000,34.4000000000000,34.4849315100000,34.5698630100000,34.6547945200000,34.7397260300000,34.8246575300000,34.9095890400000,34.9945205500000,35.0794520500000,35.1643835600000,35.2493150700000,35.3342465800000,35.4191780800000,35.5041095900000,35.5890411000000,35.6739726000000,35.7589041100000,35.8438356200000,35.9287671200000,36.0136986300000,36.0986301400000,36.1835616400000,36.2684931500000,36.3534246600000,36.4383561600000,36.5232876700000,36.6082191800000,36.6931506800000,36.7780821900000,36.8630137000000,36.9479452100000,37.0328767100000,37.1178082200000,37.2027397300000,37.2876712300000,37.3726027400000,37.4575342500000,37.5424657500000,37.6273972600000,37.7123287700000,37.7972602700000,37.8821917800000,37.9671232900000,38.0520547900000,38.1369863000000,38.2219178100000,38.3068493200000,38.3917808200000,38.4767123300000,38.5616438400000,38.6465753400000,38.7315068500000,38.8164383600000,38.9013698600000,38.9863013700000,39.0712328800000,39.1561643800000,39.2410958900000,39.3260274000000,39.4109589000000,39.4958904100000,39.5808219200000,39.6657534200000,39.7506849300000,39.8356164400000,39.9205479500000,40.0054794500000]
    Pi = [1.0,0.999966573000000,0.998930882000000,0.997824062000000,0.996511145000000,0.995199956000000,0.993821602000000,0.992277014000000,0.990734827000000,0.989164324000000,0.987428762000000,0.985704346000000,0.983946708000000,0.982068207000000,0.980193293000000,0.978281187000000,0.976255832000000,0.974234670000000,0.972174514000000,0.970028236000000,0.967886697000000,0.965693800000000,0.963440984000000,0.961193424000000,0.958903753000000,0.956575247000000,0.954252397000000,0.951842433000000,0.949228406000000,0.946482248000000,0.943632525000000,0.940707509000000,0.937735160000000,0.934743101000000,0.931758611000000,0.928808623000000,0.925919731000000,0.923110403000000,0.920338655000000,0.917589739000000,0.914858924000000,0.912141532000000,0.909432941000000,0.906728583000000,0.904023944000000,0.901314567000000,0.898596044000000,0.895864025000000,0.893114214000000,0.890346348000000,0.887569841000000,0.884786140000000,0.881996090000000,0.879200528000000,0.876400278000000,0.873596157000000,0.870788975000000,0.867979528000000,0.865168605000000,0.862356987000000,0.859545442000000,0.856732322000000,0.853915005000000,0.851094371000000,0.848271318000000,0.845446732000000,0.842621487000000,0.839796449000000,0.836972470000000,0.834150393000000,0.831331050000000,0.828515261000000,0.825703790000000,0.822893949000000,0.820084325000000,0.817275636000000,0.814468590000000,0.811663887000000,0.808862219000000,0.806064266000000,0.803270701000000,0.800482187000000,0.797699379000000,0.794922921000000,0.792153338000000,0.789389717000000,0.786631547000000,0.783878633000000,0.781130782000000,0.778387801000000,0.775649505000000,0.772915708000000,0.770186227000000,0.767460882000000,0.764739497000000,0.762021898000000,0.759308200000000,0.756599781000000,0.753896558000000,0.751198194000000,0.748504356000000,0.745814715000000,0.743128950000000,0.740446741000000,0.737767776000000,0.735091745000000,0.732418345000000,0.729747277000000,0.727078670000000,0.724413443000000,0.721751645000000,0.719093279000000,0.716438346000000,0.713786850000000,0.711138792000000,0.708494177000000,0.705853006000000,0.703215284000000,0.700581015000000,0.697950202000000,0.695320544000000,0.692690217000000,0.690059929000000,0.687430378000000,0.684802253000000,0.682176235000000,0.679552992000000,0.676933185000000,0.674317462000000,0.671706465000000,0.669100824000000,0.666501159000000,0.663908082000000,0.661322194000000,0.658744089000000,0.656174348000000,0.653613545000000,0.651062245000000,0.648521002000000,0.645990363000000,0.643470865000000,0.640963036000000,0.638467395000000,0.635983411000000,0.633503959000000,0.631028021000000,0.628556105000000,0.626088714000000,0.623626343000000,0.621169478000000,0.618718599000000,0.616274178000000,0.613836679000000,0.611406558000000,0.608984265000000,0.606570242000000,0.604164924000000,0.601768738000000,0.599382105000000,0.597005438000000,0.594639144000000,0.592283621000000,0.589939262000000,0.587606453000000,0.585285574000000,0.582976996000000,0.580681085000000,0.578398202000000,0.576128698000000,0.573872922000000,0.571631214000000,0.569403908000000,0.567191334000000,0.564993814000000,0.562811666000000,0.560645202000000,0.558494727000000,0.556360543000000,0.554242913000000,0.552138115000000,0.550043470000000,0.547959012000000,0.545884776000000,0.543820795000000,0.541767101000000,0.539723726000000,0.537690699000000,0.535668051000000,0.533655809000000,0.531654001000000,0.529662652000000,0.527681790000000,0.525711438000000,0.523751620000000,0.521802360000000,0.519863678000000,0.517935598000000,0.516018137000000,0.514111318000000,0.512215157000000,0.510329674000000,0.508454885000000,0.506590807000000,0.504737456000000,0.502894847000000,0.501062994000000,0.499241910000000,0.497431609000000,0.495632103000000,0.493843403000000,0.492065521000000,0.490298466000000,0.488542249000000,0.486796879000000,0.485062364000000,0.483338711000000,0.481625929000000,0.479924024000000,0.478233003000000,0.476552870000000,0.474883632000000,0.473225293000000,0.471577858000000,0.469941330000000,0.468315712000000,0.466701007000000,0.465097218000000,0.463504347000000,0.461922395000000,0.460351364000000,0.458791253000000,0.457242065000000,0.455703798000000,0.454176452000000,0.452660027000000,0.451154522000000,0.449659935000000,0.448176257000000,0.446703083000000,0.445240105000000,0.443787209000000,0.442344286000000,0.440911226000000,0.439487920000000,0.438074261000000,0.436670143000000,0.435275460000000,0.433890109000000,0.432513986000000,0.431146989000000,0.429789017000000,0.428439971000000,0.427099751000000,0.425768260000000,0.424445399000000,0.423131074000000,0.421825189000000,0.420527650000000,0.419238363000000,0.417957237000000,0.416684180000000,0.415419101000000,0.414161910000000,0.412912520000000,0.411670842000000,0.410436789000000,0.409210275000000,0.407991213000000,0.406779521000000,0.405575114000000,0.404377909000000,0.403187824000000,0.402004777000000,0.400828688000000,0.399659478000000,0.398497066000000,0.397341375000000,0.396192326000000,0.395049844000000,0.393913852000000,0.392784274000000,0.391661036000000,0.390544064000000,0.389433283000000,0.388328623000000,0.387230010000000,0.386137373000000,0.385050641000000,0.383969745000000,0.382894615000000,0.381825182000000,0.380761378000000,0.379703135000000,0.378650387000000,0.377603067000000,0.376561110000000,0.375524516000000,0.374495509000000,0.373475055000000,0.372462955000000,0.371459016000000,0.370463043000000,0.369474847000000,0.368494238000000,0.367521029000000,0.366555034000000,0.365596072000000,0.364643959000000,0.363698516000000,0.362759566000000,0.361826932000000,0.360900441000000,0.359979918000000,0.359065194000000,0.358156099000000,0.357252465000000,0.356354126000000,0.355460918000000,0.354572678000000,0.353689243000000,0.352810455000000,0.351936156000000,0.351066187000000,0.350200393000000,0.349338622000000,0.348480719000000,0.347626535000000,0.346775919000000,0.345928722000000,0.345084799000000,0.344244003000000,0.343406190000000,0.342571217000000,0.341738943000000,0.340909228000000,0.340081932000000,0.339256917000000,0.338434048000000,0.337613190000000,0.336794207000000,0.335976969000000,0.335161343000000,0.334347200000000,0.333534411000000,0.332722847000000,0.331912384000000,0.331102895000000,0.330294257000000,0.329486347000000,0.328679044000000,0.327872227000000,0.327065778000000,0.326259578000000,0.325453511000000,0.324647462000000,0.323841447000000,0.323037558000000,0.322236538000000,0.321438361000000,0.320642997000000,0.319850421000000,0.319060604000000,0.318273519000000,0.317489140000000,0.316707441000000,0.315928394000000,0.315151974000000,0.314378155000000,0.313606911000000,0.312838216000000,0.312072045000000,0.311308373000000,0.310547175000000,0.309788426000000,0.309032102000000,0.308278178000000,0.307526629000000,0.306777432000000,0.306030564000000,0.305285999000000,0.304543716000000,0.303803690000000,0.303065898000000,0.302330318000000,0.301596926000000,0.300865701000000,0.300136619000000,0.299409659000000,0.298684798000000,0.297962014000000,0.297241286000000,0.296522593000000,0.295805912000000,0.295091222000000,0.294378503000000,0.293667734000000,0.292958894000000,0.292251962000000,0.291546917000000,0.290843741000000,0.290142412000000,0.289442910000000,0.288745217000000,0.288049311000000,0.287355175000000,0.286662788000000,0.285972131000000,0.285283185000000,0.284595932000000,0.283910353000000,0.283226429000000,0.282544142000000,0.281863473000000,0.281184405000000,0.280506920000000,0.279830999000000,0.279156625000000,0.278483781000000,0.277812449000000,0.277142612000000,0.276474253000000,0.275807354000000,0.275141900000000,0.274477873000000,0.273815257000000,0.273154036000000,0.272494193000000,0.271835712000000,0.271178577000000,0.270522772000000,0.269868283000000,0.269215092000000,0.268563184000000,0.267912545000000,0.267263159000000,0.266615011000000,0.265968086000000,0.265322370000000,0.264677847000000,0.264034503000000,0.263392324000000,0.262751295000000,0.262111403000000,0.261472633000000,0.260834972000000,0.260198405000000,0.259562919000000,0.258928501000000,0.258295136000000,0.257662813000000,0.257031517000000,0.256401236000000,0.255771957000000,0.255143667000000,0.254516353000000,0.253890002000000,0.253264603000000,0.252640144000000,0.252016611000000,0.251393992000000,0.250772277000000,0.250151453000000,0.249531508000000,0.248912431000000,0.248294210000000,0.247676834000000,0.247060291000000,0.246444571000000,0.245829663000000,0.245215555000000,0.244602237000000,0.243989698000000,0.243377928000000]
    interpolator =  interpolate.splrep(ti, np.log(Pi), s=0.00001)
    P0T = lambda T: np.exp(interpolate.splev(T, interpolator, der=0))
    r0 = HW_r_0(P0T,lambd,eta)
    
    # In this experiment we compare ZCB from the Market and Analytical expression
    N = 20
    T_end0 = 49.0
    Tgrid= np.linspace(0.1,T_end0,N)
    
    Exact = np.zeros([N,1])
    Proxy_1FHW= np.zeros ([N,1])
    Yield = np.zeros ([N,1])
    for i,Ti in enumerate(Tgrid):
        Proxy_1FHW[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)
        Exact[i] = P0T(Ti)
        Yield[i] = -np.log(Proxy_1FHW[i])/Ti
        
    plt.figure(1)
    plt.grid()
    plt.xlabel('T')
    plt.xlabel('ZCB, P(0,t)')
    plt.plot(Tgrid,Exact,'-k')
    plt.plot(Tgrid,Proxy_1FHW,'--r')
    plt.legend(["Analytcal ZCB","ZCB- 1F Model","ZCB- 2F Model"])
    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')
    
     # Yield 
    plt.figure(2)
    plt.grid()
    plt.plot(Tgrid,Yield,'-k')
    plt.xlabel('T')
    plt.ylabel('yield, r(0,T)')
    
    "Monte Carlo part"   
    T_end = 10.0
    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T_end ,P0T, lambd, eta)
    r = paths["R"]
    timeGrid = paths["time"]
    
    # Yield Curves on the last point
    plt.figure(3)
    plt.xlabel('time')
    plt.ylabel('r(t)')
    plt.title('MC Paths + Yield Curve (Hull-White)')
    plt.grid()
    T_end2 = T_end + 40.0
    Tgrid2= np.linspace(T_end+0.001,T_end2-0.01,N)
    ZCB = np.zeros([N,1])
    r_T = r[:,-1]
    for i in range(0,20):
        for j,Tj in enumerate(Tgrid2):
            ZCB[j] = HW_ZCB(lambd,eta,P0T,T_end,Tj,r_T[i])
            Yield[j] = -np.log(ZCB[j])/(Tj-T_end)
        plt.plot(Tgrid2, Yield)
        plt.plot(timeGrid,r[i,:])

mainCalculation()

================================================
FILE: Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull-White-ZCBs2.py
================================================
#%%
"""
Created on July 12 2021
Hull-White Model, Simulation of the Model + Computation of ZCBs, P(0,t)

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""

import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate

def f0T(t,P0T):
    # time-step needed for differentiation
    dt = 0.01    
    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    return expr

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.01,P0T)
    theta = lambda t: 1.0/lambd * (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + f0T(t,P0T) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    M = np.zeros([NoOfPaths, NoOfSteps+1])
    M[:,0]= 1.0
    R[:,0]= r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        M[:,i+1] = M[:,i] * np.exp((R[:,i+1]+R[:,i])*0.5*dt)
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R,"M":M}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.01    
    theta = lambda t: 1.0/lambd * (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + f0T(t,P0T) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    #print("CHANGED THETA")
    return theta#lambda t: 0.1+t-t

def HW_A(lambd,eta,P0T,T1,T2):
    tau = T2-T1
    zGrid = np.linspace(0.0,tau,250)
    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)
    theta = HW_theta(lambd,eta,P0T)    
    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)  
    temp2 = eta*eta/(4.0*np.power(lambd,3.0)) * (np.exp(-2.0*lambd*tau)*(4*np.exp(lambd*tau)-1.0) -3.0) + eta*eta*tau/(2.0*lambd*lambd)
    return temp1 + temp2

def HW_B(lambd,eta,T1,T2):
    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)

def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    return np.exp(A_r + B_r *rT1)

def mainCalculation():
    NoOfPaths = 20000
    NoOfSteps = 25
       
    lambd = 0.04
    eta   = 0.01

    # We define a ZCB curve (obtained from the market)
    P0T = lambda T: np.exp(-0.1*T)
    
    # Initial r_0
    r0 = f0T(0.01,P0T)    
       
    # In this experiment we compare ZCB from the Market and Monte Carlo
    "Monte Carlo part"   
    T = 40
    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta)
    M = paths["M"]
    ti = paths["time"]
    #dt = timeGrid[1]-timeGrid[0]
    
    # Here we compare the price of an option on a ZCB from Monte Carlo the Market  
    P_tMC = np.zeros([NoOfSteps+1])
    for i in range(0,NoOfSteps+1):
        P_tMC[i] = np.mean(1.0/M[:,i])
   
    # Analytical expression for ZCB using the Affine properties of the HW model
    P_tHW = np.zeros([NoOfSteps+1])
    for i in range(0,NoOfSteps+1):
        P_tHW[i] = HW_ZCB(lambd,eta,P0T,0.0,ti[i],r0)
   
    plt.figure(1)
    plt.grid()
    plt.xlabel('T')
    plt.ylabel('P(0,T)')
    plt.plot(ti,P0T(ti))
    plt.plot(ti,P_tMC,'--r')
    plt.plot(ti,P_tHW,'.k')
    plt.legend(['P(0,t) market','P(0,t) Monte Carlo','P(0,t) from Affine form'])
    plt.title('ZCBs from Hull-White Model')
    
mainCalculation()

================================================
FILE: Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull_White_1F_2F_Comparison.py
================================================
#%%
"""
Created on July 12 2021
Yield Curve shapes and the Hull-White Model (1 and 2 factor cases)

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""

import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate
from scipy import interpolate

def GeneratePathsHW2FEuler(NoOfPaths,NoOfSteps,T,P0T, lambd1,lambd2, eta1,eta2,rho):    
    # time-step needed for differentiation
    phi = lambda t: f0T(t,P0T) + (eta1**2.0)/(2.0*lambd1**2.0)*(1.0-np.exp(-lambd1*t))*(1.0-np.exp(-lambd1*t))+\
                                +(eta2**2.0)/(2.0*lambd2**2.0)*(1.0-np.exp(-lambd2*t))*(1.0-np.exp(-lambd2*t))+\
                           + rho*eta1*eta2/(lambd1*lambd2)*(1.0-np.exp(-lambd1*t))*(1.0-np.exp(-lambd2*t))
    
    Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    Z2 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W1 = np.zeros([NoOfPaths, NoOfSteps+1])
    W2 = np.zeros([NoOfPaths, NoOfSteps+1])
    X = np.zeros([NoOfPaths, NoOfSteps+1])
    Y = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0] = phi(0)
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i])
            Z2[:,i] = (Z2[:,i] - np.mean(Z2[:,i])) / np.std(Z2[:,i])
            Z2[:,i] = rho * Z1[:,i] + np.sqrt(1.0-rho**2)*Z2[:,i]
            
        W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i]
        W2[:,i+1] = W2[:,i] + np.power(dt, 0.5)*Z2[:,i]
        
        X[:,i+1] = X[:,i] - lambd1*X[:,i] * dt + eta1* (W1[:,i+1]-W1[:,i])
        Y[:,i+1] = Y[:,i] - lambd2*Y[:,i] * dt + eta2* (W2[:,i+1]-W2[:,i])
        time[i+1] = time[i] +dt

        R[:,i+1] = X[:,i+1] + Y[:,i+1] + phi(time[i+1])
    
    # Outputs
    paths = {"time":time,"R":R,"X":X,"Y":Y}
    return paths

def f0T(t,P0T):
    # time-step needed for differentiation
    dt = 0.01    
    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    return expr

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.01,P0T)
    theta = lambda t: 1.0/lambd * (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + f0T(t,P0T) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.01    
    theta = lambda t: 1.0/lambd * (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + f0T(t,P0T) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    #print("CHANGED THETA")
    return theta#lambda t: 0.1+t-t
    
def HW_A(lambd,eta,P0T,T1,T2):
    tau = T2-T1
    zGrid = np.linspace(0.0,tau,250)
    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)
    theta = HW_theta(lambd,eta,P0T)    
    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)
    
    temp2 = eta*eta/(4.0*np.power(lambd,3.0)) * (np.exp(-2.0*lambd*tau)*(4*np.exp(lambd*tau)-1.0) -3.0) + eta*eta*tau/(2.0*lambd*lambd)
    
    return temp1 + temp2

def HW_B(lambd,eta,T1,T2):
    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)

def HW2F_ZCB(lambd1,lambd2,eta1,eta2,rho,P0T,T1,T2,xT1,yT1):
    V = lambda t,T: (eta1**2.0)/(lambd1**2.0)*((T-t)+2.0/lambd1*np.exp(-lambd1*(T-t))-1.0/(2.0*lambd1)*np.exp(-2.0*lambd1*(T-t))-3.0/(2.0*lambd1))+\
                   +(eta2**2.0)/(lambd2**2.0)*((T-t)+2.0/lambd2*np.exp(-lambd2*(T-t))-1.0/(2.0*lambd2)*np.exp(-2.0*lambd2*(T-t))-3.0/(2.0*lambd2))+\
            + 2.0*rho*eta1*eta2/(lambd1*lambd2)*(T-t + 1.0/lambd1*(np.exp(-lambd1*(T-t))-1.0)+ 1.0/lambd2*(np.exp(-lambd2*(T-t))-1.0) - 1.0/(lambd1+lambd2)*(np.exp(-(lambd1+lambd2)*(T-t))-1.0))
            
    intPhi = - np.log(P0T(T2)/P0T(T1)*np.exp(-0.5*(V(0,T2)-V(0,T1))))        
    
    A = 1.0/lambd1 * (1.0-np.exp(-lambd1 * (T2-T1)))
    B = 1.0/lambd2 * (1.0-np.exp(-lambd2 * (T2-T1)))
    
    return np.exp(- intPhi -A * xT1 - B * yT1 + 0.5 * V(T1,T2))

def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    return np.exp(A_r + B_r *rT1)

def HW_r_0(P0T,lambd,eta):
    r0 = f0T(0.001,P0T)
    return r0


def mainCalculation():
    NoOfPaths = 2000
    NoOfSteps = 100
       
    #HW1F
    lambd     = 0.01
    eta       = 0.002
    
    #HW2F 
    lambd2 = 0.1
    eta2   = 0.002
    rho    = -0.2
    
    # We define a ZCB curve (obtained from the market), 1-D points with a B-spline curve.
    ti = [0.0,0.00273972600000000,0.0876712330000000,0.172602740000000,0.257534247000000,0.342465753000000,0.427397260000000,0.512328767000000,0.597260274000000,0.682191781000000,0.767123288000000,0.852054795000000,0.936986301000000,1.02191780800000,1.10684931500000,1.19178082200000,1.27671232900000,1.36164383600000,1.44657534200000,1.53150684900000,1.61643835600000,1.70136986300000,1.78630137000000,1.87123287700000,1.95616438400000,2.04109589000000,2.12602739700000,2.21095890400000,2.29589041100000,2.38082191800000,2.46575342500000,2.55068493200000,2.63561643800000,2.72054794500000,2.80547945200000,2.89041095900000,2.97534246600000,3.06027397300000,3.14520547900000,3.23013698600000,3.31506849300000,3.40000000000000,3.48493150700000,3.56986301400000,3.65479452100000,3.73972602700000,3.82465753400000,3.90958904100000,3.99452054800000,4.07945205500000,4.16438356200000,4.24931506800000,4.33424657500000,4.41917808200000,4.50410958900000,4.58904109600000,4.67397260300000,4.75890411000000,4.84383561600000,4.92876712300000,5.01369863000000,5.09863013700000,5.18356164400000,5.26849315100000,5.35342465800000,5.43835616400000,5.52328767100000,5.60821917800000,5.69315068500000,5.77808219200000,5.86301369900000,5.94794520500000,6.03287671200000,6.11780821900000,6.20273972600000,6.28767123300000,6.37260274000000,6.45753424700000,6.54246575300000,6.62739726000000,6.71232876700000,6.79726027400000,6.88219178100000,6.96712328800000,7.05205479500000,7.13698630100000,7.22191780800000,7.30684931500000,7.39178082200000,7.47671232900000,7.56164383600000,7.64657534200000,7.73150684900000,7.81643835600000,7.90136986300000,7.98630137000000,8.07123287700000,8.15616438400000,8.24109589000000,8.32602739700000,8.41095890400000,8.49589041100000,8.58082191800000,8.66575342500000,8.75068493200000,8.83561643800000,8.92054794500000,9.00547945200000,9.09041095900000,9.17534246600000,9.26027397300000,9.34520547900000,9.43013698600000,9.51506849300000,9.60000000000000,9.68493150700000,9.76986301400000,9.85479452100000,9.93972602700000,10.0246575300000,10.1095890400000,10.1945205500000,10.2794520500000,10.3643835600000,10.4493150700000,10.5342465800000,10.6191780800000,10.7041095900000,10.7890411000000,10.8739726000000,10.9589041100000,11.0438356200000,11.1287671200000,11.2136986300000,11.2986301400000,11.3835616400000,11.4684931500000,11.5534246600000,11.6383561600000,11.7232876700000,11.8082191800000,11.8931506800000,11.9780821900000,12.0630137000000,12.1479452100000,12.2328767100000,12.3178082200000,12.4027397300000,12.4876712300000,12.5726027400000,12.6575342500000,12.7424657500000,12.8273972600000,12.9123287700000,12.9972602700000,13.0821917800000,13.1671232900000,13.2520547900000,13.3369863000000,13.4219178100000,13.5068493200000,13.5917808200000,13.6767123300000,13.7616438400000,13.8465753400000,13.9315068500000,14.0164383600000,14.1013698600000,14.1863013700000,14.2712328800000,14.3561643800000,14.4410958900000,14.5260274000000,14.6109589000000,14.6958904100000,14.7808219200000,14.8657534200000,14.9506849300000,15.0356164400000,15.1205479500000,15.2054794500000,15.2904109600000,15.3753424700000,15.4602739700000,15.5452054800000,15.6301369900000,15.7150684900000,15.8000000000000,15.8849315100000,15.9698630100000,16.0547945200000,16.1397260300000,16.2246575300000,16.3095890400000,16.3945205500000,16.4794520500000,16.5643835600000,16.6493150700000,16.7342465800000,16.8191780800000,16.9041095900000,16.9890411000000,17.0739726000000,17.1589041100000,17.2438356200000,17.3287671200000,17.4136986300000,17.4986301400000,17.5835616400000,17.6684931500000,17.7534246600000,17.8383561600000,17.9232876700000,18.0082191800000,18.0931506800000,18.1780821900000,18.2630137000000,18.3479452100000,18.4328767100000,18.5178082200000,18.6027397300000,18.6876712300000,18.7726027400000,18.8575342500000,18.9424657500000,19.0273972600000,19.1123287700000,19.1972602700000,19.2821917800000,19.3671232900000,19.4520547900000,19.5369863000000,19.6219178100000,19.7068493200000,19.7917808200000,19.8767123300000,19.9616438400000,20.0465753400000,20.1315068500000,20.2164383600000,20.3013698600000,20.3863013700000,20.4712328800000,20.5561643800000,20.6410958900000,20.7260274000000,20.8109589000000,20.8958904100000,20.9808219200000,21.0657534200000,21.1506849300000,21.2356164400000,21.3205479500000,21.4054794500000,21.4904109600000,21.5753424700000,21.6602739700000,21.7452054800000,21.8301369900000,21.9150684900000,22,22.0849315100000,22.1698630100000,22.2547945200000,22.3397260300000,22.4246575300000,22.5095890400000,22.5945205500000,22.6794520500000,22.7643835600000,22.8493150700000,22.9342465800000,23.0191780800000,23.1041095900000,23.1890411000000,23.2739726000000,23.3589041100000,23.4438356200000,23.5287671200000,23.6136986300000,23.6986301400000,23.7835616400000,23.8684931500000,23.9534246600000,24.0383561600000,24.1232876700000,24.2082191800000,24.2931506800000,24.3780821900000,24.4630137000000,24.5479452100000,24.6328767100000,24.7178082200000,24.8027397300000,24.8876712300000,24.9726027400000,25.0575342500000,25.1424657500000,25.2273972600000,25.3123287700000,25.3972602700000,25.4821917800000,25.5671232900000,25.6520547900000,25.7369863000000,25.8219178100000,25.9068493200000,25.9917808200000,26.0767123300000,26.1616438400000,26.2465753400000,26.3315068500000,26.4164383600000,26.5013698600000,26.5863013700000,26.6712328800000,26.7561643800000,26.8410958900000,26.9260274000000,27.0109589000000,27.0958904100000,27.1808219200000,27.2657534200000,27.3506849300000,27.4356164400000,27.5205479500000,27.6054794500000,27.6904109600000,27.7753424700000,27.8602739700000,27.9452054800000,28.0301369900000,28.1150684900000,28.2000000000000,28.2849315100000,28.3698630100000,28.4547945200000,28.5397260300000,28.6246575300000,28.7095890400000,28.7945205500000,28.8794520500000,28.9643835600000,29.0493150700000,29.1342465800000,29.2191780800000,29.3041095900000,29.3890411000000,29.4739726000000,29.5589041100000,29.6438356200000,29.7287671200000,29.8136986300000,29.8986301400000,29.9835616400000,30.0684931500000,30.1534246600000,30.2383561600000,30.3232876700000,30.4082191800000,30.4931506800000,30.5780821900000,30.6630137000000,30.7479452100000,30.8328767100000,30.9178082200000,31.0027397300000,31.0876712300000,31.1726027400000,31.2575342500000,31.3424657500000,31.4273972600000,31.5123287700000,31.5972602700000,31.6821917800000,31.7671232900000,31.8520547900000,31.9369863000000,32.0219178100000,32.1068493200000,32.1917808200000,32.2767123300000,32.3616438400000,32.4465753400000,32.5315068500000,32.6164383600000,32.7013698600000,32.7863013700000,32.8712328800000,32.9561643800000,33.0410958900000,33.1260274000000,33.2109589000000,33.2958904100000,33.3808219200000,33.4657534200000,33.5506849300000,33.6356164400000,33.7205479500000,33.8054794500000,33.8904109600000,33.9753424700000,34.0602739700000,34.1452054800000,34.2301369900000,34.3150684900000,34.4000000000000,34.4849315100000,34.5698630100000,34.6547945200000,34.7397260300000,34.8246575300000,34.9095890400000,34.9945205500000,35.0794520500000,35.1643835600000,35.2493150700000,35.3342465800000,35.4191780800000,35.5041095900000,35.5890411000000,35.6739726000000,35.7589041100000,35.8438356200000,35.9287671200000,36.0136986300000,36.0986301400000,36.1835616400000,36.2684931500000,36.3534246600000,36.4383561600000,36.5232876700000,36.6082191800000,36.6931506800000,36.7780821900000,36.8630137000000,36.9479452100000,37.0328767100000,37.1178082200000,37.2027397300000,37.2876712300000,37.3726027400000,37.4575342500000,37.5424657500000,37.6273972600000,37.7123287700000,37.7972602700000,37.8821917800000,37.9671232900000,38.0520547900000,38.1369863000000,38.2219178100000,38.3068493200000,38.3917808200000,38.4767123300000,38.5616438400000,38.6465753400000,38.7315068500000,38.8164383600000,38.9013698600000,38.9863013700000,39.0712328800000,39.1561643800000,39.2410958900000,39.3260274000000,39.4109589000000,39.4958904100000,39.5808219200000,39.6657534200000,39.7506849300000,39.8356164400000,39.9205479500000,40.0054794500000]
    Pi = [1.0,0.999966573000000,0.998930882000000,0.997824062000000,0.996511145000000,0.995199956000000,0.993821602000000,0.992277014000000,0.990734827000000,0.989164324000000,0.987428762000000,0.985704346000000,0.983946708000000,0.982068207000000,0.980193293000000,0.978281187000000,0.976255832000000,0.974234670000000,0.972174514000000,0.970028236000000,0.967886697000000,0.965693800000000,0.963440984000000,0.961193424000000,0.958903753000000,0.956575247000000,0.954252397000000,0.951842433000000,0.949228406000000,0.946482248000000,0.943632525000000,0.940707509000000,0.937735160000000,0.934743101000000,0.931758611000000,0.928808623000000,0.925919731000000,0.923110403000000,0.920338655000000,0.917589739000000,0.914858924000000,0.912141532000000,0.909432941000000,0.906728583000000,0.904023944000000,0.901314567000000,0.898596044000000,0.895864025000000,0.893114214000000,0.890346348000000,0.887569841000000,0.884786140000000,0.881996090000000,0.879200528000000,0.876400278000000,0.873596157000000,0.870788975000000,0.867979528000000,0.865168605000000,0.862356987000000,0.859545442000000,0.856732322000000,0.853915005000000,0.851094371000000,0.848271318000000,0.845446732000000,0.842621487000000,0.839796449000000,0.836972470000000,0.834150393000000,0.831331050000000,0.828515261000000,0.825703790000000,0.822893949000000,0.820084325000000,0.817275636000000,0.814468590000000,0.811663887000000,0.808862219000000,0.806064266000000,0.803270701000000,0.800482187000000,0.797699379000000,0.794922921000000,0.792153338000000,0.789389717000000,0.786631547000000,0.783878633000000,0.781130782000000,0.778387801000000,0.775649505000000,0.772915708000000,0.770186227000000,0.767460882000000,0.764739497000000,0.762021898000000,0.759308200000000,0.756599781000000,0.753896558000000,0.751198194000000,0.748504356000000,0.745814715000000,0.743128950000000,0.740446741000000,0.737767776000000,0.735091745000000,0.732418345000000,0.729747277000000,0.727078670000000,0.724413443000000,0.721751645000000,0.719093279000000,0.716438346000000,0.713786850000000,0.711138792000000,0.708494177000000,0.705853006000000,0.703215284000000,0.700581015000000,0.697950202000000,0.695320544000000,0.692690217000000,0.690059929000000,0.687430378000000,0.684802253000000,0.682176235000000,0.679552992000000,0.676933185000000,0.674317462000000,0.671706465000000,0.669100824000000,0.666501159000000,0.663908082000000,0.661322194000000,0.658744089000000,0.656174348000000,0.653613545000000,0.651062245000000,0.648521002000000,0.645990363000000,0.643470865000000,0.640963036000000,0.638467395000000,0.635983411000000,0.633503959000000,0.631028021000000,0.628556105000000,0.626088714000000,0.623626343000000,0.621169478000000,0.618718599000000,0.616274178000000,0.613836679000000,0.611406558000000,0.608984265000000,0.606570242000000,0.604164924000000,0.601768738000000,0.599382105000000,0.597005438000000,0.594639144000000,0.592283621000000,0.589939262000000,0.587606453000000,0.585285574000000,0.582976996000000,0.580681085000000,0.578398202000000,0.576128698000000,0.573872922000000,0.571631214000000,0.569403908000000,0.567191334000000,0.564993814000000,0.562811666000000,0.560645202000000,0.558494727000000,0.556360543000000,0.554242913000000,0.552138115000000,0.550043470000000,0.547959012000000,0.545884776000000,0.543820795000000,0.541767101000000,0.539723726000000,0.537690699000000,0.535668051000000,0.533655809000000,0.531654001000000,0.529662652000000,0.527681790000000,0.525711438000000,0.523751620000000,0.521802360000000,0.519863678000000,0.517935598000000,0.516018137000000,0.514111318000000,0.512215157000000,0.510329674000000,0.508454885000000,0.506590807000000,0.504737456000000,0.502894847000000,0.501062994000000,0.499241910000000,0.497431609000000,0.495632103000000,0.493843403000000,0.492065521000000,0.490298466000000,0.488542249000000,0.486796879000000,0.485062364000000,0.483338711000000,0.481625929000000,0.479924024000000,0.478233003000000,0.476552870000000,0.474883632000000,0.473225293000000,0.471577858000000,0.469941330000000,0.468315712000000,0.466701007000000,0.465097218000000,0.463504347000000,0.461922395000000,0.460351364000000,0.458791253000000,0.457242065000000,0.455703798000000,0.454176452000000,0.452660027000000,0.451154522000000,0.449659935000000,0.448176257000000,0.446703083000000,0.445240105000000,0.443787209000000,0.442344286000000,0.440911226000000,0.439487920000000,0.438074261000000,0.436670143000000,0.435275460000000,0.433890109000000,0.432513986000000,0.431146989000000,0.429789017000000,0.428439971000000,0.427099751000000,0.425768260000000,0.424445399000000,0.423131074000000,0.421825189000000,0.420527650000000,0.419238363000000,0.417957237000000,0.416684180000000,0.415419101000000,0.414161910000000,0.412912520000000,0.411670842000000,0.410436789000000,0.409210275000000,0.407991213000000,0.406779521000000,0.405575114000000,0.404377909000000,0.403187824000000,0.402004777000000,0.400828688000000,0.399659478000000,0.398497066000000,0.397341375000000,0.396192326000000,0.395049844000000,0.393913852000000,0.392784274000000,0.391661036000000,0.390544064000000,0.389433283000000,0.388328623000000,0.387230010000000,0.386137373000000,0.385050641000000,0.383969745000000,0.382894615000000,0.381825182000000,0.380761378000000,0.379703135000000,0.378650387000000,0.377603067000000,0.376561110000000,0.375524516000000,0.374495509000000,0.373475055000000,0.372462955000000,0.371459016000000,0.370463043000000,0.369474847000000,0.368494238000000,0.367521029000000,0.366555034000000,0.365596072000000,0.364643959000000,0.363698516000000,0.362759566000000,0.361826932000000,0.360900441000000,0.359979918000000,0.359065194000000,0.358156099000000,0.357252465000000,0.356354126000000,0.355460918000000,0.354572678000000,0.353689243000000,0.352810455000000,0.351936156000000,0.351066187000000,0.350200393000000,0.349338622000000,0.348480719000000,0.347626535000000,0.346775919000000,0.345928722000000,0.345084799000000,0.344244003000000,0.343406190000000,0.342571217000000,0.341738943000000,0.340909228000000,0.340081932000000,0.339256917000000,0.338434048000000,0.337613190000000,0.336794207000000,0.335976969000000,0.335161343000000,0.334347200000000,0.333534411000000,0.332722847000000,0.331912384000000,0.331102895000000,0.330294257000000,0.329486347000000,0.328679044000000,0.327872227000000,0.327065778000000,0.326259578000000,0.325453511000000,0.324647462000000,0.323841447000000,0.323037558000000,0.322236538000000,0.321438361000000,0.320642997000000,0.319850421000000,0.319060604000000,0.318273519000000,0.317489140000000,0.316707441000000,0.315928394000000,0.315151974000000,0.314378155000000,0.313606911000000,0.312838216000000,0.312072045000000,0.311308373000000,0.310547175000000,0.309788426000000,0.309032102000000,0.308278178000000,0.307526629000000,0.306777432000000,0.306030564000000,0.305285999000000,0.304543716000000,0.303803690000000,0.303065898000000,0.302330318000000,0.301596926000000,0.300865701000000,0.300136619000000,0.299409659000000,0.298684798000000,0.297962014000000,0.297241286000000,0.296522593000000,0.295805912000000,0.295091222000000,0.294378503000000,0.293667734000000,0.292958894000000,0.292251962000000,0.291546917000000,0.290843741000000,0.290142412000000,0.289442910000000,0.288745217000000,0.288049311000000,0.287355175000000,0.286662788000000,0.285972131000000,0.285283185000000,0.284595932000000,0.283910353000000,0.283226429000000,0.282544142000000,0.281863473000000,0.281184405000000,0.280506920000000,0.279830999000000,0.279156625000000,0.278483781000000,0.277812449000000,0.277142612000000,0.276474253000000,0.275807354000000,0.275141900000000,0.274477873000000,0.273815257000000,0.273154036000000,0.272494193000000,0.271835712000000,0.271178577000000,0.270522772000000,0.269868283000000,0.269215092000000,0.268563184000000,0.267912545000000,0.267263159000000,0.266615011000000,0.265968086000000,0.265322370000000,0.264677847000000,0.264034503000000,0.263392324000000,0.262751295000000,0.262111403000000,0.261472633000000,0.260834972000000,0.260198405000000,0.259562919000000,0.258928501000000,0.258295136000000,0.257662813000000,0.257031517000000,0.256401236000000,0.255771957000000,0.255143667000000,0.254516353000000,0.253890002000000,0.253264603000000,0.252640144000000,0.252016611000000,0.251393992000000,0.250772277000000,0.250151453000000,0.249531508000000,0.248912431000000,0.248294210000000,0.247676834000000,0.247060291000000,0.246444571000000,0.245829663000000,0.245215555000000,0.244602237000000,0.243989698000000,0.243377928000000]
    interpolator =  interpolate.splrep(ti, Pi, s=0.0001)
    P0T = lambda T: interpolate.splev(T, interpolator, der=0)
    r0 = HW_r_0(P0T,lambd,eta)
    
    # In this experiment we compare ZCB from the Market and Analytical expression
    N = 20
    T_end0 = 39.0
    Tgrid= np.linspace(0.1,T_end0,N)

    
    Exact = np.zeros([N,1])
    Proxy_1FHW= np.zeros ([N,1])
    Proxy_2FHW= np.zeros ([N,1])
    Yield = np.zeros ([N,1])
    for i,Ti in enumerate(Tgrid):
        Proxy_1FHW[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)
        Proxy_2FHW[i] = HW2F_ZCB(lambd,lambd2,eta,eta2,rho,P0T,0.0,Ti,0.0,0.0)
        Exact[i] = P0T(Ti)
        Yield[i] = -np.log(Proxy_1FHW[i])/Ti
        
    plt.figure(1)
    plt.grid()
    plt.plot(Tgrid,Exact,'-k')
    plt.plot(Tgrid,Proxy_1FHW,'--r')
    plt.plot(Tgrid,Proxy_2FHW,'.k')
    plt.legend(["Analytcal ZCB","ZCB- 1F Model","ZCB- 2F Model"])
    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')
     
    "Monte Carlo part"   
    T_end = 10.0
    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T_end ,P0T, lambd, eta)
    r = paths["R"]
    timeGrid = paths["time"]
        
    # Yield 
    #r = -np.log(Proxy)/T
    plt.figure(2)
    plt.grid()
    plt.plot(Tgrid,Yield,'-k')
    
    # Yield Curves on the last point
    plt.figure(3)
    plt.xlabel('time')
    plt.ylabel('r(t)')
    plt.title('MC Paths + Yield Curve (Hull-White)')
    plt.grid()
    T_end2 = T_end + 40.0
    Tgrid2= np.linspace(T_end+0.001,T_end2-0.01,N)
    ZCB = np.zeros([N,1])
    r_T = r[:,-1]
    for i in range(0,20):
        for j,Tj in enumerate(Tgrid2):
            ZCB[j] = HW_ZCB(lambd,eta,P0T,T_end,Tj,r_T[i])
            Yield[j] = -np.log(ZCB[j])/(Tj-T_end)
        plt.plot(Tgrid2, Yield)
        plt.plot(timeGrid,r[i,:])
        
    # Analysis for the 2F model
    paths= GeneratePathsHW2FEuler(NoOfPaths,NoOfSteps,T_end,P0T, lambd,lambd2, eta,eta2,rho)
    x = paths["X"]
    y = paths["Y"]
    r = paths["R"]
    timeGrid = paths["time"]
       
     # Yield Curves on the last point
    plt.figure(4)
    plt.xlabel('time')
    plt.ylabel('r(t)')
    plt.title('MC Paths + Yield Curve (Hull-White 2F)')
    plt.grid()
    x_T = x[:,-1]
    y_T = y[:,-1]
    r_T = r[:,-1]
    Tgrid2= np.linspace(T_end+0.001,T_end2,N)
    ZCB = np.zeros([N,1])
    for i in range(0,20):
        for j,Tj in enumerate(Tgrid2):
            ZCB[j] = HW2F_ZCB(lambd,lambd2,eta,eta2,rho,P0T,T_end,Tj,x_T[i],y_T[i])
            #HW_ZCB(lambd,eta,P0T,T_end,Tj,r_T[i])
            Yield[j] = -np.log(ZCB[j])/(Tj-T_end)
        plt.plot(timeGrid,r[i,:])
        plt.plot(Tgrid2, Yield)
    
mainCalculation()

================================================
FILE: Lecture 05-Interest Rate Products/Materials/HW_Caplets.py
================================================
#%%
"""
Created on July 12 2021
Caplets under the Hull-White Model

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import enum 
import matplotlib.pyplot as plt
import scipy.stats as st
import scipy.integrate as integrate

# This class defines puts and calls
class OptionType(enum.Enum):
    CALL = 1.0
    PUT = -1.0

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    #print("CHANGED THETA")
    return theta#lambda t: 0.1+t-t
    
def HW_A(lambd,eta,P0T,T1,T2):
    tau = T2-T1
    zGrid = np.linspace(0.0,tau,250)
    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)
    theta = HW_theta(lambd,eta,P0T)    
    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)
    
    temp2 = eta*eta/(4.0*np.power(lambd,3.0)) * (np.exp(-2.0*lambd*tau)*(4*np.exp(lambd*tau)-1.0) -3.0) + eta*eta*tau/(2.0*lambd*lambd)
    
    return temp1 + temp2

def HW_B(lambd,eta,T1,T2):
    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)

def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    return np.exp(A_r + B_r *rT1)

def HWMean_r(P0T,lambd,eta,T):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = HW_theta(lambd,eta,P0T)
    zGrid = np.linspace(0.0,T,2500)
    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))
    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)
    return r_mean

def HW_r_0(P0T,lambd,eta):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    return r0

def HW_Mu_FrwdMeasure(P0T,lambd,eta,T):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = HW_theta(lambd,eta,P0T)
    zGrid = np.linspace(0.0,T,500)
    
    theta_hat =lambda t,T:  theta(t) + eta*eta / lambd *1.0/lambd * (np.exp(-lambd*(T-t))-1.0)
    
    temp =lambda z: theta_hat(z,T) * np.exp(-lambd*(T-z))
    
    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)
    
    return r_mean

def HWVar_r(lambd,eta,T):
    return eta*eta/(2.0*lambd) *( 1.0-np.exp(-2.0*lambd *T))

def HWDensity(P0T,lambd,eta,T):
    r_mean = HWMean_r(P0T,lambd,eta,T)
    r_var = HWVar_r(lambd,eta,T)
    return lambda x: st.norm.pdf(x,r_mean,np.sqrt(r_var))

def HW_CapletFloorletPrice(CP,N,K,lambd,eta,P0T,T1,T2):
    if CP == OptionType.CALL:
        N_new = N * (1.0+(T2-T1)*K)
        K_new = 1.0 + (T2-T1)*K
        caplet = N_new*HW_ZCB_CallPutPrice(OptionType.PUT,1.0/K_new,lambd,eta,P0T,T1,T2)
        value= caplet
    elif CP==OptionType.PUT:
        value = 0.0 # In the homework assignmnet you need to fill this part in
    return value
    
def HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    
    mu_r = HW_Mu_FrwdMeasure(P0T,lambd,eta,T1)
    v_r =  np.sqrt(HWVar_r(lambd,eta,T1))
    
    K_hat = K * np.exp(-A_r)
    
    a = (np.log(K_hat) - B_r*mu_r)/(B_r*v_r)
    
    d1 = a - B_r*v_r
    d2 = d1 +  B_r*v_r
    
    term1 = np.exp(0.5* B_r*B_r*v_r*v_r + B_r*mu_r)*st.norm.cdf(d1) - K_hat * st.norm.cdf(d2)    
    value =P0T(T1) * np.exp(A_r) * term1 
    
    if CP == OptionType.CALL:
        return value
    elif CP==OptionType.PUT:
        return value - P0T(T2) + K*P0T(T1)


def mainCalculation():
    CP= OptionType.CALL
    NoOfPaths = 20000
    NoOfSteps = 1000
        
    lambd     = 0.02

    eta       = 0.02
    
    # We define a ZCB curve (obtained from the market)
    P0T = lambda T: np.exp(-0.1*T)#np.exp(-0.03*T*T-0.1*T)
    r0 = HW_r_0(P0T,lambd,eta)
    
    # In this experiment we compare ZCB from the Market and Analytical expression
    N = 25
    T_end = 50
    Tgrid= np.linspace(0,T_end,N)
    
    Exact = np.zeros([N,1])
    Proxy= np.zeros ([N,1])
    for i,Ti in enumerate(Tgrid):
        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)
        Exact[i] = P0T(Ti)
        
    plt.figure(1)
    plt.grid()
    plt.plot(Tgrid,Exact,'-k')
    plt.plot(Tgrid,Proxy,'--r')
    plt.legend(["Analytcal ZCB","Monte Carlo ZCB"])
    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')

    # In this experiment we compare Monte Carlo results for 
    T1 = 4.0
    T2 = 8.0
    
    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T1 ,P0T, lambd, eta)
    r = paths["R"]
    timeGrid = paths["time"]
    dt = timeGrid[1]-timeGrid[0]
    
    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    
    M_t = np.zeros([NoOfPaths,NoOfSteps])
    for i in range(0,NoOfPaths):
        M_t[i,:] = np.exp(np.cumsum(r[i,:-1])*dt)
        
    KVec = np.linspace(0.01,1.7,50)
    Price_MC_V = np.zeros([len(KVec),1])
    Price_Th_V =np.zeros([len(KVec),1])
    P_T1_T2 = HW_ZCB(lambd,eta,P0T,T1,T2,r[:,-1])
    for i,K in enumerate(KVec):
        if CP==OptionType.CALL:
            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(P_T1_T2-K,0.0)) 
        elif CP==OptionType.PUT:
            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(K-P_T1_T2,0.0)) 
        Price_Th_V[i] =HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2)#HW_ZCB_CallPrice(K,lambd,eta,P0T,T1,T2)
        
    plt.figure(2)
    plt.grid()
    plt.plot(KVec,Price_MC_V)
    plt.plot(KVec,Price_Th_V,'--r')
    plt.legend(['Monte Carlo','Theoretical'])
    plt.title('Option on ZCB')

    # Effect of the HW model parameters on Implied Volatilities
    # define a forward rate between T1 and T2
    frwd = 1.0/(T2-T1) *(P0T(T1)/P0T(T2)-1.0)
    K = np.linspace(frwd/2.0,3.0*frwd,25)
    Notional = 1.0
    
    capletPrice = np.zeros(len(K))
    for idx in range(0,len(K)):
           capletPrice[idx] = HW_CapletFloorletPrice(CP,Notional,K[idx],lambd,eta,P0T,T1,T2)
           
    plt.figure(3)
    plt.title('Caplet Price')
    plt.plot(K,capletPrice)
    plt.xlabel('strike')
    plt.ylabel('Caplet Price')
    plt.grid()
    
    
mainCalculation()

================================================
FILE: Lecture 05-Interest Rate Products/Materials/HW_OptionsOnZCBs.py
================================================
#%%
"""
Created on July 12 2021
Options on ZCBs under the Hull-White model

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import enum 
import matplotlib.pyplot as plt
import scipy.stats as st
import scipy.integrate as integrate
from scipy import interpolate

# This class defines puts and calls
class OptionType(enum.Enum):
    CALL = 1.0
    PUT = -1.0

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    #print("CHANGED THETA")
    return theta#lambda t: 0.1+t-t
    
def HW_A(lambd,eta,P0T,T1,T2):
    tau = T2-T1
    zGrid = np.linspace(0.0,tau,250)
    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)
    theta = HW_theta(lambd,eta,P0T)    
    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)
    
    temp2 = eta*eta/(4.0*np.power(lambd,3.0)) * (np.exp(-2.0*lambd*tau)*(4*np.exp(lambd*tau)-1.0) -3.0) + eta*eta*tau/(2.0*lambd*lambd)
    
    return temp1 + temp2

def HW_B(lambd,eta,T1,T2):
    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)

def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    return np.exp(A_r + B_r *rT1)

def HWMean_r(P0T,lambd,eta,T):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = HW_theta(lambd,eta,P0T)
    zGrid = np.linspace(0.0,T,2500)
    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))
    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)
    return r_mean

def HW_r_0(P0T,lambd,eta):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    return r0

def HW_Mu_FrwdMeasure(P0T,lambd,eta,T):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = HW_theta(lambd,eta,P0T)
    zGrid = np.linspace(0.0,T,500)
    
    theta_hat =lambda t,T:  theta(t) + eta*eta / lambd *1.0/lambd * (np.exp(-lambd*(T-t))-1.0)
    
    temp =lambda z: theta_hat(z,T) * np.exp(-lambd*(T-z))
    
    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)
    
    return r_mean

def HWVar_r(lambd,eta,T):
    return eta*eta/(2.0*lambd) *( 1.0-np.exp(-2.0*lambd *T))

def HWDensity(P0T,lambd,eta,T):
    r_mean = HWMean_r(P0T,lambd,eta,T)
    r_var = HWVar_r(lambd,eta,T)
    return lambda x: st.norm.pdf(x,r_mean,np.sqrt(r_var))

def HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    
    mu_r = HW_Mu_FrwdMeasure(P0T,lambd,eta,T1)
    v_r =  np.sqrt(HWVar_r(lambd,eta,T1))
    
    K_hat = K * np.exp(-A_r)
    
    a = (np.log(K_hat) - B_r*mu_r)/(B_r*v_r)
    
    d1 = a - B_r*v_r
    d2 = d1 +  B_r*v_r
    
    term1 = np.exp(0.5* B_r*B_r*v_r*v_r + B_r*mu_r)*st.norm.cdf(d1) - K_hat * st.norm.cdf(d2)    
    value =P0T(T1) * np.exp(A_r) * term1 
    
    if CP == OptionType.CALL:
        return value
    elif CP==OptionType.PUT:
        return value - P0T(T2) + K*P0T(T1)

def mainCalculation():
    CP= OptionType.CALL
    NoOfPaths = 20000
    NoOfSteps = 1000
        
    lambd     = 0.02
    eta       = 0.02
    
    # We define a ZCB curve (obtained from the market)
    # We define a ZCB curve (obtained from the market), 1-D points with a B-spline curve.
    ti = [0.0,0.00273972600000000,0.0876712330000000,0.172602740000000,0.257534247000000,0.342465753000000,0.427397260000000,0.512328767000000,0.597260274000000,0.682191781000000,0.767123288000000,0.852054795000000,0.936986301000000,1.02191780800000,1.10684931500000,1.19178082200000,1.27671232900000,1.36164383600000,1.44657534200000,1.53150684900000,1.61643835600000,1.70136986300000,1.78630137000000,1.87123287700000,1.95616438400000,2.04109589000000,2.12602739700000,2.21095890400000,2.29589041100000,2.38082191800000,2.46575342500000,2.55068493200000,2.63561643800000,2.72054794500000,2.80547945200000,2.89041095900000,2.97534246600000,3.06027397300000,3.14520547900000,3.23013698600000,3.31506849300000,3.40000000000000,3.48493150700000,3.56986301400000,3.65479452100000,3.73972602700000,3.82465753400000,3.90958904100000,3.99452054800000,4.07945205500000,4.16438356200000,4.24931506800000,4.33424657500000,4.41917808200000,4.50410958900000,4.58904109600000,4.67397260300000,4.75890411000000,4.84383561600000,4.92876712300000,5.01369863000000,5.09863013700000,5.18356164400000,5.26849315100000,5.35342465800000,5.43835616400000,5.52328767100000,5.60821917800000,5.69315068500000,5.77808219200000,5.86301369900000,5.94794520500000,6.03287671200000,6.11780821900000,6.20273972600000,6.28767123300000,6.37260274000000,6.45753424700000,6.54246575300000,6.62739726000000,6.71232876700000,6.79726027400000,6.88219178100000,6.96712328800000,7.05205479500000,7.13698630100000,7.22191780800000,7.30684931500000,7.39178082200000,7.47671232900000,7.56164383600000,7.64657534200000,7.73150684900000,7.81643835600000,7.90136986300000,7.98630137000000,8.07123287700000,8.15616438400000,8.24109589000000,8.32602739700000,8.41095890400000,8.49589041100000,8.58082191800000,8.66575342500000,8.75068493200000,8.83561643800000,8.92054794500000,9.00547945200000,9.09041095900000,9.17534246600000,9.26027397300000,9.34520547900000,9.43013698600000,9.51506849300000,9.60000000000000,9.68493150700000,9.76986301400000,9.85479452100000,9.93972602700000,10.0246575300000,10.1095890400000,10.1945205500000,10.2794520500000,10.3643835600000,10.4493150700000,10.5342465800000,10.6191780800000,10.7041095900000,10.7890411000000,10.8739726000000,10.9589041100000,11.0438356200000,11.1287671200000,11.2136986300000,11.2986301400000,11.3835616400000,11.4684931500000,11.5534246600000,11.6383561600000,11.7232876700000,11.8082191800000,11.8931506800000,11.9780821900000,12.0630137000000,12.1479452100000,12.2328767100000,12.3178082200000,12.4027397300000,12.4876712300000,12.5726027400000,12.6575342500000,12.7424657500000,12.8273972600000,12.9123287700000,12.9972602700000,13.0821917800000,13.1671232900000,13.2520547900000,13.3369863000000,13.4219178100000,13.5068493200000,13.5917808200000,13.6767123300000,13.7616438400000,13.8465753400000,13.9315068500000,14.0164383600000,14.1013698600000,14.1863013700000,14.2712328800000,14.3561643800000,14.4410958900000,14.5260274000000,14.6109589000000,14.6958904100000,14.7808219200000,14.8657534200000,14.9506849300000,15.0356164400000,15.1205479500000,15.2054794500000,15.2904109600000,15.3753424700000,15.4602739700000,15.5452054800000,15.6301369900000,15.7150684900000,15.8000000000000,15.8849315100000,15.9698630100000,16.0547945200000,16.1397260300000,16.2246575300000,16.3095890400000,16.3945205500000,16.4794520500000,16.5643835600000,16.6493150700000,16.7342465800000,16.8191780800000,16.9041095900000,16.9890411000000,17.0739726000000,17.1589041100000,17.2438356200000,17.3287671200000,17.4136986300000,17.4986301400000,17.5835616400000,17.6684931500000,17.7534246600000,17.8383561600000,17.9232876700000,18.0082191800000,18.0931506800000,18.1780821900000,18.2630137000000,18.3479452100000,18.4328767100000,18.5178082200000,18.6027397300000,18.6876712300000,18.7726027400000,18.8575342500000,18.9424657500000,19.0273972600000,19.1123287700000,19.1972602700000,19.2821917800000,19.3671232900000,19.4520547900000,19.5369863000000,19.6219178100000,19.7068493200000,19.7917808200000,19.8767123300000,19.9616438400000,20.0465753400000,20.1315068500000,20.2164383600000,20.3013698600000,20.3863013700000,20.4712328800000,20.5561643800000,20.6410958900000,20.7260274000000,20.8109589000000,20.8958904100000,20.9808219200000,21.0657534200000,21.1506849300000,21.2356164400000,21.3205479500000,21.4054794500000,21.4904109600000,21.5753424700000,21.6602739700000,21.7452054800000,21.8301369900000,21.9150684900000,22,22.0849315100000,22.1698630100000,22.2547945200000,22.3397260300000,22.4246575300000,22.5095890400000,22.5945205500000,22.6794520500000,22.7643835600000,22.8493150700000,22.9342465800000,23.0191780800000,23.1041095900000,23.1890411000000,23.2739726000000,23.3589041100000,23.4438356200000,23.5287671200000,23.6136986300000,23.6986301400000,23.7835616400000,23.8684931500000,23.9534246600000,24.0383561600000,24.1232876700000,24.2082191800000,24.2931506800000,24.3780821900000,24.4630137000000,24.5479452100000,24.6328767100000,24.7178082200000,24.8027397300000,24.8876712300000,24.9726027400000,25.0575342500000,25.1424657500000,25.2273972600000,25.3123287700000,25.3972602700000,25.4821917800000,25.5671232900000,25.6520547900000,25.7369863000000,25.8219178100000,25.9068493200000,25.9917808200000,26.0767123300000,26.1616438400000,26.2465753400000,26.3315068500000,26.4164383600000,26.5013698600000,26.5863013700000,26.6712328800000,26.7561643800000,26.8410958900000,26.9260274000000,27.0109589000000,27.0958904100000,27.1808219200000,27.2657534200000,27.3506849300000,27.4356164400000,27.5205479500000,27.6054794500000,27.6904109600000,27.7753424700000,27.8602739700000,27.9452054800000,28.0301369900000,28.1150684900000,28.2000000000000,28.2849315100000,28.3698630100000,28.4547945200000,28.5397260300000,28.6246575300000,28.7095890400000,28.7945205500000,28.8794520500000,28.9643835600000,29.0493150700000,29.1342465800000,29.2191780800000,29.3041095900000,29.3890411000000,29.4739726000000,29.5589041100000,29.6438356200000,29.7287671200000,29.8136986300000,29.8986301400000,29.9835616400000,30.0684931500000,30.1534246600000,30.2383561600000,30.3232876700000,30.4082191800000,30.4931506800000,30.5780821900000,30.6630137000000,30.7479452100000,30.8328767100000,30.9178082200000,31.0027397300000,31.0876712300000,31.1726027400000,31.2575342500000,31.3424657500000,31.4273972600000,31.5123287700000,31.5972602700000,31.6821917800000,31.7671232900000,31.8520547900000,31.9369863000000,32.0219178100000,32.1068493200000,32.1917808200000,32.2767123300000,32.3616438400000,32.4465753400000,32.5315068500000,32.6164383600000,32.7013698600000,32.7863013700000,32.8712328800000,32.9561643800000,33.0410958900000,33.1260274000000,33.2109589000000,33.2958904100000,33.3808219200000,33.4657534200000,33.5506849300000,33.6356164400000,33.7205479500000,33.8054794500000,33.8904109600000,33.9753424700000,34.0602739700000,34.1452054800000,34.2301369900000,34.3150684900000,34.4000000000000,34.4849315100000,34.5698630100000,34.6547945200000,34.7397260300000,34.8246575300000,34.9095890400000,34.9945205500000,35.0794520500000,35.1643835600000,35.2493150700000,35.3342465800000,35.4191780800000,35.5041095900000,35.5890411000000,35.6739726000000,35.7589041100000,35.8438356200000,35.9287671200000,36.0136986300000,36.0986301400000,36.1835616400000,36.2684931500000,36.3534246600000,36.4383561600000,36.5232876700000,36.6082191800000,36.6931506800000,36.7780821900000,36.8630137000000,36.9479452100000,37.0328767100000,37.1178082200000,37.2027397300000,37.2876712300000,37.3726027400000,37.4575342500000,37.5424657500000,37.6273972600000,37.7123287700000,37.7972602700000,37.8821917800000,37.9671232900000,38.0520547900000,38.1369863000000,38.2219178100000,38.3068493200000,38.3917808200000,38.4767123300000,38.5616438400000,38.6465753400000,38.7315068500000,38.8164383600000,38.9013698600000,38.9863013700000,39.0712328800000,39.1561643800000,39.2410958900000,39.3260274000000,39.4109589000000,39.4958904100000,39.5808219200000,39.6657534200000,39.7506849300000,39.8356164400000,39.9205479500000,40.0054794500000]
    Pi = [1.0,0.999966573000000,0.998930882000000,0.997824062000000,0.996511145000000,0.995199956000000,0.993821602000000,0.992277014000000,0.990734827000000,0.989164324000000,0.987428762000000,0.985704346000000,0.983946708000000,0.982068207000000,0.980193293000000,0.978281187000000,0.976255832000000,0.974234670000000,0.972174514000000,0.970028236000000,0.967886697000000,0.965693800000000,0.963440984000000,0.961193424000000,0.958903753000000,0.956575247000000,0.954252397000000,0.951842433000000,0.949228406000000,0.946482248000000,0.943632525000000,0.940707509000000,0.937735160000000,0.934743101000000,0.931758611000000,0.928808623000000,0.925919731000000,0.923110403000000,0.920338655000000,0.917589739000000,0.914858924000000,0.912141532000000,0.909432941000000,0.906728583000000,0.904023944000000,0.901314567000000,0.898596044000000,0.895864025000000,0.893114214000000,0.890346348000000,0.887569841000000,0.884786140000000,0.881996090000000,0.879200528000000,0.876400278000000,0.873596157000000,0.870788975000000,0.867979528000000,0.865168605000000,0.862356987000000,0.859545442000000,0.856732322000000,0.853915005000000,0.851094371000000,0.848271318000000,0.845446732000000,0.842621487000000,0.839796449000000,0.836972470000000,0.834150393000000,0.831331050000000,0.828515261000000,0.825703790000000,0.822893949000000,0.820084325000000,0.817275636000000,0.814468590000000,0.811663887000000,0.808862219000000,0.806064266000000,0.803270701000000,0.800482187000000,0.797699379000000,0.794922921000000,0.792153338000000,0.789389717000000,0.786631547000000,0.783878633000000,0.781130782000000,0.778387801000000,0.775649505000000,0.772915708000000,0.770186227000000,0.767460882000000,0.764739497000000,0.762021898000000,0.759308200000000,0.756599781000000,0.753896558000000,0.751198194000000,0.748504356000000,0.745814715000000,0.743128950000000,0.740446741000000,0.737767776000000,0.735091745000000,0.732418345000000,0.729747277000000,0.727078670000000,0.724413443000000,0.721751645000000,0.719093279000000,0.716438346000000,0.713786850000000,0.711138792000000,0.708494177000000,0.705853006000000,0.703215284000000,0.700581015000000,0.697950202000000,0.695320544000000,0.692690217000000,0.690059929000000,0.687430378000000,0.684802253000000,0.682176235000000,0.679552992000000,0.676933185000000,0.674317462000000,0.671706465000000,0.669100824000000,0.666501159000000,0.663908082000000,0.661322194000000,0.658744089000000,0.656174348000000,0.653613545000000,0.651062245000000,0.648521002000000,0.645990363000000,0.643470865000000,0.640963036000000,0.638467395000000,0.635983411000000,0.633503959000000,0.631028021000000,0.628556105000000,0.626088714000000,0.623626343000000,0.621169478000000,0.618718599000000,0.616274178000000,0.613836679000000,0.611406558000000,0.608984265000000,0.606570242000000,0.604164924000000,0.601768738000000,0.599382105000000,0.597005438000000,0.594639144000000,0.592283621000000,0.589939262000000,0.587606453000000,0.585285574000000,0.582976996000000,0.580681085000000,0.578398202000000,0.576128698000000,0.573872922000000,0.571631214000000,0.569403908000000,0.567191334000000,0.564993814000000,0.562811666000000,0.560645202000000,0.558494727000000,0.556360543000000,0.554242913000000,0.552138115000000,0.550043470000000,0.547959012000000,0.545884776000000,0.543820795000000,0.541767101000000,0.539723726000000,0.537690699000000,0.535668051000000,0.533655809000000,0.531654001000000,0.529662652000000,0.527681790000000,0.525711438000000,0.523751620000000,0.521802360000000,0.519863678000000,0.517935598000000,0.516018137000000,0.514111318000000,0.512215157000000,0.510329674000000,0.508454885000000,0.506590807000000,0.504737456000000,0.502894847000000,0.501062994000000,0.499241910000000,0.497431609000000,0.495632103000000,0.493843403000000,0.492065521000000,0.490298466000000,0.488542249000000,0.486796879000000,0.485062364000000,0.483338711000000,0.481625929000000,0.479924024000000,0.478233003000000,0.476552870000000,0.474883632000000,0.473225293000000,0.471577858000000,0.469941330000000,0.468315712000000,0.466701007000000,0.465097218000000,0.463504347000000,0.461922395000000,0.460351364000000,0.458791253000000,0.457242065000000,0.455703798000000,0.454176452000000,0.452660027000000,0.451154522000000,0.449659935000000,0.448176257000000,0.446703083000000,0.445240105000000,0.443787209000000,0.442344286000000,0.440911226000000,0.439487920000000,0.438074261000000,0.436670143000000,0.435275460000000,0.433890109000000,0.432513986000000,0.431146989000000,0.429789017000000,0.428439971000000,0.427099751000000,0.425768260000000,0.424445399000000,0.423131074000000,0.421825189000000,0.420527650000000,0.419238363000000,0.417957237000000,0.416684180000000,0.415419101000000,0.414161910000000,0.412912520000000,0.411670842000000,0.410436789000000,0.409210275000000,0.407991213000000,0.406779521000000,0.405575114000000,0.404377909000000,0.403187824000000,0.402004777000000,0.400828688000000,0.399659478000000,0.398497066000000,0.397341375000000,0.396192326000000,0.395049844000000,0.393913852000000,0.392784274000000,0.391661036000000,0.390544064000000,0.389433283000000,0.388328623000000,0.387230010000000,0.386137373000000,0.385050641000000,0.383969745000000,0.382894615000000,0.381825182000000,0.380761378000000,0.379703135000000,0.378650387000000,0.377603067000000,0.376561110000000,0.375524516000000,0.374495509000000,0.373475055000000,0.372462955000000,0.371459016000000,0.370463043000000,0.369474847000000,0.368494238000000,0.367521029000000,0.366555034000000,0.365596072000000,0.364643959000000,0.363698516000000,0.362759566000000,0.361826932000000,0.360900441000000,0.359979918000000,0.359065194000000,0.358156099000000,0.357252465000000,0.356354126000000,0.355460918000000,0.354572678000000,0.353689243000000,0.352810455000000,0.351936156000000,0.351066187000000,0.350200393000000,0.349338622000000,0.348480719000000,0.347626535000000,0.346775919000000,0.345928722000000,0.345084799000000,0.344244003000000,0.343406190000000,0.342571217000000,0.341738943000000,0.340909228000000,0.340081932000000,0.339256917000000,0.338434048000000,0.337613190000000,0.336794207000000,0.335976969000000,0.335161343000000,0.334347200000000,0.333534411000000,0.332722847000000,0.331912384000000,0.331102895000000,0.330294257000000,0.329486347000000,0.328679044000000,0.327872227000000,0.327065778000000,0.326259578000000,0.325453511000000,0.324647462000000,0.323841447000000,0.323037558000000,0.322236538000000,0.321438361000000,0.320642997000000,0.319850421000000,0.319060604000000,0.318273519000000,0.317489140000000,0.316707441000000,0.315928394000000,0.315151974000000,0.314378155000000,0.313606911000000,0.312838216000000,0.312072045000000,0.311308373000000,0.310547175000000,0.309788426000000,0.309032102000000,0.308278178000000,0.307526629000000,0.306777432000000,0.306030564000000,0.305285999000000,0.304543716000000,0.303803690000000,0.303065898000000,0.302330318000000,0.301596926000000,0.300865701000000,0.300136619000000,0.299409659000000,0.298684798000000,0.297962014000000,0.297241286000000,0.296522593000000,0.295805912000000,0.295091222000000,0.294378503000000,0.293667734000000,0.292958894000000,0.292251962000000,0.291546917000000,0.290843741000000,0.290142412000000,0.289442910000000,0.288745217000000,0.288049311000000,0.287355175000000,0.286662788000000,0.285972131000000,0.285283185000000,0.284595932000000,0.283910353000000,0.283226429000000,0.282544142000000,0.281863473000000,0.281184405000000,0.280506920000000,0.279830999000000,0.279156625000000,0.278483781000000,0.277812449000000,0.277142612000000,0.276474253000000,0.275807354000000,0.275141900000000,0.274477873000000,0.273815257000000,0.273154036000000,0.272494193000000,0.271835712000000,0.271178577000000,0.270522772000000,0.269868283000000,0.269215092000000,0.268563184000000,0.267912545000000,0.267263159000000,0.266615011000000,0.265968086000000,0.265322370000000,0.264677847000000,0.264034503000000,0.263392324000000,0.262751295000000,0.262111403000000,0.261472633000000,0.260834972000000,0.260198405000000,0.259562919000000,0.258928501000000,0.258295136000000,0.257662813000000,0.257031517000000,0.256401236000000,0.255771957000000,0.255143667000000,0.254516353000000,0.253890002000000,0.253264603000000,0.252640144000000,0.252016611000000,0.251393992000000,0.250772277000000,0.250151453000000,0.249531508000000,0.248912431000000,0.248294210000000,0.247676834000000,0.247060291000000,0.246444571000000,0.245829663000000,0.245215555000000,0.244602237000000,0.243989698000000,0.243377928000000]
    interpolator =  interpolate.splrep(ti, Pi, s=0.0001)
    P0T = lambda T: interpolate.splev(T, interpolator, der=0)
    r0 = HW_r_0(P0T,lambd,eta)
    
    # In this experiment we compare ZCB from the Market and Analytical expression
    N = 25
    T_end = 50
    Tgrid= np.linspace(0,T_end,N)
    
    Exact = np.zeros([N,1])
    Proxy= np.zeros ([N,1])
    for i,Ti in enumerate(Tgrid):
        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)
        Exact[i] = P0T(Ti)
        
    plt.figure(1)
    plt.grid()
    plt.plot(Tgrid,Exact,'-k')
    plt.plot(Tgrid,Proxy,'--r')
    plt.legend(["Analytcal ZCB","Monte Carlo ZCB"])
    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')

    # In this experiment we compare Monte Carlo results for 
    T1 = 4.0
    T2 = 8.0
    
    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T1 ,P0T, lambd, eta)
    r = paths["R"]
    timeGrid = paths["time"]
    dt = timeGrid[1]-timeGrid[0]
    
    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    
    M_t = np.zeros([NoOfPaths,NoOfSteps])
    for i in range(0,NoOfPaths):
        M_t[i,:] = np.exp(np.cumsum(r[i,:-1])*dt)
        
    KVec = np.linspace(0.01,1.7,50)
    Price_MC_V = np.zeros([len(KVec),1])
    Price_Th_V =np.zeros([len(KVec),1])
    P_T1_T2 = HW_ZCB(lambd,eta,P0T,T1,T2,r[:,-1])
    for i,K in enumerate(KVec):
        if CP==OptionType.CALL:
            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(P_T1_T2-K,0.0)) 
        elif CP==OptionType.PUT:
            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(K-P_T1_T2,0.0)) 
        Price_Th_V[i] =HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2)
        
    plt.figure(2)
    plt.grid()
    plt.plot(KVec,Price_MC_V)
    plt.plot(KVec,Price_Th_V,'--r')
    plt.legend(['Monte Carlo','Theoretical'])
    plt.title('Call Option on ZCB')
    plt.xlabel('Strike')
    plt.ylabel('Option value')

mainCalculation()

================================================
FILE: Lecture 05-Interest Rate Products/Materials/Swaps_HW.py
================================================
#%%
"""
Created on July 12 2021
Swaps computed from a yield curve and from the Hull-White Model

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""

import numpy as np
import enum 
import matplotlib.pyplot as plt
import scipy.integrate as integrate
from scipy import interpolate
from scipy.optimize import newton

# This class defines puts and calls
class OptionTypeSwap(enum.Enum):
    RECEIVER = 1.0
    PAYER = -1.0

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    #print("CHANGED THETA")
    return theta#lambda t: 0.1+t-t
    
def HW_A(lambd,eta,P0T,T1,T2):
    tau = T2-T1
    zGrid = np.linspace(0.0,tau,250)
    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)
    theta = HW_theta(lambd,eta,P0T)    
    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)
    
    temp2 = eta*eta/(4.0*np.power(lambd,3.0)) * (np.exp(-2.0*lambd*tau)*(4*np.exp(lambd*tau)-1.0) -3.0) + eta*eta*tau/(2.0*lambd*lambd)
    
    return temp1 + temp2

def HW_B(lambd,eta,T1,T2):
    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)

def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
    n = np.size(rT1) 
        
    if T1<T2:
        B_r = HW_B(lambd,eta,T1,T2)
        A_r = HW_A(lambd,eta,P0T,T1,T2)
        return np.exp(A_r + B_r *rT1)
    else:
        return np.ones([n])

def HW_r_0(P0T,lambd,eta):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    return r0

def SwapPrice(CP,notional,K,t,Ti,Tm,n,P0T):
    # CP- payer of receiver
    # n- notional
    # K- strike
    # t- today's date
    # Ti- beginning of the swap
    # Tm- end of Swap
    # n- number of dates payments between Ti and Tm
    # r_t -interest rate at time t

    if n == 1:
        ti_grid =np.array([Ti,Tm])
    else:
        ti_grid = np.linspace(Ti,Tm,n)
    tau = ti_grid[1]- ti_grid[0]
    
    # overwrite Ti if t>Ti
    prevTi = ti_grid[np.where(ti_grid<t)]
    if np.size(prevTi) > 0: #prevTi != []:
        Ti = prevTi[-1]
    
    # Now we need to handle the case when some payments are already done
    ti_grid = ti_grid[np.where(ti_grid>t)]          

    temp= 0.0
    
    for (idx,ti) in enumerate(ti_grid):
        if ti>Ti:
            temp = temp + tau * P0T(ti)

    if CP==OptionTypeSwap.PAYER:
        swap = (P0T(Ti) - P0T(Tm)) - K * temp
    elif CP==OptionTypeSwap.RECEIVER:
        swap = K * temp- (P0T(Ti) - P0T(Tm))
    
    return swap*notional

def HW_SwapPrice(CP,notional,K,t,Ti,Tm,n,r_t,P0T,lambd,eta):
    # CP- payer of receiver
    # n- notional
    # K- strike
    # t- today's date
    # Ti- beginning of the swap
    # Tm- end of Swap
    # n- number of dates payments between Ti and Tm
    # r_t -interest rate at time t

    if n == 1:
        ti_grid =np.array([Ti,Tm])
    else:
        ti_grid = np.linspace(Ti,Tm,n)
    tau = ti_grid[1]- ti_grid[0]
    
    # overwrite Ti if t>Ti
    prevTi = ti_grid[np.where(ti_grid<t)]
    if np.size(prevTi) > 0: #prevTi != []:
        Ti = prevTi[-1]
    
    # Now we need to handle the case when some payments are already done
    ti_grid = ti_grid[np.where(ti_grid>t)]          

    temp= np.zeros(np.size(r_t));
    
    P_t_TiLambda = lambda Ti : HW_ZCB(lambd,eta,P0T,t,Ti,r_t)
    
    for (idx,ti) in enumerate(ti_grid):
        if ti>Ti:
            temp = temp + tau * P_t_TiLambda(ti)
            
    P_t_Ti = P_t_TiLambda(Ti)
    P_t_Tm = P_t_TiLambda(Tm)
    
    if CP==OptionTypeSwap.PAYER:
        swap = (P_t_Ti - P_t_Tm) - K * temp
    elif CP==OptionTypeSwap.RECEIVER:
        swap = K * temp- (P_t_Ti - P_t_Tm)
    
    return swap*notional

def mainCalculation():
    NoOfPaths = 2000
    NoOfSteps = 1000
    CP = OptionTypeSwap.PAYER
    lambd     = 0.5
    eta       = 0.03
    notional  = 10000.0 
    
    # We define a ZCB curve (obtained from the market), 1-D points with a B-spline curve.
    ti = [0.0,0.00273972600000000,0.0876712330000000,0.172602740000000,0.257534247000000,0.342465753000000,0.427397260000000,0.512328767000000,0.597260274000000,0.682191781000000,0.767123288000000,0.852054795000000,0.936986301000000,1.02191780800000,1.10684931500000,1.19178082200000,1.27671232900000,1.36164383600000,1.44657534200000,1.53150684900000,1.61643835600000,1.70136986300000,1.78630137000000,1.87123287700000,1.95616438400000,2.04109589000000,2.12602739700000,2.21095890400000,2.29589041100000,2.38082191800000,2.46575342500000,2.55068493200000,2.63561643800000,2.72054794500000,2.80547945200000,2.89041095900000,2.97534246600000,3.06027397300000,3.14520547900000,3.23013698600000,3.31506849300000,3.40000000000000,3.48493150700000,3.56986301400000,3.65479452100000,3.73972602700000,3.82465753400000,3.90958904100000,3.99452054800000,4.07945205500000,4.16438356200000,4.24931506800000,4.33424657500000,4.41917808200000,4.50410958900000,4.58904109600000,4.67397260300000,4.75890411000000,4.84383561600000,4.92876712300000,5.01369863000000,5.09863013700000,5.18356164400000,5.26849315100000,5.35342465800000,5.43835616400000,5.52328767100000,5.60821917800000,5.69315068500000,5.77808219200000,5.86301369900000,5.94794520500000,6.03287671200000,6.11780821900000,6.20273972600000,6.28767123300000,6.37260274000000,6.45753424700000,6.54246575300000,6.62739726000000,6.71232876700000,6.79726027400000,6.88219178100000,6.96712328800000,7.05205479500000,7.13698630100000,7.22191780800000,7.30684931500000,7.39178082200000,7.47671232900000,7.56164383600000,7.64657534200000,7.73150684900000,7.81643835600000,7.90136986300000,7.98630137000000,8.07123287700000,8.15616438400000,8.24109589000000,8.32602739700000,8.41095890400000,8.49589041100000,8.58082191800000,8.66575342500000,8.75068493200000,8.83561643800000,8.92054794500000,9.00547945200000,9.09041095900000,9.17534246600000,9.26027397300000,9.34520547900000,9.43013698600000,9.51506849300000,9.60000000000000,9.68493150700000,9.76986301400000,9.85479452100000,9.93972602700000,10.0246575300000,10.1095890400000,10.1945205500000,10.2794520500000,10.3643835600000,10.4493150700000,10.5342465800000,10.6191780800000,10.7041095900000,10.7890411000000,10.8739726000000,10.9589041100000,11.0438356200000,11.1287671200000,11.2136986300000,11.2986301400000,11.3835616400000,11.4684931500000,11.5534246600000,11.6383561600000,11.7232876700000,11.8082191800000,11.8931506800000,11.9780821900000,12.0630137000000,12.1479452100000,12.2328767100000,12.3178082200000,12.4027397300000,12.4876712300000,12.5726027400000,12.6575342500000,12.7424657500000,12.8273972600000,12.9123287700000,12.9972602700000,13.0821917800000,13.1671232900000,13.2520547900000,13.3369863000000,13.4219178100000,13.5068493200000,13.5917808200000,13.6767123300000,13.7616438400000,13.8465753400000,13.9315068500000,14.0164383600000,14.1013698600000,14.1863013700000,14.2712328800000,14.3561643800000,14.4410958900000,14.5260274000000,14.6109589000000,14.6958904100000,14.7808219200000,14.8657534200000,14.9506849300000,15.0356164400000,15.1205479500000,15.2054794500000,15.2904109600000,15.3753424700000,15.4602739700000,15.5452054800000,15.6301369900000,15.7150684900000,15.8000000000000,15.8849315100000,15.9698630100000,16.0547945200000,16.1397260300000,16.2246575300000,16.3095890400000,16.3945205500000,16.4794520500000,16.5643835600000,16.6493150700000,16.7342465800000,16.8191780800000,16.9041095900000,16.9890411000000,17.0739726000000,17.1589041100000,17.2438356200000,17.3287671200000,17.4136986300000,17.4986301400000,17.5835616400000,17.6684931500000,17.7534246600000,17.8383561600000,17.9232876700000,18.0082191800000,18.0931506800000,18.1780821900000,18.2630137000000,18.3479452100000,18.4328767100000,18.5178082200000,18.6027397300000,18.6876712300000,18.7726027400000,18.8575342500000,18.9424657500000,19.0273972600000,19.1123287700000,19.1972602700000,19.2821917800000,19.3671232900000,19.4520547900000,19.5369863000000,19.6219178100000,19.7068493200000,19.7917808200000,19.8767123300000,19.9616438400000,20.0465753400000,20.1315068500000,20.2164383600000,20.3013698600000,20.3863013700000,20.4712328800000,20.5561643800000,20.6410958900000,20.7260274000000,20.8109589000000,20.8958904100000,20.9808219200000,21.0657534200000,21.1506849300000,21.2356164400000,21.3205479500000,21.4054794500000,21.4904109600000,21.5753424700000,21.6602739700000,21.7452054800000,21.8301369900000,21.9150684900000,22,22.0849315100000,22.1698630100000,22.2547945200000,22.3397260300000,22.4246575300000,22.5095890400000,22.5945205500000,22.6794520500000,22.7643835600000,22.8493150700000,22.9342465800000,23.0191780800000,23.1041095900000,23.1890411000000,23.2739726000000,23.3589041100000,23.4438356200000,23.5287671200000,23.6136986300000,23.6986301400000,23.7835616400000,23.8684931500000,23.9534246600000,24.0383561600000,24.1232876700000,24.2082191800000,24.2931506800000,24.3780821900000,24.4630137000000,24.5479452100000,24.6328767100000,24.7178082200000,24.8027397300000,24.8876712300000,24.9726027400000,25.0575342500000,25.1424657500000,25.2273972600000,25.3123287700000,25.3972602700000,25.4821917800000,25.5671232900000,25.6520547900000,25.7369863000000,25.8219178100000,25.9068493200000,25.9917808200000,26.0767123300000,26.1616438400000,26.2465753400000,26.3315068500000,26.4164383600000,26.5013698600000,26.5863013700000,26.6712328800000,26.7561643800000,26.8410958900000,26.9260274000000,27.0109589000000,27.0958904100000,27.1808219200000,27.2657534200000,27.3506849300000,27.4356164400000,27.5205479500000,27.6054794500000,27.6904109600000,27.7753424700000,27.8602739700000,27.9452054800000,28.0301369900000,28.1150684900000,28.2000000000000,28.2849315100000,28.3698630100000,28.4547945200000,28.5397260300000,28.6246575300000,28.7095890400000,28.7945205500000,28.8794520500000,28.9643835600000,29.0493150700000,29.1342465800000,29.2191780800000,29.3041095900000,29.3890411000000,29.4739726000000,29.5589041100000,29.6438356200000,29.7287671200000,29.8136986300000,29.8986301400000,29.9835616400000,30.0684931500000,30.1534246600000,30.2383561600000,30.3232876700000,30.4082191800000,30.4931506800000,30.5780821900000,30.6630137000000,30.7479452100000,30.8328767100000,30.9178082200000,31.0027397300000,31.0876712300000,31.1726027400000,31.2575342500000,31.3424657500000,31.4273972600000,31.5123287700000,31.5972602700000,31.6821917800000,31.7671232900000,31.8520547900000,31.9369863000000,32.0219178100000,32.1068493200000,32.1917808200000,32.2767123300000,32.3616438400000,32.4465753400000,32.5315068500000,32.6164383600000,32.7013698600000,32.7863013700000,32.8712328800000,32.9561643800000,33.0410958900000,33.1260274000000,33.2109589000000,33.2958904100000,33.3808219200000,33.4657534200000,33.5506849300000,33.6356164400000,33.7205479500000,33.8054794500000,33.8904109600000,33.9753424700000,34.0602739700000,34.1452054800000,34.2301369900000,34.3150684900000,34.4000000000000,34.4849315100000,34.5698630100000,34.6547945200000,34.7397260300000,34.8246575300000,34.9095890400000,34.9945205500000,35.0794520500000,35.1643835600000,35.2493150700000,35.3342465800000,35.4191780800000,35.5041095900000,35.5890411000000,35.6739726000000,35.7589041100000,35.8438356200000,35.9287671200000,36.0136986300000,36.0986301400000,36.1835616400000,36.2684931500000,36.3534246600000,36.4383561600000,36.5232876700000,36.6082191800000,36.6931506800000,36.7780821900000,36.8630137000000,36.9479452100000,37.0328767100000,37.1178082200000,37.2027397300000,37.2876712300000,37.3726027400000,37.4575342500000,37.5424657500000,37.6273972600000,37.7123287700000,37.7972602700000,37.8821917800000,37.9671232900000,38.0520547900000,38.1369863000000,38.2219178100000,38.3068493200000,38.3917808200000,38.4767123300000,38.5616438400000,38.6465753400000,38.7315068500000,38.8164383600000,38.9013698600000,38.9863013700000,39.0712328800000,39.1561643800000,39.2410958900000,39.3260274000000,39.4109589000000,39.4958904100000,39.5808219200000,39.6657534200000,39.7506849300000,39.8356164400000,39.9205479500000,40.0054794500000]
    Pi = [1.0,0.999966573000000,0.998930882000000,0.997824062000000,0.996511145000000,0.995199956000000,0.993821602000000,0.992277014000000,0.990734827000000,0.989164324000000,0.987428762000000,0.985704346000000,0.983946708000000,0.982068207000000,0.980193293000000,0.978281187000000,0.976255832000000,0.974234670000000,0.972174514000000,0.970028236000000,0.967886697000000,0.965693800000000,0.963440984000000,0.961193424000000,0.958903753000000,0.956575247000000,0.954252397000000,0.951842433000000,0.949228406000000,0.946482248000000,0.943632525000000,0.940707509000000,0.937735160000000,0.934743101000000,0.931758611000000,0.928808623000000,0.925919731000000,0.923110403000000,0.920338655000000,0.917589739000000,0.914858924000000,0.912141532000000,0.909432941000000,0.906728583000000,0.904023944000000,0.901314567000000,0.898596044000000,0.895864025000000,0.893114214000000,0.890346348000000,0.887569841000000,0.884786140000000,0.881996090000000,0.879200528000000,0.876400278000000,0.873596157000000,0.870788975000000,0.867979528000000,0.865168605000000,0.862356987000000,0.859545442000000,0.856732322000000,0.853915005000000,0.851094371000000,0.848271318000000,0.845446732000000,0.842621487000000,0.839796449000000,0.836972470000000,0.834150393000000,0.831331050000000,0.828515261000000,0.825703790000000,0.822893949000000,0.820084325000000,0.817275636000000,0.814468590000000,0.811663887000000,0.808862219000000,0.806064266000000,0.803270701000000,0.800482187000000,0.797699379000000,0.794922921000000,0.792153338000000,0.789389717000000,0.786631547000000,0.783878633000000,0.781130782000000,0.778387801000000,0.775649505000000,0.772915708000000,0.770186227000000,0.767460882000000,0.764739497000000,0.762021898000000,0.759308200000000,0.756599781000000,0.753896558000000,0.751198194000000,0.748504356000000,0.745814715000000,0.743128950000000,0.740446741000000,0.737767776000000,0.735091745000000,0.732418345000000,0.729747277000000,0.727078670000000,0.724413443000000,0.721751645000000,0.719093279000000,0.716438346000000,0.713786850000000,0.711138792000000,0.708494177000000,0.705853006000000,0.703215284000000,0.700581015000000,0.697950202000000,0.695320544000000,0.692690217000000,0.690059929000000,0.687430378000000,0.684802253000000,0.682176235000000,0.679552992000000,0.676933185000000,0.674317462000000,0.671706465000000,0.669100824000000,0.666501159000000,0.663908082000000,0.661322194000000,0.658744089000000,0.656174348000000,0.653613545000000,0.651062245000000,0.648521002000000,0.645990363000000,0.643470865000000,0.640963036000000,0.638467395000000,0.635983411000000,0.633503959000000,0.631028021000000,0.628556105000000,0.626088714000000,0.623626343000000,0.621169478000000,0.618718599000000,0.616274178000000,0.613836679000000,0.611406558000000,0.608984265000000,0.606570242000000,0.604164924000000,0.601768738000000,0.599382105000000,0.597005438000000,0.594639144000000,0.592283621000000,0.589939262000000,0.587606453000000,0.585285574000000,0.582976996000000,0.580681085000000,0.578398202000000,0.576128698000000,0.573872922000000,0.571631214000000,0.569403908000000,0.567191334000000,0.564993814000000,0.562811666000000,0.560645202000000,0.558494727000000,0.556360543000000,0.554242913000000,0.552138115000000,0.550043470000000,0.547959012000000,0.545884776000000,0.543820795000000,0.541767101000000,0.539723726000000,0.537690699000000,0.535668051000000,0.533655809000000,0.531654001000000,0.529662652000000,0.527681790000000,0.525711438000000,0.523751620000000,0.521802360000000,0.519863678000000,0.517935598000000,0.516018137000000,0.514111318000000,0.512215157000000,0.510329674000000,0.508454885000000,0.506590807000000,0.504737456000000,0.502894847000000,0.501062994000000,0.499241910000000,0.497431609000000,0.495632103000000,0.493843403000000,0.492065521000000,0.490298466000000,0.488542249000000,0.486796879000000,0.485062364000000,0.483338711000000,0.481625929000000,0.479924024000000,0.478233003000000,0.476552870000000,0.474883632000000,0.473225293000000,0.471577858000000,0.469941330000000,0.468315712000000,0.466701007000000,0.465097218000000,0.463504347000000,0.461922395000000,0.460351364000000,0.458791253000000,0.457242065000000,0.455703798000000,0.454176452000000,0.452660027000000,0.451154522000000,0.449659935000000,0.448176257000000,0.446703083000000,0.445240105000000,0.443787209000000,0.442344286000000,0.440911226000000,0.439487920000000,0.438074261000000,0.436670143000000,0.435275460000000,0.433890109000000,0.432513986000000,0.431146989000000,0.429789017000000,0.428439971000000,0.427099751000000,0.425768260000000,0.424445399000000,0.423131074000000,0.421825189000000,0.420527650000000,0.419238363000000,0.417957237000000,0.416684180000000,0.415419101000000,0.414161910000000,0.412912520000000,0.411670842000000,0.410436789000000,0.409210275000000,0.407991213000000,0.406779521000000,0.405575114000000,0.404377909000000,0.403187824000000,0.402004777000000,0.400828688000000,0.399659478000000,0.398497066000000,0.397341375000000,0.396192326000000,0.395049844000000,0.393913852000000,0.392784274000000,0.391661036000000,0.390544064000000,0.389433283000000,0.388328623000000,0.387230010000000,0.386137373000000,0.385050641000000,0.383969745000000,0.382894615000000,0.381825182000000,0.380761378000000,0.379703135000000,0.378650387000000,0.377603067000000,0.376561110000000,0.375524516000000,0.374495509000000,0.373475055000000,0.372462955000000,0.371459016000000,0.370463043000000,0.369474847000000,0.368494238000000,0.367521029000000,0.366555034000000,0.365596072000000,0.364643959000000,0.363698516000000,0.362759566000000,0.361826932000000,0.360900441000000,0.359979918000000,0.359065194000000,0.358156099000000,0.357252465000000,0.356354126000000,0.355460918000000,0.354572678000000,0.353689243000000,0.352810455000000,0.351936156000000,0.351066187000000,0.350200393000000,0.349338622000000,0.348480719000000,0.347626535000000,0.346775919000000,0.345928722000000,0.345084799000000,0.344244003000000,0.343406190000000,0.342571217000000,0.341738943000000,0.340909228000000,0.340081932000000,0.339256917000000,0.338434048000000,0.337613190000000,0.336794207000000,0.335976969000000,0.335161343000000,0.334347200000000,0.333534411000000,0.332722847000000,0.331912384000000,0.331102895000000,0.330294257000000,0.329486347000000,0.328679044000000,0.327872227000000,0.327065778000000,0.326259578000000,0.325453511000000,0.324647462000000,0.323841447000000,0.323037558000000,0.322236538000000,0.321438361000000,0.320642997000000,0.319850421000000,0.319060604000000,0.318273519000000,0.317489140000000,0.316707441000000,0.315928394000000,0.315151974000000,0.314378155000000,0.313606911000000,0.312838216000000,0.312072045000000,0.311308373000000,0.310547175000000,0.309788426000000,0.309032102000000,0.308278178000000,0.307526629000000,0.306777432000000,0.306030564000000,0.305285999000000,0.304543716000000,0.303803690000000,0.303065898000000,0.302330318000000,0.301596926000000,0.300865701000000,0.300136619000000,0.299409659000000,0.298684798000000,0.297962014000000,0.297241286000000,0.296522593000000,0.295805912000000,0.295091222000000,0.294378503000000,0.293667734000000,0.292958894000000,0.292251962000000,0.291546917000000,0.290843741000000,0.290142412000000,0.289442910000000,0.288745217000000,0.288049311000000,0.287355175000000,0.286662788000000,0.285972131000000,0.285283185000000,0.284595932000000,0.283910353000000,0.283226429000000,0.282544142000000,0.281863473000000,0.281184405000000,0.280506920000000,0.279830999000000,0.279156625000000,0.278483781000000,0.277812449000000,0.277142612000000,0.276474253000000,0.275807354000000,0.275141900000000,0.274477873000000,0.273815257000000,0.273154036000000,0.272494193000000,0.271835712000000,0.271178577000000,0.270522772000000,0.269868283000000,0.269215092000000,0.268563184000000,0.267912545000000,0.267263159000000,0.266615011000000,0.265968086000000,0.265322370000000,0.264677847000000,0.264034503000000,0.263392324000000,0.262751295000000,0.262111403000000,0.261472633000000,0.260834972000000,0.260198405000000,0.259562919000000,0.258928501000000,0.258295136000000,0.257662813000000,0.257031517000000,0.256401236000000,0.255771957000000,0.255143667000000,0.254516353000000,0.253890002000000,0.253264603000000,0.252640144000000,0.252016611000000,0.251393992000000,0.250772277000000,0.250151453000000,0.249531508000000,0.248912431000000,0.248294210000000,0.247676834000000,0.247060291000000,0.246444571000000,0.245829663000000,0.245215555000000,0.244602237000000,0.243989698000000,0.243377928000000]
    interpolator =  interpolate.splrep(ti, Pi, s=0.0001)
    P0T = lambda T: interpolate.splev(T, interpolator, der=0)
    
    r0 = HW_r_0(P0T,lambd,eta)
    
    # In this experiment we compare ZCB from the Market and Analytical expression
    N = 25
    T_end = 50
    Tgrid= np.linspace(0,T_end,N)
    
    Exact = np.zeros([N,1])
    Proxy= np.zeros ([N,1])
    for i,Ti in enumerate(Tgrid):
        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)
        Exact[i] = P0T(Ti)
        
    plt.figure(1)
    plt.grid()
    plt.plot(Tgrid,Exact,'-k')
    plt.plot(Tgrid,Proxy,'--r')
    plt.legend(["Analytcal ZCB","Monte Carlo ZCB"])
    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')
    
    
    # Here we simulate the exposure profiles for a swap, using the HW model    
    # Swap settings
    K = np.linspace(-0.1,0.1,25)  # strike
    Ti = 1.0  # begining of the swap
    Tm = 10.0 # end date of the swap 
    n = 10    # number of payments between Ti and Tm
    

    paths= GeneratePathsHWEuler(NoOfPaths, NoOfSteps,Tm + 1.0, P0T, lambd, eta)

    r = paths["R"]
    timeGrid = paths["time"]
    dt = timeGrid[1]-timeGrid[0]
    
    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    
    M_t = np.zeros([NoOfPaths,NoOfSteps])
            
    for i in range(0,NoOfPaths):
        M_t[i,:] = np.exp(np.cumsum(r[i,0:-1])*dt)
        
    # Portfolio without netting    
    VSwapHW = np.zeros(len(K))
    VSwap = np.zeros(len(K))
    t0= 0
    for (idx,Ki) in enumerate(K):
        VHW = HW_SwapPrice(CP,notional,Ki,t0,Ti,Tm,n,r0,P0T,lambd,eta)
        V = SwapPrice(CP,notional,Ki,t0,Ti,Tm,n,P0T) 
        VSwap[idx] = V
        VSwapHW[idx] = VHW[0]
    
    plt.figure(2)
    plt.plot(K,VSwap)
    plt.plot(K,VSwapHW,'--r')
    plt.grid()
    plt.xlabel('strike,K')
    plt.ylabel('Swap value')
    plt.title('Swap pricing')
    
    # Computation for Swap Par
    K=0.0
    print('Swap price for K = 0 is {0}'.format(SwapPrice(CP,notional,K,t0,Ti,Tm,n,P0T)))
    
    # Determine a part swap
    func = lambda k: SwapPrice(CP,notional,k,t0,Ti,Tm,n,P0T)
    K_par = newton(func, 0.0)
    print('Swap price for K_par = {0} is {1}'.format(K_par,SwapPrice(CP,notional,K_par,t0,Ti,Tm,n,P0T)))
    
    
mainCalculation()

================================================
FILE: Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/MultiCurveBuild.py
================================================
#%%
"""
Created on June 27 2021
Construction of a multi- curve for a given set of swap instruments

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import enum 
from copy import deepcopy
import matplotlib.pyplot as plt
from scipy.interpolate import splrep, splev, interp1d

# This class defines puts and calls
class OptionTypeSwap(enum.Enum):
    RECEIVER = 1.0
    PAYER = -1.0
    
def IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):
    # CP- payer of receiver
    # n- notional
    # K- strike
    # t- today's date
    # Ti- beginning of the swap
    # Tm- end of Swap
    # n- number of dates payments between Ti and Tm
    # r_t -interest rate at time t
    ti_grid = np.linspace(Ti,Tm,int(n))
    tau = ti_grid[1]- ti_grid[0]
    
    temp= 0.0
        
    for (idx,ti) in enumerate(ti_grid):
        if idx>0:
            temp = temp + tau * P0T(ti)
            
    P_t_Ti = P0T(Ti)
    P_t_Tm = P0T(Tm)
    
    if CP==OptionTypeSwap.PAYER:
        swap = (P_t_Ti - P_t_Tm) - K * temp
    elif CP==OptionTypeSwap.RECEIVER:
        swap = K * temp - (P_t_Ti - P_t_Tm)
    
    return swap * notional


def IRSwapMultiCurve(CP,notional,K,t,Ti,Tm,n,P0T,P0TFrd):
    # CP- payer of receiver
    # n- notional
    # K- strike
    # t- today's date
    # Ti- beginning of the swap
    # Tm- end of Swap
    # n- number of dates payments between Ti and Tm
    # r_t -interest rate at time t
    ti_grid = np.linspace(Ti,Tm,int(n))
    tau = ti_grid[1]- ti_grid[0]
    
    swap = 0.0
        
    for (idx,ti) in enumerate(ti_grid):
        #L(t_0,t_{k-1},t_{k}) from the forward curve
        if idx>0:
            L_frwd = 1.0/tau * (P0TFrd(ti_grid[idx-1])-P0TFrd(ti_grid[idx])) / P0TFrd(ti_grid[idx])
            swap = swap + tau * P0T(ti_grid[idx]) * (L_frwd - K)
            
    return swap * notional


def P0TModel(t,ti,ri,method):
    rInterp = method(ti,ri)
    r = rInterp(t)
    return np.exp(-r*t)

def YieldCurve(instruments, maturities, r0, method, tol):
    r0 = deepcopy(r0)
    ri = MultivariateNewtonRaphson(r0, maturities, instruments, method, tol=tol)
    return ri

def MultivariateNewtonRaphson(ri, ti, instruments, method, tol):
    err = 10e10
    idx = 0
    while err > tol:
        idx = idx +1
        values = EvaluateInstruments(ti,ri,instruments,method)
        J = Jacobian(ti,ri, instruments, method)
        J_inv = np.linalg.inv(J)
        err = - np.dot(J_inv, values)
        ri[0:] = ri[0:] + err 
        err = np.linalg.norm(err)
        print('index in the loop is',idx,' Error is ', err)
    return ri

def Jacobian(ti, ri, instruments, method):
    eps = 1e-05
    swap_num = len(ti)
    J = np.zeros([swap_num, swap_num])
    val = EvaluateInstruments(ti,ri,instruments,method)
    ri_up = deepcopy(ri)
    
    for j in range(0, len(ri)):
        ri_up[j] = ri[j] + eps  
        val_up = EvaluateInstruments(ti,ri_up,instruments,method)
        ri_up[j] = ri[j]
        dv = (val_up - val) / eps
        J[:, j] = dv[:]
    return J

def EvaluateInstruments(ti,ri,instruments,method):
    P0Ttemp = lambda t: P0TModel(t,ti,ri,method)
    val = np.zeros(len(instruments))
    for i in range(0,len(instruments)):
        val[i] = instruments[i](P0Ttemp)
    return val

def linear_interpolation(ti,ri):
    interpolator = lambda t: np.interp(t, ti, ri)
    return interpolator

def spline_interpolate(ti,ri):
    interpolator = splrep(ti, ri, s=0.01)
    interp = lambda t: splev(t,interpolator)
    return interp

def scipy_1d_interpolate(ti, ri):
    interpolator = lambda t: interp1d(ti, ri, kind='quadratic')(t)
    return interpolator

#def ComputeDelta(instrument,)

def mainCode():
    
    # Convergence tolerance
    tol = 1.0e-15
    # Initial guess for the spine points
    r0 = np.array([0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01])   
    # Interpolation method
    method = linear_interpolation
    
    # Construct swaps that are used for building of the yield curve
    K   = np.array([0.04/100.0,	0.16/100.0,	0.31/100.0,	0.81/100.0,	1.28/100.0,	1.62/100.0,	2.22/100.0,	2.30/100.0])
    mat = np.array([1.0,2.0,3.0,5.0,7.0,10.0,20.0,30.0])
    
    #                   IRSwap(CP,           notional,K,   t,   Ti,Tm,   n,P0T)
    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[0],0.0,0.0,mat[0],4*mat[0],P0T)
    swap2 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[1],0.0,0.0,mat[1],5*mat[1],P0T)
    swap3 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[2],0.0,0.0,mat[2],6*mat[2],P0T)
    swap4 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[3],0.0,0.0,mat[3],7*mat[3],P0T)
    swap5 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[4],0.0,0.0,mat[4],8*mat[4],P0T)
    swap6 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[5],0.0,0.0,mat[5],9*mat[5],P0T)
    swap7 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[6],0.0,0.0,mat[6],10*mat[6],P0T)
    swap8 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[7],0.0,0.0,mat[7],11*mat[7],P0T)
    instruments = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8]
    
    # determine optimal spine points
    ri = YieldCurve(instruments, mat, r0, method, tol)
    print('\n Spine points are',ri,'\n')
    
    # Build a ZCB-curve/yield curve from the spine points
    P0T_Initial = lambda t: P0TModel(t,mat,r0,method)
    P0T = lambda t: P0TModel(t,mat,ri,method)
    
    # price back the swaps
    swapsModel = np.zeros(len(instruments))
    swapsInitial = np.zeros(len(instruments))
    for i in range(0,len(instruments)):
        swapsModel[i] = instruments[i](P0T)
        swapsInitial[i] = instruments[i](P0T_Initial)
    
    print('Prices for Pas Swaps (initial) = ',swapsInitial,'\n')
    print('Prices for Par Swaps = ',swapsModel,'\n')
    
    
    # Multi Curve extension- simple sanity check
    P0TFrd = deepcopy(P0T)
    Ktest = 0.2
    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,Ktest,0.0,0.0,mat[0],4*mat[0],P0T)
    swap1MC = lambda P0T: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,Ktest,0.0,0.0,mat[0],4*mat[0],P0T,P0TFrd)
    print('Sanity check: swap1 = {0}, swap2 = {1}'.format(swap1(P0T),swap1MC(P0T)))
    
    # Forward curve settings
    r0Frwd = np.array([0.01, 0.01, 0.01, 0.01])   
    KFrwd   = np.array([0.09/100.0,	0.26/100.0,	0.37/100.0,	1.91/100.0])
    matFrwd = np.array([1.0, 2.0, 3.0, 5.0])
    
    # At this point we already know P(0,T) for the discount curve
    P0TDiscount = lambda t: P0TModel(t,mat,ri,method)
    swap1Frwd = lambda P0TFrwd: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,KFrwd[0],0.0,0.0,matFrwd[0],4*matFrwd[0],P0TDiscount,P0TFrwd)
    swap2Frwd = lambda P0TFrwd: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,KFrwd[1],0.0,0.0,matFrwd[1],5*matFrwd[1],P0TDiscount,P0TFrwd)
    swap3Frwd = lambda P0TFrwd: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,KFrwd[2],0.0,0.0,matFrwd[2],6*matFrwd[2],P0TDiscount,P0TFrwd)
    swap4Frwd = lambda P0TFrwd: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,KFrwd[3],0.0,0.0,matFrwd[3],7*matFrwd[3],P0TDiscount,P0TFrwd)
    
    instrumentsFrwd = [swap1Frwd,swap2Frwd,swap3Frwd,swap4Frwd]
    
    # determine optimal spine points for the forward curve
    riFrwd = YieldCurve(instrumentsFrwd, matFrwd, r0Frwd, method, tol)
    print('\n Frwd Spine points are',riFrwd,'\n')
    
    # Build a ZCB-curve/yield curve from the spine points
    P0TFrwd_Initial = lambda t: P0TModel(t,matFrwd,r0Frwd,method)
    P0TFrwd         = lambda t: P0TModel(t,matFrwd,riFrwd,method)
    # price back the swaps
    swapsModelFrwd   = np.zeros(len(instrumentsFrwd))
    swapsInitialFrwd = np.zeros(len(instrumentsFrwd))
    
    for i in range(0,len(instrumentsFrwd)):
        swapsModelFrwd[i]   = instrumentsFrwd[i](P0TFrwd)
        swapsInitialFrwd[i] = instrumentsFrwd[i](P0TFrwd_Initial)
    
    print('Prices for Pas Swaps (initial) = ',swapsInitialFrwd,'\n')
    print('Prices for Par Swaps = ',swapsModelFrwd,'\n')
    
    print(swap1Frwd(P0TFrwd))
    
    t = np.linspace(0,10,100)
    plt.figure()
    plt.plot(t,P0TDiscount(t),'--r')
    plt.plot(t,P0TFrwd(t),'-b')
    plt.legend(['discount','forecast'])
    
    return 0.0

mainCode()
    
    

================================================
FILE: Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/YieldCurveBuildGreeks.py
================================================
#%%
"""
Created on June 27 2021
Construction of a yield curve for a given set of swap instruments

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import enum 
from copy import deepcopy
from scipy.interpolate import splrep, splev, interp1d

# This class defines puts and calls
class OptionTypeSwap(enum.Enum):
    RECEIVER = 1.0
    PAYER = -1.0
    
def IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):
    # CP- payer of receiver
    # n- notional
    # K- strike
    # t- today's date
    # Ti- beginning of the swap
    # Tm- end of Swap
    # n- number of dates payments between Ti and Tm
    # r_t -interest rate at time t
    ti_grid = np.linspace(Ti,Tm,int(n))
    tau = ti_grid[1]- ti_grid[0]
    
    # overwrite Ti if t>Ti
    prevTi = ti_grid[np.where(ti_grid<t)]
    if np.size(prevTi) > 0: 
        Ti = prevTi[-1]
    
    # Now we need to handle the case when some payments are already done
    ti_grid = ti_grid[np.where(ti_grid>t)]           

    temp= 0.0
        
    for (idx,ti) in enumerate(ti_grid):
        if ti>Ti:
            temp = temp + tau * P0T(ti)
            
    P_t_Ti = P0T(Ti)
    P_t_Tm = P0T(Tm)
    
    if CP==OptionTypeSwap.PAYER:
        swap = (P_t_Ti - P_t_Tm) - K * temp
    elif CP==OptionTypeSwap.RECEIVER:
        swap = K * temp - (P_t_Ti - P_t_Tm)
    
    return swap * notional

def P0TModel(t,ti,ri,method):
    rInterp = method(ti,ri)
    if t>=ti[-1]:
        r = ri[-1]
    if t<=ti[0]:
        r= ri[0]
    if t>ti[0] and t<ti[-1]:
        r = rInterp(t)
    
    return np.exp(-r*t)

def YieldCurve(instruments, maturities, r0, method, tol):
    r0 = deepcopy(r0)
    ri = MultivariateNewtonRaphson(r0, maturities, instruments, method, tol=tol)
    return ri

def MultivariateNewtonRaphson(ri, ti, instruments, method, tol):
    err = 10e10
    idx = 0
    while err > tol:
        idx = idx +1
        values = EvaluateInstruments(ti,ri,instruments,method)
        J = Jacobian(ti,ri, instruments, method)
        J_inv = np.linalg.inv(J)
        err = - np.dot(J_inv, values)
        ri[0:] = ri[0:] + err 
        err = np.linalg.norm(err)
        print('index in the loop is',idx,' Error is ', err)
    return ri

def Jacobian(ti, ri, instruments, method):
    eps = 1e-05
    swap_num = len(ti)
    J = np.zeros([swap_num, swap_num])
    val = EvaluateInstruments(ti,ri,instruments,method)
    ri_up = deepcopy(ri)
    
    for j in range(0, len(ri)):
        ri_up[j] = ri[j] + eps  
        val_up = EvaluateInstruments(ti,ri_up,instruments,method)
        ri_up[j] = ri[j]
        dv = (val_up - val) / eps
        J[:, j] = dv[:]
    return J

def EvaluateInstruments(ti,ri,instruments,method):
    P0Ttemp = lambda t: P0TModel(t,ti,ri,method)
    val = np.zeros(len(instruments))
    for i in range(0,len(instruments)):
        val[i] = instruments[i](P0Ttemp)
    return val

def linear_interpolation(ti,ri):
    interpolator = lambda t: np.interp(t, ti, ri)
    return interpolator

def quadratic_interpolation(ti, ri):
    interpolator = interp1d(ti, ri, kind='quadratic')
    return interpolator

def cubic_interpolation(ti, ri):
    interpolator = interp1d(ti, ri, kind='cubic')
    return interpolator

#def ComputeDelta(instrument,)

def BuildInstruments(K,mat):
    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[0],0.0,0.0,mat[0],4*mat[0],P0T)
    swap2 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[1],0.0,0.0,mat[1],4*mat[1],P0T)
    swap3 = lambda P0T: IRSwap(OptionTypeSwap.RECEIVER,1,K[2],0.0,0.0,mat[2],4*mat[2],P0T)
    swap4 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[3],0.0,0.0,mat[3],4*mat[3],P0T)
    swap5 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[4],0.0,0.0,mat[4],4*mat[4],P0T)
    swap6 = lambda P0T: IRSwap(OptionTypeSwap.RECEIVER,1,K[5],0.0,0.0,mat[5],4*mat[5],P0T)
    swap7 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[6],0.0,0.0,mat[6],4*mat[6],P0T)
    swap8 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[7],0.0,0.0,mat[7],4*mat[7],P0T)
    instruments = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8]
    return instruments

def mainCode():
    
    # Convergence tolerance
    tol = 1.0e-8
    # Initial guess for the spine points
    r0 = np.array([0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01])   
    # Interpolation method
    method = cubic_interpolation
    
    # Construct swaps that are used for building of the yield curve
    K   = np.array([0.02, 0.03, 0.04, 0.05, 0.06, 0.07,0.08,0.09])
    mat = np.array([1.0,2.0,3.0,5.0,7.0,10.0,20.0,30.0])
    
    instruments = BuildInstruments(K,mat)
    ri = YieldCurve(instruments, mat, r0, method, tol)
    P0T = lambda t: P0TModel(t,mat,ri,method)
        
    # Define off market Swap 
    SwapLambda = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,0.03,0.0,0.0,4,6*mat[0],P0T)
    swap= SwapLambda(P0T)
    print('Swap price = ',swap)
    
    dK = 0.0001
    delta = np.zeros(len(K))
    K_new = K
    for i in range(0,len(K)):
        K_new[i] = K_new[i] + dK
        instruments = BuildInstruments(K_new,mat)
        ri = YieldCurve(instruments, mat, r0, method, tol)
        P0T_new = lambda t: P0TModel(t,mat,ri,method)
        swap_shock = SwapLambda(P0T_new)
        delta[i] = (swap_shock-swap)/dK
        K_new[i] = K_new[i] - dK
    
    print(delta)
    return 0.0

mainCode()
    
    

================================================
FILE: Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/YieldCurveBuild_Treasury.py
================================================
#%%
"""
Created on June 27 2021
Construction of a yield curve for a given set of swap instruments

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import enum 

from copy import deepcopy
from scipy.interpolate import splrep, splev, interp1d

# This class defines puts and calls
class OptionTypeSwap(enum.Enum):
    RECEIVER = 1.0
    PAYER = -1.0
    
def IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):
    # CP- payer of receiver
    # n- notional
    # K- strike
    # t- today's date
    # Ti- beginning of the swap
    # Tm- end of Swap
    # n- number of dates payments between Ti and Tm
    # r_t -interest rate at time t
    ti_grid = np.linspace(Ti,Tm,int(n))
    tau = ti_grid[1]- ti_grid[0]
    
    # overwrite Ti if t>Ti
    prevTi = ti_grid[np.where(ti_grid<t)]
    if np.size(prevTi) > 0: #prevTi != []:
        Ti = prevTi[-1]
    
    # Now we need to handle the case when some payments are already done
    ti_grid = ti_grid[np.where(ti_grid>t)]           

    temp= 0.0
        
    for (idx,ti) in enumerate(ti_grid):
        if ti>Ti:
            temp = temp + tau * P0T(ti)
            
    P_t_Ti = P0T(Ti)
    P_t_Tm = P0T(Tm)
    
    if CP==OptionTypeSwap.PAYER:
        swap = (P_t_Ti - P_t_Tm) - K * temp
    elif CP==OptionTypeSwap.RECEIVER:
        swap = K * temp - (P_t_Ti - P_t_Tm)
    
    return swap * notional

def P0TModel(t,ti,ri,method):
    rInterp = method(ti,ri)
    r = rInterp(t)
    return np.exp(-r*t)

def YieldCurve(instruments, maturities, r0, method, tol):
    r0 = deepcopy(r0)
    ri = MultivariateNewtonRaphson(r0, maturities, instruments, method, tol=tol)
    return ri

def MultivariateNewtonRaphson(ri, ti, instruments, method, tol):
    err = 10e10
    idx = 0
    while err > tol:
        idx = idx +1
        values = EvaluateInstruments(ti,ri,instruments,method)
        J = Jacobian(ti,ri, instruments, method)
        J_inv = np.linalg.inv(J)
        err = - np.dot(J_inv, values)
        ri[0:] = ri[0:] + err 
        err = np.linalg.norm(err)
        print('index in the loop is',idx,' Error is ', err)
    return ri

def Jacobian(ti, ri, instruments, method):
    eps = 1e-05
    swap_num = len(ti)
    J = np.zeros([swap_num, swap_num])
    val = EvaluateInstruments(ti,ri,instruments,method)
    ri_up = deepcopy(ri)
    
    for j in range(0, len(ri)):
        ri_up[j] = ri[j] + eps  
        val_up = EvaluateInstruments(ti,ri_up,instruments,method)
        ri_up[j] = ri[j]
        dv = (val_up - val) / eps
        J[:, j] = dv[:]
    return J

def EvaluateInstruments(ti,ri,instruments,method):
    P0Ttemp = lambda t: P0TModel(t,ti,ri,method)
    val = np.zeros(len(instruments))
    for i in range(0,len(instruments)):
        val[i] = instruments[i](P0Ttemp)
    return val

def linear_interpolation(ti,ri):
    interpolator = lambda t: np.interp(t, ti, ri)
    return interpolator

def mainCode():
    
    # Convergence tolerance
    tol = 1.0e-15
    # Initial guess for the spine points
    r0 = np.array([0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01])   
    # Interpolation method
    method = linear_interpolation
    
    # Construct swaps that are used for building of the yield curve
    K   = np.array([0.04/100.0,	0.16/100.0,	0.31/100.0,	0.81/100.0,	1.28/100.0,	1.62/100.0,	2.22/100.0,	2.30/100.0])
    mat = np.array([1.0,2.0,3.0,5.0,7.0,10.0,20.0,30.0])
    
    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[0],0.0,0.0,mat[0],4*mat[0],P0T)
    swap2 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[1],0.0,0.0,mat[1],4*mat[1],P0T)
    swap3 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[2],0.0,0.0,mat[2],4*mat[2],P0T)
    swap4 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[3],0.0,0.0,mat[3],4*mat[3],P0T)
    swap5 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[4],0.0,0.0,mat[4],4*mat[4],P0T)
    swap6 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[5],0.0,0.0,mat[5],4*mat[5],P0T)
    swap7 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[6],0.0,0.0,mat[6],4*mat[6],P0T)
    swap8 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[7],0.0,0.0,mat[7],4*mat[7],P0T)
    instruments = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8]
    
    # determine optimal spine points
    ri = YieldCurve(instruments, mat, r0, method, tol)
    print('\n Spine points are',ri,'\n')
    
    # Build a ZCB-curve/yield curve from the spine points
    P0T_Initial = lambda t: P0TModel(t,mat,r0,method)
    P0T = lambda t: P0TModel(t,mat,ri,method)
    
    # price back the swaps
    swapsModel = np.zeros(len(instruments))
    swapsInitial = np.zeros(len(instruments))
    for i in range(0,len(instruments)):
        swapsInitial[i] = instruments[i](P0T_Initial)
        swapsModel[i] = instruments[i](P0T)
    
    print('Prices for Pas Swaps (initial) = ',swapsInitial,'\n')
    print('Prices for Par Swaps = ',swapsModel,'\n')
    
    return 0.0

mainCode()
    
    

================================================
FILE: Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/HW_CapletsAndFloorlets.py
================================================
#%%
"""
Created on July 12 2021
Caplets and floorlets under the Hull-White Model

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import enum 
import matplotlib.pyplot as plt
import scipy.stats as st
import scipy.integrate as integrate
import scipy.optimize as optimize

# This class defines puts and calls
class OptionType(enum.Enum):
    CALL = 1.0
    PUT = -1.0

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    #print("CHANGED THETA")
    return theta#lambda t: 0.1+t-t
    
def HW_A(lambd,eta,P0T,T1,T2):
    tau = T2-T1
    zGrid = np.linspace(0.0,tau,250)
    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)
    theta = HW_theta(lambd,eta,P0T)    
    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)
    
    temp2 = eta*eta/(4.0*np.power(lambd,3.0)) * (np.exp(-2.0*lambd*tau)*(4*np.exp(lambd*tau)-1.0) -3.0) + eta*eta*tau/(2.0*lambd*lambd)
    
    return temp1 + temp2

def HW_B(lambd,eta,T1,T2):
    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)

def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    return np.exp(A_r + B_r *rT1)

def HWMean_r(P0T,lambd,eta,T):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = HW_theta(lambd,eta,P0T)
    zGrid = np.linspace(0.0,T,2500)
    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))
    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)
    return r_mean

def HW_r_0(P0T,lambd,eta):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    return r0

def HW_Mu_FrwdMeasure(P0T,lambd,eta,T):
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = HW_theta(lambd,eta,P0T)
    zGrid = np.linspace(0.0,T,500)
    
    theta_hat =lambda t,T:  theta(t) + eta*eta / lambd *1.0/lambd * (np.exp(-lambd*(T-t))-1.0)
    
    temp =lambda z: theta_hat(z,T) * np.exp(-lambd*(T-z))
    
    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)
    
    return r_mean

def HWVar_r(lambd,eta,T):
    return eta*eta/(2.0*lambd) *( 1.0-np.exp(-2.0*lambd *T))

def HWDensity(P0T,lambd,eta,T):
    r_mean = HWMean_r(P0T,lambd,eta,T)
    r_var = HWVar_r(lambd,eta,T)
    return lambda x: st.norm.pdf(x,r_mean,np.sqrt(r_var))

def HW_CapletFloorletPrice(CP,N,K,lambd,eta,P0T,T1,T2):
    if CP == OptionType.CALL:
        N_new = N * (1.0+(T2-T1)*K)
        K_new = 1.0 + (T2-T1)*K
        caplet = N_new*HW_ZCB_CallPutPrice(OptionType.PUT,1.0/K_new,lambd,eta,P0T,T1,T2)
        value= caplet
    elif CP==OptionType.PUT:
        N_new = N * (1.0+ (T2-T1)*K)
        K_new = 1.0 + (T2-T1)*K
        floorlet = N_new*HW_ZCB_CallPutPrice(OptionType.CALL,1.0/K_new,lambd,eta,P0T,T1,T2)
        value = floorlet
    return value
    
def HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):
    B_r = HW_B(lambd,eta,T1,T2)
    A_r = HW_A(lambd,eta,P0T,T1,T2)
    
    mu_r = HW_Mu_FrwdMeasure(P0T,lambd,eta,T1)
    v_r =  np.sqrt(HWVar_r(lambd,eta,T1))
    
    K_hat = K * np.exp(-A_r)
    
    a = (np.log(K_hat) - B_r*mu_r)/(B_r*v_r)
    
    d1 = a - B_r*v_r
    d2 = d1 +  B_r*v_r
    
    term1 = np.exp(0.5* B_r*B_r*v_r*v_r + B_r*mu_r)*st.norm.cdf(d1) - K_hat * st.norm.cdf(d2)    
    value =P0T(T1) * np.exp(A_r) * term1 
    
    if CP == OptionType.CALL:
        return value
    elif CP==OptionType.PUT:
        return value - P0T(T2) + K*P0T(T1)

# Black-Scholes Call option price
def BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):
    if K is list:
        K = np.array(K).reshape([len(K),1])
    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * tau) / (sigma * np.sqrt(tau))
    d2    = d1 - sigma * np.sqrt(tau)
    if CP == OptionType.CALL:
        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)
    elif CP == OptionType.PUT:
        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0
    return value

# Implied volatility method
def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
    # To determine initial volatility we interpolate define a grid for sigma
    # and interpolate on the inverse
    sigmaGrid = np.linspace(0.0,5.0,5000)
    optPriceGrid = BS_Call_Put_Option_Price(CP,S_0,K,sigmaGrid,T,0.0)
    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)
    print("Strike = {0}".format(K))
    print("Initial volatility = {0}".format(sigmaInitial))
    
    # Use determined input for the local-search (final tuning)
    func = lambda sigma: np.power(BS_Call_Put_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)
    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15)
    print("Final volatility = {0}".format(impliedVol))
    if impliedVol > 2.0:
        impliedVol = 0.0
    return impliedVol

def mainCalculation():
    CP= OptionType.CALL
    NoOfPaths = 20000
    NoOfSteps = 1000
        
    lambd     = 0.02
    eta       = 0.02
    
    # We define a ZCB curve (obtained from the market)
    P0T = lambda T: np.exp(-0.1*T)#np.exp(-0.03*T*T-0.1*T)
    r0 = HW_r_0(P0T,lambd,eta)
    
    # In this experiment we compare ZCB from the Market and Analytical expression
    N = 25
    T_end = 50
    Tgrid= np.linspace(0,T_end,N)
    
    Exact = np.zeros([N,1])
    Proxy= np.zeros ([N,1])
    for i,Ti in enumerate(Tgrid):
        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)
        Exact[i] = P0T(Ti)
        
    plt.figure(1)
    plt.grid()
    plt.plot(Tgrid,Exact,'-k')
    plt.plot(Tgrid,Proxy,'--r')
    plt.legend(["Analytcal ZCB","Monte Carlo ZCB"])
    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')

    # In this experiment we compare Monte Carlo results for 
    T1 = 4.0
    T2 = 8.0
    
    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T1 ,P0T, lambd, eta)
    r = paths["R"]
    timeGrid = paths["time"]
    dt = timeGrid[1]-timeGrid[0]
    
    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    
    M_t = np.zeros([NoOfPaths,NoOfSteps])
    for i in range(0,NoOfPaths):
        M_t[i,:] = np.exp(np.cumsum(r[i,:-1])*dt)
        
    KVec = np.linspace(0.01,1.7,50)
    Price_MC_V = np.zeros([len(KVec),1])
    Price_Th_V =np.zeros([len(KVec),1])
    P_T1_T2 = HW_ZCB(lambd,eta,P0T,T1,T2,r[:,-1])
    for i,K in enumerate(KVec):
        if CP==OptionType.CALL:
            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(P_T1_T2-K,0.0)) 
        elif CP==OptionType.PUT:
            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(K-P_T1_T2,0.0)) 
        Price_Th_V[i] =HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2)#HW_ZCB_CallPrice(K,lambd,eta,P0T,T1,T2)
        
    plt.figure(2)
    plt.grid()
    plt.plot(KVec,Price_MC_V)
    plt.plot(KVec,Price_Th_V,'--r')
    plt.legend(['Monte Carlo','Theoretical'])
    plt.title('Option on ZCB')

    # Effect of the HW model parameters on Implied Volatilities
    # define a forward rate between T1 and T2
    frwd = 1.0/(T2-T1) *(P0T(T1)/P0T(T2)-1.0)
    K = np.linspace(frwd/2.0,3.0*frwd,25)
    
    # Effect of eta
    plt.figure(3)
    plt.grid()
    plt.xlabel('strike, K')
    plt.ylabel('implied volatility')
    etaV =[0.01, 0.02, 0.03, 0.04]
    legend = []
    Notional = 1.0
    for etaTemp in etaV:    
       optPrice = HW_CapletFloorletPrice(CP,Notional,K,lambd,etaTemp,P0T,T1,T2)
       # Implied volatilities
       IV =np.zeros([len(K),1])
       for idx in range(0,len(K)):
           valFrwd = optPrice[idx]/P0T(T2)/(T2-T1)
           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K[idx],T2,frwd)
       plt.plot(K,IV*100.0)
       #plt.plot(K,optPrice)
       legend.append('eta={0}'.format(etaTemp))
    plt.legend(legend)        
    
     # Effect of lambda
    plt.figure(4)
    plt.grid()
    plt.xlabel('strike, K')
    plt.ylabel('implied volatility')
    lambdaV = [0.01, 0.03, 0.05, 0.09]
    legend = []
    Notional = 1.0
    for lambdTemp in lambdaV:    
       optPrice = HW_CapletFloorletPrice(CP,Notional,K,lambdTemp,eta,P0T,T1,T2)
       # Implied volatilities
       IV =np.zeros([len(K),1])
       for idx in range(0,len(K)):
           valFrwd = optPrice[idx]/P0T(T2)/(T2-T1)
           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K[idx],T2,frwd)
       #plt.plot(K,optPrice)
       plt.plot(K,IV*100.0)
       legend.append('lambda={0}'.format(lambdTemp))
    plt.legend(legend)  

    print('frwd={0}'.format(frwd*P0T(T2)))
mainCalculation()

================================================
FILE: Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/JamshidianTrick.py
================================================
#%%
"""
Created on July 18 2021
Jamshidian's trick for handling E max sum -> sum E max 

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import matplotlib.pyplot as plt
import scipy.optimize as optimize

def PsiSum(psi,N,x):
    temp = 0
    for i in range(0,N):
        temp = temp + psi(i,x)
    return temp

def JamshidianTrick(psi,N,K):
    A = lambda x: PsiSum(psi,N,x) - K
    result = optimize.newton(A,0.1)
    return result

def Main():
    NoOfSamples = 1000
    X =  np.random.normal(0.0,1.0,[NoOfSamples,1])
    psi_i = lambda i,X: np.exp(-i*np.abs(X))
    
    # Number of terms
    N = 15
    
    A = 0
    for i in range(0,N):
        A = A + psi_i(i,X)
    
    K = np.linspace(2,10,10)
    resultMC = np.zeros(len(K))
    for (i,Ki) in enumerate(K):
        resultMC[i] = np.mean(np.maximum(A-Ki,0))
    
    # Jamshidians trick
    resultJams = np.zeros(len(K))
    for i,Ki in enumerate(K):
        # Compute optimal K*
        optX = JamshidianTrick(psi_i,N,Ki)
        A = 0
        for j in range(0,N):
            A = A + np.mean(np.maximum(psi_i(j,X)-psi_i(j,optX),0))
        resultJams[i] = A
        
    plt.figure()
    plt.plot(K,resultMC)
    plt.plot(K,resultJams,'--r')
    plt.grid()
    plt.xlabel('K')
    plt.ylabel('expectation')
    plt.legend(['Monte Carlo','Jamshidians trick'])
    
Main()

================================================
FILE: Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/ShiftedLognormal.py
================================================
#%%
"""
Created on July 12 2021
Shifted GBM and pricing of caplets/floorlets

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""

import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as st
import enum 
import scipy.optimize as optimize

# This class defines puts and calls
class OptionType(enum.Enum):
    CALL = 1.0
    PUT = -1.0

def GeneratePathsGBMShifted(NoOfPaths,NoOfSteps,T,r,sigma,S_0,shift):
    S_0shift = S_0 + shift
    if (S_0shift < 0.0):
        raise('Shift is too small!')
    
    paths =GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0shift)
    Sshifted = paths["S"] - shift
    time = paths["time"]
    return {"time":time,"S":Sshifted}      

def GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0):    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    X = np.zeros([NoOfPaths, NoOfSteps+1])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    time = np.zeros([NoOfSteps+1])
        
    X[:,0] = np.log(S_0)
    
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        X[:,i+1] = X[:,i] + (r - 0.5 * sigma * sigma) * dt + sigma * (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    #Compute exponent of ABM
    S = np.exp(X)
    paths = {"time":time,"S":S}
    return paths

# Shifted Black-Scholes Call option price
def BS_Call_Put_Option_Price_Shifted(CP,S_0,K,sigma,tau,r,shift):
    K_new = K + shift
    S_0_new = S_0 + shift
    return BS_Call_Put_Option_Price(CP,S_0_new,K_new,sigma,tau,r)

# Black-Scholes Call option price
def BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):
    if K is list:
        K = np.array(K).reshape([len(K),1])
    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * tau) / (sigma * np.sqrt(tau))
    d2    = d1 - sigma * np.sqrt(tau)
    if CP == OptionType.CALL:
        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)
    elif CP == OptionType.PUT:
        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0
    return value

# Implied volatility method
def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
    # To determine initial volatility we interpolate define a grid for sigma
    # and interpolate on the inverse
    sigmaGrid = np.linspace(0.0,2.0,5000)
    optPriceGrid = BS_Call_Put_Option_Price(CP,S_0,K,sigmaGrid,T,0.0)
    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)
    print("Initial volatility = {0}".format(sigmaInitial))
    
    # Use determined input for the local-search (final tuning)
    func = lambda sigma: np.power(BS_Call_Put_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)
    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15)
    print("Final volatility = {0}".format(impliedVol))
    return impliedVol

# Implied volatility method
def ImpliedVolatilityBlack76Shifted(CP,marketPrice,K,T,S_0,shift):
    # To determine initial volatility we interpolate define a grid for sigma
    # and interpolate on the inverse
    sigmaGrid = np.linspace(0.0,2.0,5000)
    optPriceGrid = BS_Call_Put_Option_Price_Shifted(CP,S_0,K,sigmaGrid,T,0.0,shift)
    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)
    print("Initial volatility = {0}".format(sigmaInitial))
    
    # Use determined input for the local-search (final tuning)
    func = lambda sigma: np.power(BS_Call_Put_Option_Price_Shifted(CP,S_0,K,sigma,T,0.0,shift) - marketPrice, 1.0)
    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15)
    print("Final volatility = {0}".format(impliedVol))
    return impliedVol

def mainCalculation():
    NoOfPaths = 10000
    NoOfSteps = 500
    T         = 3.0
    sigma     = 0.2
    L0        = -0.05
    shift     = 0.1
    K         = [0.95]
    CP        = OptionType.CALL
    
    P0T =lambda T: np.exp(-0.1*T)
    
    np.random.seed(4)
    Paths = GeneratePathsGBMShifted(NoOfPaths,NoOfSteps,T,0.0,sigma,L0,shift)
    time  = Paths["time"]
    L     = Paths["S"]
    
    print(np.mean(L[:,-1]))
    
    # Plot first few paths
    plt.figure(1)
    plt.plot(time,np.transpose(L[0:20,:]))
    plt.grid()
    
    # Shifted lognormal for different shift parameters
    plt.figure(2)
    shiftV = [1.0,2.0,3.0,4.0,5.0]
    legend = []
    for shiftTemp in shiftV:
        x = np.linspace(-shiftTemp,10,1000)
        lognnormPDF = lambda x,t :  st.lognorm.pdf(x+shiftTemp, scale = np.exp(np.log(L0+shiftTemp) + (- 0.5 * sigma * sigma)*t), s= np.sqrt(t) * sigma)
        pdf_x= lognnormPDF(x,T)
        plt.plot(x,pdf_x)
        legend.append('shift={0}'.format(shiftTemp))
    plt.legend(legend)
    plt.xlabel('x')
    plt.ylabel('pdf')
    plt.title('shifted lognormal density')
    plt.grid()
    
    # Call/Put option prices, MC vs. Analytical
    plt.figure(3)
    K = np.linspace(-shift,np.abs(L0)*3,25)
    optPriceMCV=np.zeros([len(K),1])
    for idx in range(0,len(K)):
        optPriceMCV[idx] =0.0
        if CP == OptionType.CALL:
            optPriceMCV[idx] = P0T(T)*np.mean(np.maximum(L[:,-1]-K[idx],0.0))
        elif CP == OptionType.PUT:
            optPriceMCV[idx] = P0T(T)*np.mean(np.maximum(K[idx]-L[:,-1],0.0))
    
    optPriceExact = P0T(T)*BS_Call_Put_Option_Price_Shifted(CP,L0,K,sigma,T,0.0,shift)
    plt.plot(K,optPriceMCV)       
    plt.plot(K,optPriceExact,'--r')       
    plt.grid()
    plt.xlabel('strike,K')
    plt.ylabel('option price')
    plt.legend(['Monte Carlo','Exact'])
    
    # Shift Effect on Option prices
    plt.figure(4)
    legend = []
    for shiftTemp in [0.2,0.3,0.4,0.5]:    
        K = np.linspace(-shiftTemp,np.abs(L0)*6.0,25)
        optPriceExact = P0T(T)*BS_Call_Put_Option_Price_Shifted(CP,L0,K,sigma,T,0.0,shiftTemp)
        plt.plot(K,optPriceExact)       
        legend.append('shift={0}'.format(shiftTemp))
    plt.grid()
    plt.xlabel('strike,K')
    plt.ylabel('option price')
    plt.legend(legend)
    
mainCalculation()

================================================
FILE: Lecture 08-Mortgages and Prepayments/Materials/AnnuityMortgage.py
================================================
#%%
"""
Created on July 05  2021
Annuity mortgage- payment profile

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak and Emanuele Casamassima
"""
import numpy as np
import matplotlib.pyplot as plt

def Annuity(rate,notional,periods,CPR):
    # it returns a matrix M such that
    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_quote(t)  installment(t)]
    # WARNING! here "rate" and "periods" are quite general, the choice of getting year/month/day.. steps, depends on the rate
    # that the function receives. So, it is necessary to pass the correct rate to the function
    M = np.zeros((periods + 1,6))
    M[:,0] = np.arange(periods + 1) # we define the times
    M[0,1] = notional
    for t in range(1,periods + 1):
        # we are computing the installment at time t knowing the oustanding at time (t-1)
        remaining_periods = periods - (t - 1)  
        
        # Installment, C(t_i) 
        M[t,5] = rate * M[t-1,1]/(1 - 1/(1 + rate)**remaining_periods) 
        
        # Interest rate payment, I(t_i) = K * N(t_{i})
        M[t,4] = rate * M[t-1,1] 
        
        # Notional payment, Q(t_i) = C(t_i) - I(t_i)
        M[t,3] = M[t,5] - M[t,4] 
        
        # Prepayment, P(t_i)= Lambda * (N(t_i) -Q(t_i))
        M[t,2] = CPR * (M[t-1,1] - M[t,3]) 
        
        # notional, N(t_{i+1}) = N(t_{i}) - lambda * (Q(t_{i} + P(t_i)))
        M[t,1] = M[t-1,1] - M[t,3] - M[t,2] 
    return M

def mainCode():

    # Initial notional
    N0     = 1000000
    
    # Interest rates from a bank
    r = 0.05
    
    # Prepayment rate, 0.1 = 10%
    Lambda = 0.1

    # For simplicity we assume 1 as unit (yearly payments of mortgage)
    T_end = 30
    M = Annuity(r,N0,T_end,Lambda)
    
    for i in range(0,T_end+1):
        print("Ti={0}, Notional={1:.0f}, Prepayment={2:.0f}, Notional Repayment={3:.0f}, Interest Rate={4:.0f}, Installment={5:.0f} ".format(M[i,0],M[i,1],M[i,2],M[i,3],M[i,4],M[i,5]))
    
    plt.figure(1)
    plt.plot(M[:,0],M[:,1],'.r')
    plt.grid()
    plt.xlabel('time')
    plt.ylabel('notional')
    return 0.0

mainCode()
    
    

================================================
FILE: Lecture 08-Mortgages and Prepayments/Materials/BulletMortgage.py
================================================
#%%
"""
Created on July 05  2021
Bullet mortgage- payment profile

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak and Emanuele Casamassima
"""
import numpy as np
import matplotlib.pyplot as plt

def Bullet(rate,notional,periods,CPR):
    # it returns a matrix M such that
    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_(t)  installment(t)]
    # WARNING! here "rate" and "periods" are quite general, the choice of getting year/month/day.. steps, depends on the rate
    # that the function receives. So, it is necessary to pass the correct rate to the function
    M = np.zeros((periods + 1,6))
    M[:,0] = np.arange(periods + 1) # we define the times
    M[0,1] = notional
    for t in range(1,periods):
        M[t,4] = rate*M[t-1,1]      # interest quote
        M[t,3] = 0                  # repayment, 0 for bullet mortgage
        scheduled_oustanding = M[t-1,1] - M[t,3]
        M[t,2] = scheduled_oustanding * CPR    # prepayment
        M[t,1] = scheduled_oustanding - M[t,2] # notional(t) = notional(t-1) - (repayment + prepayment)
        M[t,5] = M[t,4] + M[t,2] + M[t,3]
        
    M[periods,4] = rate*M[periods-1,1] # interest quote
    M[periods,3] = M[periods-1,1]      # notional quote
    M[periods,5] = M[periods,4] + M[periods,2] + M[periods,3]
    return M

def mainCode():

    # Initial notional
    N0     = 1000000
    
    # Interest rates from a bank
    r = 0.05
    
    # Prepayment rate, 0.1 = 10%
    Lambda = 0.01

    # For simplicity we assume 1 as unit (yearly payments of mortgage)
    T_end = 30
    M = Bullet(r,N0,T_end,Lambda)
    
    for i in range(0,T_end+1):
        print("Ti={0}, Notional={1:.0f}, Prepayment={2:.0f}, Notional Repayment={3:.0f}, Interest Rate={4:.0f}, Installment={5:.0f} ".format(M[i,0],M[i,1],M[i,2],M[i,3],M[i,4],M[i,5]))
    
    plt.figure(1)
    plt.plot(M[:,0],M[:,1],'-r')
    plt.grid()
    plt.xlabel('time')
    plt.ylabel('notional')
    
    return 0.0

mainCode()
    
    

================================================
FILE: Lecture 08-Mortgages and Prepayments/Materials/Incentives.py
================================================
#%%
"""
Created on July 05  2021
Incentive function as a function of a swap rate or the differential w.r.t. "old" mortgage rate

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak and Emanuele Cassamassima
"""
import numpy as np
import matplotlib.pyplot as plt

def Annuity(rate,notional,periods,CPR):
    # it returns a matrix M such that
    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_quote(t)  installment(t)]
    # WARNING! here "rate" and "periods" are quite general, the choice of getting year/month/day.. steps, depends on the rate
    # that the function receives. So, it is necessary to pass the correct rate to the function
    M = np.zeros((periods + 1,6))
    M[:,0] = np.arange(periods + 1) # we define the times
    M[0,1] = notional
    for t in range(1,periods + 1):
        # we are computing the installment at time t knowing the oustanding at time (t-1)
        remaining_periods = periods - (t - 1)  
        
        # Installment, C(t_i) 
        M[t,5] = rate * M[t-1,1]/(1 - 1/(1 + rate)**remaining_periods) 
        
        # Interest rate payment, I(t_i) = r * N(t_{i})
        M[t,4] = rate * M[t-1,1] 
        
        # Notional payment, Q(t_i) = C(t_i) - I(t_i)
        M[t,3] = M[t,5] - M[t,4] 
        
        # Prepayment, P(t_i)= Lambda * (N(t_i) -Q(t_i))
        M[t,2] = CPR * (M[t-1,1] - M[t,3]) 
        
        # notional, N(t_{i+1}) = N(t_{i}) - lambda * (Q(t_{i} + P(t_i)))
        M[t,1] = M[t-1,1] - M[t,3] - M[t,2] 
    return M

def mainCode():


    IncentiveFunction = lambda x : 0.04 + 0.1/(1 + np.exp(115 * (0.02-x))) 
    
    oldRate = 0.05
    newRate = np.linspace(-0.05,0.15,25)
    
    epsilon = oldRate-newRate
    incentive = IncentiveFunction(epsilon)
    
    plt.figure(1)
    plt.plot(newRate,incentive)
    plt.xlabel('S(t)')
    plt.ylabel('Incentive')
    plt.grid()
    
    plt.figure(2)
    plt.plot(epsilon,incentive)
    plt.xlabel('epsilon= K - S(t)')
    plt.ylabel('Incentive')
    plt.grid()

    return 0.0

mainCode()
    
    

================================================
FILE: Lecture 08-Mortgages and Prepayments/Materials/StochasticAmortizingSwap.py
================================================
#%%
"""
Created on July 05  2021
Stochastic amortization given the incentive function and irrational/rational behavior profile

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak and Emanuele Cassamassima
"""
import numpy as np
import matplotlib.pyplot as plt
import scipy.optimize as optimize
import scipy.integrate as integrate

def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    
    # time-step needed for differentiation
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    
    #theta = lambda t: 0.1 +t -t
    #print("changed theta")
    
    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])
    W = np.zeros([NoOfPaths, NoOfSteps+1])
    R = np.zeros([NoOfPaths, NoOfSteps+1])
    R[:,0]=r0
    time = np.zeros([NoOfSteps+1])
        
    dt = T / float(NoOfSteps)
    for i in range(0,NoOfSteps):
        # making sure that samples from normal have mean 0 and variance 1
        if NoOfPaths > 1:
            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])
        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]
        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])
        time[i+1] = time[i] +dt
        
    # Outputs
    paths = {"time":time,"R":R}
    return paths

def HW_theta(lambd,eta,P0T):
    dt = 0.0001    
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))
    return theta

def HW_A(lambd,eta,P0T,T1,T2):
    tau = T2-T1
    zGrid = np.linspace(0.0,tau,250)
    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)
    theta = HW_theta(lambd,eta,P0T)    
    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)
    
    temp2 = eta*eta/(4.0*np.power(lambd,3.0)) * (np.exp(-2.0*lambd*tau)*(4*np.exp(lambd*tau)-1.0) -3.0) + eta*eta*tau/(2.0*lambd*lambd)
    
    return temp1 + temp2

def HW_B(lambd,eta,T1,T2):
    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)

def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
    n = np.size(rT1) 
        
    if T1<T2:
        B_r = HW_B(lambd,eta,T1,T2)
        A_r = HW_A(lambd,eta,P0T,T1,T2)
        return np.exp(A_r + B_r *rT1)
    else:
        return np.ones([n])

def SwapRateHW(t,Ti,Tm,n,r_t,P0T,lambd,eta):
    # CP- payer of receiver
    # n- notional
    # K- strike
    # t- today's date
    # Ti- beginning of the swap
    # Tm- end of Swap
    # n- number of dates payments between Ti and Tm
    # r_t -interest rate at time t

    if n == 1:
        ti_grid =np.array([Ti,Tm])
    else:
        ti_grid = np.linspace(Ti,Tm,n)
    tau = ti_grid[1]- ti_grid[0]
    
    # overwrite Ti if t>Ti
    prevTi = ti_grid[np.where(ti_grid<t)]
    if np.size(prevTi) > 0: #prevTi != []:
        Ti = prevTi[-1]
    
    # Now we need to handle the case when some payments are already done
    ti_grid = ti_grid[np.where(ti_grid>t)]          

    temp= np.zeros(np.size(r_t));
    
    P_t_TiLambda = lambda Ti : HW_ZCB(lambd,eta,P0T,t,Ti,r_t)
    
    for (idx,ti) in enumerate(ti_grid):
        if ti>Ti:
            temp = temp + tau * P_t_TiLambda(ti)
            
    P_t_Ti = P_t_TiLambda(Ti)
    P_t_Tm = P_t_TiLambda(Tm)

    swapRate = (P_t_Ti - P_t_Tm) / temp
    
    return swapRate

def Bullet(rate,notional,periods,CPR):
    # it returns a matrix M such that
    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_(t)  installment(t)]
    # WARNING! here "rate" and "periods" are quite general, the choice of getting year/month/day.. steps, depends on the rate
    # that the function receives. So, it is necessary to pass the correct rate to the function
    M = np.zeros((periods + 1,6))
    M[:,0] = np.arange(periods + 1) # we define the times
    M[0,1] = notional
    for t in range(1,periods):
        M[t,4] = rate*M[t-1,1]      # interest quote
        M[t,3] = 0                  # notional quote, 0 for bullet mortgage
        scheduled_oustanding = M[t-1,1] - M[t,3]
        M[t,2] = scheduled_oustanding * CPR[t]    # prepayment
        M[t,1] = scheduled_oustanding - M[t,2] # notional(t) = notional(t-1) - (notional quote + prepayment)
        M[t,5] = M[t,4] + M[t,2] + M[t,3]
        
    M[periods,4] = rate*M[periods-1,1] # interest quote
    M[periods,3] = M[periods-1,1]      # notional quote
    M[periods,5] = M[periods,4] + M[periods,2] + M[periods,3]
    return M    

def Annuity(rate,notional,periods,CPR):
    # it returns a matrix M such that
    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_quote(t)  installment(t)]
    # WARNING! here "rate" and "periods" are quite general, the choice of getting year/month/day.. steps, depends on the rate
    # that the function receives. So, it is necessary to pass the correct rate to the function
    M = np.zeros((periods + 1,6))
    M[:,0] = np.arange(periods + 1) # we define the times
    M[0,1] = notional
    for t in range(1,periods + 1):
        # we are computing the installment at time t knowing the oustanding at time (t-1)
        remaining_periods = periods - (t - 1)  
        
        # Installment, C(t_i) 
        M[t,5] = rate * M[t-1,1]/(1 - 1/(1 + rate)**remaining_periods) 
        
        # Interest rate payment, I(t_i) = r * N(t_{i})
        M[t,4] = rate * M[t-1,1] 
        
        # Notional payment, Q(t_i) = C(t_i) - I(t_i)
        M[t,3] = M[t,5] - M[t,4] 
        
        # Prepayment, P(t_i)= Lambda * (N(t_i) -Q(t_i))
        M[t,2] = CPR[t] * (M[t-1,1] - M[t,3]) 
        
        # notional, N(t_{i+1}) = N(t_{i}) - lambda * (Q(t_{i} + P(t_i)))
        M[t,1] = M[t-1,1] - M[t,3] - M[t,2] 
    return M

def mainCode():

    Irrational = lambda x : 0.04 + 0.1/(1 + np.exp(200 * (-x))) 
    Rational   = lambda x : 0.04*(x>0.0)

    
    IncentiveFunction = Irrational
    
    K = 0.05
    newRate = np.linspace(-0.1,0.1,150)
    
    epsilon = K - newRate
    incentive = IncentiveFunction(epsilon)
    
    plt.figure(1)
    plt.plot(newRate,incentive)
    plt.xlabel('S(t)')
    plt.ylabel('Incentive')
    plt.grid()
    
    plt.figure(2)
    plt.plot(epsilon,incentive)
    plt.xlabel('epsilon= K - S(t)')
    plt.ylabel('Incentive')
    plt.grid()

    # Stochastic interest rates
    NoOfPaths = 2000
    NoOfSteps = 30
    lambd     = 0.05
    eta       = 0.01
    
    # End date of the underlying swap / mortgage
    Tend = 30 

    # Market ZCB
    P0T = lambda T: np.exp(-0.05*T)
    paths =  GeneratePathsHWEuler(NoOfPaths, NoOfSteps,Tend, P0T, lambd, eta)
    R = paths["R"]
    tiGrid = paths["time"]

    # Compute swap rates, at this point we assume that the incentive is driven by the CMS rate
    S = np.zeros([NoOfPaths,NoOfSteps+1])
    for (i,ti) in enumerate(tiGrid):
        S[:,i] = SwapRateHW(ti,ti,Tend+ti,30,R[:,i],P0T,lambd,eta)
    
    # Incentive for the new swap rate
    epsilon = K - S[:,-1]
    incentive = IncentiveFunction(epsilon)
    plt.figure(3)
    plt.plot(epsilon,incentive,'.r')
    plt.xlabel('epsilon= K - S(t)')
    plt.ylabel('Incentive')
    plt.grid()
    plt.title('Incentive for prepayment given stochastis S(t)')

    plt.figure(4)
    plt.hist(S[:,-1],bins=50)
    plt.grid()
    plt.title('Swap distribution at Tend')

    # Building up of stochastic notional N(ti) for every ti
    MortgageProfile =  Annuity
    notional = 1000000
    N = np.zeros([NoOfPaths,NoOfSteps+1])
    
    for i in range(0,NoOfPaths):
        epsilon =  K - S[i,:]
        Lambda = IncentiveFunction(epsilon)
        NotionalProfile = MortgageProfile(K,notional,NoOfSteps,Lambda)
        N[i,:] = NotionalProfile[:,1]

    plt.figure(6)
    plt.grid()
    plt.xlabel('time')
    plt.ylabel('notional')
    
    n = 100
    for k in range(0,n):
        plt.plot(tiGrid,N[k,:],'-b')
    
    AnnuityProfile_NoPrepayment = MortgageProfile(K,notional,NoOfSteps,np.zeros(NoOfSteps+1))
    plt.plot(tiGrid,AnnuityProfile_NoPrepayment[:,1],'--r')
    plt.title('Notional profile')
        
    return 0.0

mainCode()
    
    

================================================
FILE: Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/BSHW_Comparison.py
================================================
#%%
"""
Created on Thu Jan 04 2019
The BSHW model and implied volatilities obtained with the COS method and comparisons 
against a factorized 1D case where the implied volatilities are known analytically

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""
import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate
import scipy.stats as st
import enum 
import scipy.optimize as optimize

# set i= imaginary number
i   = np.complex(0.0,1.0)

# time-step needed for differentiation
dt = 0.0001
 
# This class defines puts and calls
class OptionType(enum.Enum):
    CALL = 1.0
    PUT = -1.0   

def CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):
    # cf   - characteristic function as a functon, in the book denoted as \varphi
    # CP   - C for call and P for put
    # S0   - Initial stock price
    # tau  - time to maturity
    # K    - list of strikes
    # N    - Number of expansion terms
    # L    - size of truncation domain (typ.:L=8 or L=10)  
    # P0T  - zero-coupon bond for maturity T.
        
    # reshape K to a column vector
    if K is not np.array:
        K = np.array(K).reshape([len(K),1])
    
    #assigning i=sqrt(-1)
    i = np.complex(0.0,1.0) 
    x0 = np.log(S0 / K)   
    
    # truncation domain
    a = 0.0 - L * np.sqrt(tau)
    b = 0.0 + L * np.sqrt(tau)
    
    # sumation from k = 0 to k=N-1
    k = np.linspace(0,N-1,N).reshape([N,1])  
    u = k * np.pi / (b - a);  

    # Determine coefficients for Put Prices  
    H_k = CallPutCoefficients(OptionType.PUT,a,b,k)   
    mat = np.exp(i * np.outer((x0 - a) , u))
    temp = cf(u) * H_k 
    temp[0] = 0.5 * temp[0]    
    value = K * np.real(mat.dot(temp))     
    
    # we use call-put parity for call options
    if CP == OptionType.CALL:
        value = value + S0 - K * P0T
        
    return value

# Determine coefficients for Put Prices 
def CallPutCoefficients(CP,a,b,k):
    if CP==OptionType.CALL:                  
        c = 0.0
        d = b
        coef = Chi_Psi(a,b,c,d,k)
        Chi_k = coef["chi"]
        Psi_k = coef["psi"]
        if a < b and b < 0.0:
            H_k = np.zeros([len(k),1])
        else:
            H_k      = 2.0 / (b - a) * (Chi_k - Psi_k)  
    elif CP==OptionType.PUT:
        c = a
        d = 0.0
        coef = Chi_Psi(a,b,c,d,k)
        Chi_k = coef["chi"]
        Psi_k = coef["psi"]
        H_k      = 2.0 / (b - a) * (- Chi_k + Psi_k)               
    
    return H_k    

def Chi_Psi(a,b,c,d,k):
    psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a))
    psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi)
    psi[0] = d - c
    
    chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) 
    expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d)  - np.cos(k * np.pi 
                  * (c - a) / (b - a)) * np.exp(c)
    expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * 
                        (d - a) / (b - a))   - k * np.pi / (b - a) * np.sin(k 
                        * np.pi * (c - a) / (b - a)) * np.exp(c)
    chi = chi * (expr1 + expr2)
    
    value = {"chi":chi,"psi":psi }
    return value
    
# Black-Scholes Call option price
def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r):
    if K is list:
        K = np.array(K).reshape([len(K),1])
    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 
    * tau) / float(sigma * np.sqrt(tau))
    d2    = d1 - sigma * np.sqrt(tau)
    if CP == OptionType.CALL:
        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)
    elif CP == OptionType.PUT:
        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0
    return value

# Implied volatility method
def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
    func = lambda sigma: np.power(BS_Call_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)
    impliedVol = optimize.newton(func, 0.2, tol=1e-9)
    #impliedVol = optimize.brent(func, brack= (0.05, 2))
    return impliedVol

def ChFBSHW(u, T, P0T, lambd, eta, rho, sigma):
    i = np.complex(0.0,1.0)
    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)
    
    # Initial interest rate is a forward rate at time t->0
    r0 = f0T(0.00001)
    
    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) \
        + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      
    C = lambda u,tau: 1.0/lambd*(i*u-1.0)*(1.0-np.exp(-lambd*tau))
    
    # define a grid for the numerical integration of function theta
    zGrid = np.linspace(0.0,T,2500)
    term1 = lambda u: 0.5*sigma*sigma *i*u*(i*u-1.0)*T
    term2 = lambda u: i*u*rho*sigma*eta/lambd*(i*u-1.0)*(T+1.0/lambd \
                    *(np.exp(-lambd*T)-1.0))
    term3 = lambda u: eta*eta/(4.0*np.power(lambd,3.0))*np.power(i+u,2.0)*\
            (3.0+np.exp(-2.0*lambd*T)-4.0*np.exp(-lambd*T)-2.0*lambd*T)
    term4 = lambda u:  lambd*integrate.trapz(theta(T-zGrid)*C(u,zGrid), zGrid)
    A= lambda u: term1(u) + term2(u) + term3(u) + term4(u)
    
    # Note that we don't include B(u)*x0 term as it is included in the COS method
    cf = lambda u : np.exp(A(u) + C(u,T)*r0 )
    
    # Iterate over all u and collect the ChF, iteration is necessary due to the integration over in term4
    cfV = []
    for ui in u:
        cfV.append(cf(ui))
    
    return cfV

def BSHWVolatility(T,eta,sigma,rho,lambd):
    Br= lambda t,T: 1/lambd * (np.exp(-lambd*(T-t))-1.0)
    sigmaF = lambda t: np.sqrt(sigma * sigma + eta * eta * Br(t,T) * Br(t,T)\
                               - 2.0 * rho * sigma * eta * Br(t,T)) 
    zGrid = np.linspace(0.0,T,2500)
    sigmaC = np.sqrt(1/T*integrate.trapz(sigmaF(zGrid)*sigmaF(zGrid), zGrid))
    return sigmaC

def BSHWOptionPrice(CP,S0,K,P0T,T,eta,sigma,rho,lambd):
    
    frwdS0 = S0 / P0T
    vol = BSHWVolatility(T,eta,sigma,rho,lambd)
    # As we deal with the forward prices we evaluate Black's 76 prices
    r = 0.0
    BlackPrice = BS_Call_Option_Price(CP,frwdS0,K,vol,T,r)
    return  P0T * BlackPrice

def mainCalculation():
    CP  = OptionType.CALL
        
    K = np.linspace(40.0,220.0,100)
    K = np.array(K).reshape([len(K),1])
    
    # HW model settings
    lambd = 0.1
    eta   = 0.05
    sigma = 0.2
    rho   = 0.3
    S0    = 100
    
    T = 5.0
    # We define a ZCB curve (obtained from the market)
    P0T = lambda T: np.exp(-0.05*T) 
    
    N = 500
    L = 8
    
    # Characteristic function of the BSHW model + the COS method
    cf = lambda u: ChFBSHW(u, T, P0T, lambd, eta, rho, sigma)
    valCOS = CallPutOptionPriceCOSMthd_StochIR(cf, CP, S0, T, K, N, L,P0T(T))
    exactBSHW = BSHWOptionPrice(CP,S0,K,P0T(T),T,eta,sigma,rho,lambd)
    
    IV = np.zeros([len(K),1])
    for idx in range(0,len(K)):
        frwdStock = S0 / P0T(T)
        valCOSFrwd = valCOS[idx] / P0T(T)
        IV[idx] = ImpliedVolatilityBlack76(CP,valCOSFrwd, K[idx], T, frwdStock)
    
    IVExact = BSHWVolatility(T,eta,sigma,rho,lambd)
    
    print(IVExact)
    # Plot option prices
    plt.figure(1)
    plt.plot(K,valCOS)
    plt.plot(K,exactBSHW,'--r')
    plt.grid()
    plt.xlabel("strike")
    plt.ylabel("option price")
    plt.legend(["BSHW, COS method","BSHW, exact solution"])
    
    # Plot implied volatilities
    plt.figure(2)
    plt.plot(K,IV*100.0)
    plt.plot(K,np.ones([len(K),1])*IVExact*100.0,'--r')
    plt.grid()
    plt.xlabel("strike")
    plt.ylabel("Implied Volatility [%]")
    plt.legend(["BSHW, COS method","BSHW, exact solution"])
    plt.axis([np.min(K),np.max(K),0,100])
    
mainCalculation()

================================================
FILE: Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/BSHW_ImpliedVolatility.py
================================================
#%%
"""
Created on July 05  2021
The BSHW model and implied volatilities term structure computerion

This code is purely educational and comes from "Financial Engineering" course by L.A. Grzelak
The course is based on the book “Mathematical Modeling and Computation
in Finance: With Exercises and Python and MATLAB Computer Codes”,
by C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.
@author: Lech A. Grzelak
"""

import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integrate
import scipy.stats as st
import enum 
import scipy.optimize as optimize

# set i= imaginary number
i   = np.complex(0.0,1.0)

# time-step needed for differentiation
dt = 0.0001
 
# This class defines puts and calls
class OptionType(enum.Enum):
    CALL = 1.0
    PUT = -1.0

# Black-Scholes Call option price
def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r):
    if K is list:
        K = np.array(K).reshape([len(K),1])
    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) 
    * tau) / float(sigma * np.sqrt(tau))
    d2    = d1 - sigma * np.sqrt(tau)
    if CP == OptionType.CALL:
        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)
    elif CP == OptionType.PUT:
        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0
    return value

# Implied volatility method
def ImpliedVolatilityBlack76(CP,frwdMarketPrice,K,T,frwdStock):
    func = lambda sigma: np.power(BS_Call_Option_Price(CP,frwdStock,K,sigma,T,0.0) - frwdMarketPrice, 1.0)
    impliedVol = optimize.newton(func, 0.2, tol=1e-9)
    #impliedVol = optimize.brent(func, brack= (0.05, 2))
    return impliedVol


def BSHWVolatility(T,eta,sigma,rho,lambd):
    Br= lambda t,T: 1/lambd * (np.exp(-lambd*(T-t))-1.0)
    sigmaF = lambda t: np.sqrt(sigma * sigma + eta * eta * Br(t,T) * Br(t,T)\
                               - 2.0 * rho * sigma * eta * Br(t,T)) 
    zGrid = np.linspace(0.0,T,2500)
    sigmaC = np.sqrt(1/T*integrate.trapz(sigmaF(zGrid)*sigmaF(zGrid), zGrid))
    return sigmaC

def BSHWOptionPrice(CP,S0,K,P0T,T,eta,sigma,rho,lambd):
    
    frwdS0 = S0 / P0T
    vol = BSHWVolatility(T,eta,sigma,rho,lambd)
    # As we deal with the forward prices we evaluate Black's 76 prices
    r = 0.0
    BlackPrice = BS_Call_Option_Price(CP,frwdS0,K,vol,T,r)
    return  P0T * BlackPrice

def mainCalculation():
    CP  = OptionType.CALL  
        
    # HW model settings
    lambd = 0.1
    eta   = 0.01
    sigma = 0.2
    rho   = 0.3
    S0    = 100
    
    # Strike equals stock value, thus ATM
    K = [100]
    K = np.array(K).reshape([len(K),1])
      
    # We define a ZCB curve (obtained from the market)
    P0T = lambda T: np.exp(-0.05*T) 
       
    # Maturitires at which we compute implied volatility
    TMat = np.linspace(0.1,5.0,20)
        
    # Effect of lambda
    plt.figure(2)
    plt.grid()
    plt.xlabel('maturity, T')
    plt.ylabel('implied volatility')
    lambdV = [0.001,0.1,0.5,1.5]
    legend = []
    for lambdaTemp in lambdV:    
       IV =np.zeros([len(TMat),1])
       for idx in range(0,len(TMat)):
           T = TMat[idx]
           val = BSHWOptionPrice(CP,S0,K,P0T(T),T,eta,sigma,rho,lambdaTemp)
           frwdStock = S0 / P0T(T)
           valFrwd = val / P0T(T)
           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K,T,frwdStock)
       plt.plot(TMat,IV*100.0)       
       legend.append('lambda={0}'.format(lambdaTemp))
    plt.legend(legend)    

    # Effect of eta
    plt.figure(3)
    plt.grid()
    plt.xlabel('maturity, T')
    plt.ylabel('implied volatility')
    etaV = [0.001,0.05,0.1,0.15]
    legend = []
    for etaTemp in etaV:    
       IV =np.zeros([len(TMat),1])
       for idx in range(0,len(TMat)):
           T = TMat[idx]
           val = BSHWOptionPrice(CP,S0,K,P0T(T),T,etaTemp,sigma,rho,lambd)
           frwdStock = S0 / P0T(T)
           valFrwd = val/P0T(T)
           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K,T,frwdStock)
       plt.plot(TMat,IV*100.0)       
       legend.append('eta={0}'.format(etaTemp))
    plt.legend(legend)    
    
    # Effect of sigma
    plt.figure(4)
    plt.grid()
    plt.xlabel('maturity, T')
    plt.ylabel('implied volatility')
    sigmaV = [0.1,0.2,0.3,0.4]
    legend = []
    for sigmaTemp in sigmaV:    
       IV =np.zeros([len(TMat),1])
       for idx in range(0,len(TMat)):
           T = TMat[idx]
           val = BSHWOptionPrice(CP,S0,K,P0T(T),T,eta,sigmaTemp,rho,lambd)
           frwdStock = S0 / P0T(T)
           valFrwd = val / P0T(T)
           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K,T,frwdStock)
       plt.plot(TMat,IV*100.0)       
       legend.append('sigma={0}'.format(sigmaTemp))
    plt.legend(legend)    

    # Effect of rho
    plt.figure(5)
    plt.grid()
    plt.xlabel('maturity, T')
    plt.ylabel('implied volatility')
    rhoV = [-0.7, -0.3, 0.3, 0.7]
    legend = []
    for rhoTemp in rhoV:    
       IV =np.zeros([len(TMat),1])
       for idx in range(0,len(TMat)):
           T = TMat[idx]
           val = BSHWOptionPrice(CP,S0,K,P0T(T),T,eta,sigma,rhoTemp,lambd)
           frwdStock = S0 / P0T(T)
           
Download .txt
gitextract__8ev3d3k/

├── LICENSE
├── Lecture 02-Understanding of Filtrations and Measures/
│   └── Materials/
│       ├── Black_Scholes_Jumps.py
│       └── Martingale.py
├── Lecture 03-The HJM Framework/
│   └── Materials/
│       ├── CIR_IR_paths.py
│       ├── Ho-Lee-ZCBs.py
│       ├── Hull-White-Paths.py
│       └── Hull-White-ZCBs.py
├── Lecture 04-Yield Curve Dynamics under Short Rate/
│   └── Materials/
│       ├── Hull-White-CompRateSim.py
│       ├── Hull-White-ZCBs2.py
│       └── Hull_White_1F_2F_Comparison.py
├── Lecture 05-Interest Rate Products/
│   └── Materials/
│       ├── HW_Caplets.py
│       ├── HW_OptionsOnZCBs.py
│       └── Swaps_HW.py
├── Lecture 06-Construction of Yield Curve and Multi-Curve/
│   └── Materials/
│       ├── MultiCurveBuild.py
│       ├── YieldCurveBuildGreeks.py
│       └── YieldCurveBuild_Treasury.py
├── Lecture 07-Pricing of Swaptions and Negative Interest Rates/
│   └── Materials/
│       ├── HW_CapletsAndFloorlets.py
│       ├── JamshidianTrick.py
│       └── ShiftedLognormal.py
├── Lecture 08-Mortgages and Prepayments/
│   └── Materials/
│       ├── AnnuityMortgage.py
│       ├── BulletMortgage.py
│       ├── Incentives.py
│       └── StochasticAmortizingSwap.py
├── Lecture 09-Hybrid Models and Stochastic Interest Rates/
│   └── Materials/
│       ├── BSHW_Comparison.py
│       ├── BSHW_ImpliedVolatility.py
│       ├── H1_HW_COS_vs_MC.py
│       ├── SZHW_ImpliedVolatilities.py
│       └── SZHW_MonteCarlo_DiversificationProduct.py
├── Lecture 10-Foreign Exchange (FX) and Inflation/
│   └── Materials/
│       └── H1_HW_COS_vs_MC_FX.py
├── Lecture 11-Market Model and Convexity Adjustments/
│   └── Materials/
│       ├── ConvexityCorrection.py
│       └── DD_ImpliedVolatility.py
├── Lecture 12-Valuation Adjustments- xVA (CVA,BCVA and FVA)/
│   └── Materials/
│       └── Exposures_HW_Netting.py
├── Lecture 13-Value-at-Risk and Expected Shortfall/
│   └── Materials/
│       ├── HistoricalVaR_Calculation.py
│       ├── MonteCarloVaR.py
│       └── MrktData.xlsx
└── README.md
Download .txt
SYMBOL INDEX (275 symbols across 33 files)

FILE: Lecture 02-Understanding of Filtrations and Measures/Materials/Black_Scholes_Jumps.py
  class OptionType (line 18) | class OptionType(enum.Enum):
  function GeneratePaths (line 22) | def GeneratePaths(NoOfPaths,NoOfSteps,S0,T,muJ,sigmaJ,r):
  function EUOptionPriceFromMCPaths (line 46) | def EUOptionPriceFromMCPaths(CP,S,K,T,r):
  function BS_Call_Put_Option_Price (line 53) | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,t,T,r):
  function CallOption_CondExpectation (line 64) | def CallOption_CondExpectation(NoOfPaths,T,S0,K,J,r):
  function mainCalculation (line 77) | def mainCalculation():

FILE: Lecture 02-Understanding of Filtrations and Measures/Materials/Martingale.py
  function martingaleA (line 20) | def martingaleA():
  function martingaleB (line 26) | def martingaleB():

FILE: Lecture 03-The HJM Framework/Materials/CIR_IR_paths.py
  function GeneratePathsCIREuler (line 15) | def GeneratePathsCIREuler(NoOfPaths,NoOfSteps,T,lambd,r0,theta,gamma):
  function mainCalculation (line 37) | def mainCalculation():

FILE: Lecture 03-The HJM Framework/Materials/Ho-Lee-ZCBs.py
  function f0T (line 16) | def f0T(t,P0T):
  function GeneratePathsHoLeeEuler (line 22) | def GeneratePathsHoLeeEuler(NoOfPaths,NoOfSteps,T,P0T,sigma):
  function mainCalculation (line 50) | def mainCalculation():

FILE: Lecture 03-The HJM Framework/Materials/Hull-White-Paths.py
  function GeneratePathsHWEuler (line 15) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function mainCalculation (line 44) | def mainCalculation():

FILE: Lecture 03-The HJM Framework/Materials/Hull-White-ZCBs.py
  function f0T (line 16) | def f0T(t,P0T):
  function GeneratePathsHWEuler (line 22) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 53) | def HW_theta(lambd,eta,P0T):
  function mainCalculation (line 60) | def mainCalculation():

FILE: Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull-White-CompRateSim.py
  function f0T (line 19) | def f0T(t,P0T):
  function GeneratePathsHWEuler (line 25) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 53) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 59) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 70) | def HW_B(lambd,eta,T1,T2):
  function HW2F_ZCB (line 73) | def HW2F_ZCB(lambd1,lambd2,eta1,eta2,rho,P0T,T1,T2,xT1,yT1):
  function HW_ZCB (line 85) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HW_r_0 (line 90) | def HW_r_0(P0T,lambd,eta):
  function mainCalculation (line 95) | def mainCalculation():

FILE: Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull-White-ZCBs2.py
  function f0T (line 17) | def f0T(t,P0T):
  function GeneratePathsHWEuler (line 23) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 54) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 60) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 69) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 72) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function mainCalculation (line 77) | def mainCalculation():

FILE: Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull_White_1F_2F_Comparison.py
  function GeneratePathsHW2FEuler (line 18) | def GeneratePathsHW2FEuler(NoOfPaths,NoOfSteps,T,P0T, lambd1,lambd2, eta...
  function f0T (line 55) | def f0T(t,P0T):
  function GeneratePathsHWEuler (line 61) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 89) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 95) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 106) | def HW_B(lambd,eta,T1,T2):
  function HW2F_ZCB (line 109) | def HW2F_ZCB(lambd1,lambd2,eta1,eta2,rho,P0T,T1,T2,xT1,yT1):
  function HW_ZCB (line 121) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HW_r_0 (line 126) | def HW_r_0(P0T,lambd,eta):
  function mainCalculation (line 131) | def mainCalculation():

FILE: Lecture 05-Interest Rate Products/Materials/HW_Caplets.py
  class OptionType (line 19) | class OptionType(enum.Enum):
  function GeneratePathsHWEuler (line 23) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 54) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 61) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 72) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 75) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HWMean_r (line 80) | def HWMean_r(P0T,lambd,eta,T):
  function HW_r_0 (line 92) | def HW_r_0(P0T,lambd,eta):
  function HW_Mu_FrwdMeasure (line 100) | def HW_Mu_FrwdMeasure(P0T,lambd,eta,T):
  function HWVar_r (line 117) | def HWVar_r(lambd,eta,T):
  function HWDensity (line 120) | def HWDensity(P0T,lambd,eta,T):
  function HW_CapletFloorletPrice (line 125) | def HW_CapletFloorletPrice(CP,N,K,lambd,eta,P0T,T1,T2):
  function HW_ZCB_CallPutPrice (line 135) | def HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):
  function mainCalculation (line 158) | def mainCalculation():

FILE: Lecture 05-Interest Rate Products/Materials/HW_OptionsOnZCBs.py
  class OptionType (line 20) | class OptionType(enum.Enum):
  function GeneratePathsHWEuler (line 24) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 55) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 62) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 73) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 76) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HWMean_r (line 81) | def HWMean_r(P0T,lambd,eta,T):
  function HW_r_0 (line 93) | def HW_r_0(P0T,lambd,eta):
  function HW_Mu_FrwdMeasure (line 101) | def HW_Mu_FrwdMeasure(P0T,lambd,eta,T):
  function HWVar_r (line 118) | def HWVar_r(lambd,eta,T):
  function HWDensity (line 121) | def HWDensity(P0T,lambd,eta,T):
  function HW_ZCB_CallPutPrice (line 126) | def HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):
  function mainCalculation (line 148) | def mainCalculation():

FILE: Lecture 05-Interest Rate Products/Materials/Swaps_HW.py
  class OptionTypeSwap (line 21) | class OptionTypeSwap(enum.Enum):
  function GeneratePathsHWEuler (line 25) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 56) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 63) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 74) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 77) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HW_r_0 (line 87) | def HW_r_0(P0T,lambd,eta):
  function SwapPrice (line 95) | def SwapPrice(CP,notional,K,t,Ti,Tm,n,P0T):
  function HW_SwapPrice (line 132) | def HW_SwapPrice(CP,notional,K,t,Ti,Tm,n,r_t,P0T,lambd,eta):
  function mainCalculation (line 174) | def mainCalculation():

FILE: Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/MultiCurveBuild.py
  class OptionTypeSwap (line 19) | class OptionTypeSwap(enum.Enum):
  function IRSwap (line 23) | def IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):
  function IRSwapMultiCurve (line 52) | def IRSwapMultiCurve(CP,notional,K,t,Ti,Tm,n,P0T,P0TFrd):
  function P0TModel (line 75) | def P0TModel(t,ti,ri,method):
  function YieldCurve (line 80) | def YieldCurve(instruments, maturities, r0, method, tol):
  function MultivariateNewtonRaphson (line 85) | def MultivariateNewtonRaphson(ri, ti, instruments, method, tol):
  function Jacobian (line 99) | def Jacobian(ti, ri, instruments, method):
  function EvaluateInstruments (line 114) | def EvaluateInstruments(ti,ri,instruments,method):
  function linear_interpolation (line 121) | def linear_interpolation(ti,ri):
  function spline_interpolate (line 125) | def spline_interpolate(ti,ri):
  function scipy_1d_interpolate (line 130) | def scipy_1d_interpolate(ti, ri):
  function mainCode (line 136) | def mainCode():

FILE: Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/YieldCurveBuildGreeks.py
  class OptionTypeSwap (line 18) | class OptionTypeSwap(enum.Enum):
  function IRSwap (line 22) | def IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):
  function P0TModel (line 58) | def P0TModel(t,ti,ri,method):
  function YieldCurve (line 69) | def YieldCurve(instruments, maturities, r0, method, tol):
  function MultivariateNewtonRaphson (line 74) | def MultivariateNewtonRaphson(ri, ti, instruments, method, tol):
  function Jacobian (line 88) | def Jacobian(ti, ri, instruments, method):
  function EvaluateInstruments (line 103) | def EvaluateInstruments(ti,ri,instruments,method):
  function linear_interpolation (line 110) | def linear_interpolation(ti,ri):
  function quadratic_interpolation (line 114) | def quadratic_interpolation(ti, ri):
  function cubic_interpolation (line 118) | def cubic_interpolation(ti, ri):
  function BuildInstruments (line 124) | def BuildInstruments(K,mat):
  function mainCode (line 136) | def mainCode():

FILE: Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/YieldCurveBuild_Treasury.py
  class OptionTypeSwap (line 19) | class OptionTypeSwap(enum.Enum):
  function IRSwap (line 23) | def IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):
  function P0TModel (line 59) | def P0TModel(t,ti,ri,method):
  function YieldCurve (line 64) | def YieldCurve(instruments, maturities, r0, method, tol):
  function MultivariateNewtonRaphson (line 69) | def MultivariateNewtonRaphson(ri, ti, instruments, method, tol):
  function Jacobian (line 83) | def Jacobian(ti, ri, instruments, method):
  function EvaluateInstruments (line 98) | def EvaluateInstruments(ti,ri,instruments,method):
  function linear_interpolation (line 105) | def linear_interpolation(ti,ri):
  function mainCode (line 109) | def mainCode():

FILE: Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/HW_CapletsAndFloorlets.py
  class OptionType (line 20) | class OptionType(enum.Enum):
  function GeneratePathsHWEuler (line 24) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 55) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 62) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 73) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 76) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HWMean_r (line 81) | def HWMean_r(P0T,lambd,eta,T):
  function HW_r_0 (line 93) | def HW_r_0(P0T,lambd,eta):
  function HW_Mu_FrwdMeasure (line 101) | def HW_Mu_FrwdMeasure(P0T,lambd,eta,T):
  function HWVar_r (line 118) | def HWVar_r(lambd,eta,T):
  function HWDensity (line 121) | def HWDensity(P0T,lambd,eta,T):
  function HW_CapletFloorletPrice (line 126) | def HW_CapletFloorletPrice(CP,N,K,lambd,eta,P0T,T1,T2):
  function HW_ZCB_CallPutPrice (line 139) | def HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):
  function BS_Call_Put_Option_Price (line 162) | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):
  function ImpliedVolatilityBlack76 (line 174) | def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
  function mainCalculation (line 191) | def mainCalculation():

FILE: Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/JamshidianTrick.py
  function PsiSum (line 16) | def PsiSum(psi,N,x):
  function JamshidianTrick (line 22) | def JamshidianTrick(psi,N,K):
  function Main (line 27) | def Main():

FILE: Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/ShiftedLognormal.py
  class OptionType (line 20) | class OptionType(enum.Enum):
  function GeneratePathsGBMShifted (line 24) | def GeneratePathsGBMShifted(NoOfPaths,NoOfSteps,T,r,sigma,S_0,shift):
  function GeneratePathsGBM (line 34) | def GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0):
  function BS_Call_Put_Option_Price_Shifted (line 57) | def BS_Call_Put_Option_Price_Shifted(CP,S_0,K,sigma,tau,r,shift):
  function BS_Call_Put_Option_Price (line 63) | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):
  function ImpliedVolatilityBlack76 (line 75) | def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
  function ImpliedVolatilityBlack76Shifted (line 90) | def ImpliedVolatilityBlack76Shifted(CP,marketPrice,K,T,S_0,shift):
  function mainCalculation (line 104) | def mainCalculation():

FILE: Lecture 08-Mortgages and Prepayments/Materials/AnnuityMortgage.py
  function Annuity (line 15) | def Annuity(rate,notional,periods,CPR):
  function mainCode (line 43) | def mainCode():

FILE: Lecture 08-Mortgages and Prepayments/Materials/BulletMortgage.py
  function Bullet (line 15) | def Bullet(rate,notional,periods,CPR):
  function mainCode (line 36) | def mainCode():

FILE: Lecture 08-Mortgages and Prepayments/Materials/Incentives.py
  function Annuity (line 15) | def Annuity(rate,notional,periods,CPR):
  function mainCode (line 43) | def mainCode():

FILE: Lecture 08-Mortgages and Prepayments/Materials/StochasticAmortizingSwap.py
  function GeneratePathsHWEuler (line 17) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 48) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 54) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 65) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 68) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function SwapRateHW (line 78) | def SwapRateHW(t,Ti,Tm,n,r_t,P0T,lambd,eta):
  function Bullet (line 117) | def Bullet(rate,notional,periods,CPR):
  function Annuity (line 138) | def Annuity(rate,notional,periods,CPR):
  function mainCode (line 166) | def mainCode():

FILE: Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/BSHW_Comparison.py
  class OptionType (line 27) | class OptionType(enum.Enum):
  function CallPutOptionPriceCOSMthd_StochIR (line 31) | def CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):
  function CallPutCoefficients (line 71) | def CallPutCoefficients(CP,a,b,k):
  function Chi_Psi (line 92) | def Chi_Psi(a,b,c,d,k):
  function BS_Call_Option_Price (line 109) | def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r):
  function ImpliedVolatilityBlack76 (line 122) | def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
  function ChFBSHW (line 128) | def ChFBSHW(u, T, P0T, lambd, eta, rho, sigma):
  function BSHWVolatility (line 159) | def BSHWVolatility(T,eta,sigma,rho,lambd):
  function BSHWOptionPrice (line 167) | def BSHWOptionPrice(CP,S0,K,P0T,T,eta,sigma,rho,lambd):
  function mainCalculation (line 176) | def mainCalculation():

FILE: Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/BSHW_ImpliedVolatility.py
  class OptionType (line 27) | class OptionType(enum.Enum):
  function BS_Call_Option_Price (line 32) | def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r):
  function ImpliedVolatilityBlack76 (line 45) | def ImpliedVolatilityBlack76(CP,frwdMarketPrice,K,T,frwdStock):
  function BSHWVolatility (line 52) | def BSHWVolatility(T,eta,sigma,rho,lambd):
  function BSHWOptionPrice (line 60) | def BSHWOptionPrice(CP,S0,K,P0T,T,eta,sigma,rho,lambd):
  function mainCalculation (line 69) | def mainCalculation():

FILE: Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/H1_HW_COS_vs_MC.py
  class OptionType (line 20) | class OptionType(enum.Enum):
  function CallPutOptionPriceCOSMthd_StochIR (line 24) | def CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):
  function CallPutCoefficients (line 64) | def CallPutCoefficients(CP,a,b,k):
  function Chi_Psi (line 85) | def Chi_Psi(a,b,c,d,k):
  function EUOptionPriceFromMCPathsGeneralizedStochIR (line 101) | def EUOptionPriceFromMCPathsGeneralizedStochIR(CP,S,K,T,M):
  function CIR_Sample (line 113) | def CIR_Sample(NoOfPaths,kappa,gamma,vbar,s,t,v_s):
  function GeneratePathsHestonHW_AES (line 120) | def GeneratePathsHestonHW_AES(NoOfPaths,NoOfSteps,P0T,T,S_0,kappa,gamma,...
  function GeneratePathsHestonHWEuler (line 181) | def GeneratePathsHestonHWEuler(NoOfPaths,NoOfSteps,P0T,T,S_0,kappa,gamma...
  function meanSqrtV_3 (line 240) | def meanSqrtV_3(kappa,v0,vbar,gamma):
  function C_H1HW (line 247) | def C_H1HW(u,tau,lambd):
  function D_H1HW (line 252) | def D_H1HW(u,tau,kappa,gamma,rhoxv):
  function A_H1HW (line 261) | def A_H1HW(u,tau,P0T,lambd,eta,kappa,gamma,vbar,v0,rhoxv,rhoxr):
  function ChFH1HWModel (line 290) | def ChFH1HWModel(P0T,lambd,eta,tau,kappa,gamma,vbar,v0,rhoxv, rhoxr):
  function mainCalculation (line 301) | def mainCalculation():

FILE: Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/SZHW_ImpliedVolatilities.py
  class OptionType (line 27) | class OptionType(enum.Enum):
  function CallPutOptionPriceCOSMthd_StochIR (line 32) | def CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):
  function CallPutCoefficients (line 72) | def CallPutCoefficients(CP,a,b,k):
  function Chi_Psi (line 93) | def Chi_Psi(a,b,c,d,k):
  function BS_Call_Put_Option_Price (line 110) | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):
  function ImpliedVolatilityBlack76 (line 123) | def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
  function C (line 140) | def C(u,tau,lambd):
  function D (line 144) | def D(u,tau,kappa,Rxsigma,gamma):
  function E (line 153) | def E(u,tau,lambd,gamma,Rxsigma,Rrsigma,Rxr,eta,kappa,sigmabar):
  function A (line 173) | def A(u,tau,eta,lambd,Rxsigma,Rrsigma,Rxr,gamma,kappa,sigmabar):
  function ChFSZHW (line 198) | def ChFSZHW(u,P0T,sigma0,tau,lambd,gamma,    Rxsigma,Rrsigma,Rxr,eta,kap...
  function ChFBSHW (line 212) | def ChFBSHW(u, T, P0T, lambd, eta, rho, sigma):
  function mainCalculation (line 240) | def mainCalculation():

FILE: Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/SZHW_MonteCarlo_DiversificationProduct.py
  class OptionType (line 25) | class OptionType(enum.Enum):
  function HW_theta (line 29) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 35) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 46) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 49) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function EUOptionPriceFromMCPathsGeneralizedStochIR (line 54) | def EUOptionPriceFromMCPathsGeneralizedStochIR(CP,S,K,T,M):
  function GeneratePathsSZHWEuler (line 65) | def GeneratePathsSZHWEuler(NoOfPaths,NoOfSteps,P0T,T,S0,sigma0,sigmabar,...
  function DiversifcationPayoff (line 117) | def DiversifcationPayoff(P0T,S_T,S0,r_T,M_T,T,T1,lambd,eta,omegaV):
  function mainCalculation (line 127) | def mainCalculation():

FILE: Lecture 10-Foreign Exchange (FX) and Inflation/Materials/H1_HW_COS_vs_MC_FX.py
  class OptionType (line 22) | class OptionType(enum.Enum):
  function BS_Call_Put_Option_Price (line 27) | def BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):
  function ImpliedVolatilityBlack76 (line 40) | def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
  function CallPutOptionPriceCOSMthd_StochIR (line 57) | def CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):
  function CallPutCoefficients (line 97) | def CallPutCoefficients(CP,a,b,k):
  function Chi_Psi (line 118) | def Chi_Psi(a,b,c,d,k):
  function EUOptionPriceFromMCPathsGeneralizedFXFrwd (line 134) | def EUOptionPriceFromMCPathsGeneralizedFXFrwd(CP,S,K):
  function GeneratePathsHHWFXHWEuler (line 145) | def GeneratePathsHHWFXHWEuler(NoOfPaths,NoOfSteps,T,frwdFX,v0,vbar,kappa...
  function meanSqrtV_3 (line 194) | def meanSqrtV_3(kappa,v0,vbar,gamma):
  function C_H1HW_FX (line 201) | def C_H1HW_FX(u,tau,kappa,gamma,rhoxv):
  function ChFH1HW_FX (line 210) | def ChFH1HW_FX(u,tau,gamma,Rxv,Rxrd,Rxrf,Rrdrf,Rvrd,Rvrf,lambdd,etad,lam...
  function GenerateStrikes (line 250) | def GenerateStrikes(frwd,Ti):
  function mainCalculation (line 254) | def mainCalculation():

FILE: Lecture 11-Market Model and Convexity Adjustments/Materials/ConvexityCorrection.py
  class OptionType (line 18) | class OptionType(enum.Enum):
  function GeneratePathsHWEuler (line 22) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 53) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 60) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 71) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 74) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HWMean_r (line 79) | def HWMean_r(P0T,lambd,eta,T):
  function HW_r_0 (line 91) | def HW_r_0(P0T,lambd,eta):
  function mainCalculation (line 99) | def mainCalculation():

FILE: Lecture 11-Market Model and Convexity Adjustments/Materials/DD_ImpliedVolatility.py
  class OptionType (line 25) | class OptionType(enum.Enum):
  function BS_Call_Option_Price (line 30) | def BS_Call_Option_Price(CP,S_0,K,sigma,tau,r):
  function ImpliedVolatilityBlack76_xxx (line 42) | def ImpliedVolatilityBlack76_xxx(CP,marketPrice,K,T,S_0):
  function ImpliedVolatilityBlack76 (line 49) | def ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):
  function DisplacedDiffusionModel_CallPrice (line 63) | def DisplacedDiffusionModel_CallPrice(K,P0T,beta,sigma,frwd,T):
  function mainCalculation (line 68) | def mainCalculation():

FILE: Lecture 12-Valuation Adjustments- xVA (CVA,BCVA and FVA)/Materials/Exposures_HW_Netting.py
  class OptionTypeSwap (line 20) | class OptionTypeSwap(enum.Enum):
  function GeneratePathsHWEuler (line 24) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 55) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 62) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 73) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 76) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HWMean_r (line 87) | def HWMean_r(P0T,lambd,eta,T):
  function HW_r_0 (line 99) | def HW_r_0(P0T,lambd,eta):
  function HW_Mu_FrwdMeasure (line 107) | def HW_Mu_FrwdMeasure(P0T,lambd,eta,T):
  function HWVar_r (line 124) | def HWVar_r(lambd,eta,T):
  function HWDensity (line 127) | def HWDensity(P0T,lambd,eta,T):
  function HW_SwapPrice (line 132) | def HW_SwapPrice(CP,notional,K,t,Ti,Tm,n,r_t,P0T,lambd,eta):
  function mainCalculation (line 174) | def mainCalculation():

FILE: Lecture 13-Value-at-Risk and Expected Shortfall/Materials/HistoricalVaR_Calculation.py
  class OptionTypeSwap (line 21) | class OptionTypeSwap(enum.Enum):
  function IRSwap (line 25) | def IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):
  function P0TModel (line 61) | def P0TModel(t,ti,ri,method):
  function YieldCurve (line 66) | def YieldCurve(instruments, maturities, r0, method, tol):
  function MultivariateNewtonRaphson (line 71) | def MultivariateNewtonRaphson(ri, ti, instruments, method, tol):
  function Jacobian (line 85) | def Jacobian(ti, ri, instruments, method):
  function EvaluateInstruments (line 100) | def EvaluateInstruments(ti,ri,instruments,method):
  function linear_interpolation (line 107) | def linear_interpolation(ti,ri):
  function BuildYieldCurve (line 111) | def BuildYieldCurve(K,mat):
  function Portfolio (line 137) | def Portfolio(P0T):
  function mainCode (line 152) | def mainCode():

FILE: Lecture 13-Value-at-Risk and Expected Shortfall/Materials/MonteCarloVaR.py
  class OptionTypeSwap (line 21) | class OptionTypeSwap(enum.Enum):
  function GeneratePathsHWEuler (line 25) | def GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):
  function HW_theta (line 56) | def HW_theta(lambd,eta,P0T):
  function HW_A (line 63) | def HW_A(lambd,eta,P0T,T1,T2):
  function HW_B (line 74) | def HW_B(lambd,eta,T1,T2):
  function HW_ZCB (line 77) | def HW_ZCB(lambd,eta,P0T,T1,T2,rT1):
  function HWMean_r (line 88) | def HWMean_r(P0T,lambd,eta,T):
  function HW_r_0 (line 100) | def HW_r_0(P0T,lambd,eta):
  function HW_Mu_FrwdMeasure (line 108) | def HW_Mu_FrwdMeasure(P0T,lambd,eta,T):
  function HWVar_r (line 125) | def HWVar_r(lambd,eta,T):
  function HWDensity (line 128) | def HWDensity(P0T,lambd,eta,T):
  function HW_SwapPrice (line 133) | def HW_SwapPrice(CP,notional,K,t,Ti,Tm,n,r_t,P0T,lambd,eta):
  function Portfolio (line 175) | def Portfolio(P0T,r_t,lambd,eta):
  function mainCalculation (line 191) | def mainCalculation():
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (299K chars).
[
  {
    "path": "LICENSE",
    "chars": 1493,
    "preview": "BSD 3-Clause License\n\nCopyright (c) 2024, leszek\n\nRedistribution and use in source and binary forms, with or without\nmod"
  },
  {
    "path": "Lecture 02-Understanding of Filtrations and Measures/Materials/Black_Scholes_Jumps.py",
    "chars": 4181,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nImpact of conditional expectation pricing (Black-Scholes with Jump volatility)\r\n\r\nTh"
  },
  {
    "path": "Lecture 02-Understanding of Filtrations and Measures/Materials/Martingale.py",
    "chars": 2686,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nSimulation of, E(W(t)|F(s)) = W(s) using nested Monte Carlo\r\n\r\nThis code is purely e"
  },
  {
    "path": "Lecture 03-The HJM Framework/Materials/CIR_IR_paths.py",
    "chars": 2572,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 11 2021\r\nMonte Carlo Paths for the CIR Process\r\n\r\nThis code is purely educational and comes fr"
  },
  {
    "path": "Lecture 03-The HJM Framework/Materials/Ho-Lee-ZCBs.py",
    "chars": 2652,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nHo-Lee Model, Simulation of the Model + Computation of ZCBs, P(0,t)\r\n\r\nThis code is p"
  },
  {
    "path": "Lecture 03-The HJM Framework/Materials/Hull-White-Paths.py",
    "chars": 2870,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nMonte Carlo paths for the Hull-White model\r\n\r\nThis code is purely educational and com"
  },
  {
    "path": "Lecture 03-The HJM Framework/Materials/Hull-White-ZCBs.py",
    "chars": 3061,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nHull-White Model, Simulation of the Model\r\n\r\nThis code is purely educational and come"
  },
  {
    "path": "Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull-White-CompRateSim.py",
    "chars": 22230,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nYield Curve shapes and the Hull-White Model 1 Factor\r\n\r\nThis code is purely education"
  },
  {
    "path": "Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull-White-ZCBs2.py",
    "chars": 4101,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nHull-White Model, Simulation of the Model + Computation of ZCBs, P(0,t)\r\n\r\nThis code "
  },
  {
    "path": "Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull_White_1F_2F_Comparison.py",
    "chars": 24949,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nYield Curve shapes and the Hull-White Model (1 and 2 factor cases)\r\n\r\nThis code is pu"
  },
  {
    "path": "Lecture 05-Interest Rate Products/Materials/HW_Caplets.py",
    "chars": 8182,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nCaplets under the Hull-White Model\r\n\r\nThis code is purely educational and comes from "
  },
  {
    "path": "Lecture 05-Interest Rate Products/Materials/HW_OptionsOnZCBs.py",
    "chars": 23971,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nOptions on ZCBs under the Hull-White model\r\n\r\nThis code is purely educational and com"
  },
  {
    "path": "Lecture 05-Interest Rate Products/Materials/Swaps_HW.py",
    "chars": 24692,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nSwaps computed from a yield curve and from the Hull-White Model\r\n\r\nThis code is purel"
  },
  {
    "path": "Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/MultiCurveBuild.py",
    "chars": 8550,
    "preview": "#%%\r\n\"\"\"\r\nCreated on June 27 2021\r\nConstruction of a multi- curve for a given set of swap instruments\r\n\r\nThis code is pu"
  },
  {
    "path": "Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/YieldCurveBuildGreeks.py",
    "chars": 5742,
    "preview": "#%%\r\n\"\"\"\r\nCreated on June 27 2021\r\nConstruction of a yield curve for a given set of swap instruments\r\n\r\nThis code is pur"
  },
  {
    "path": "Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/YieldCurveBuild_Treasury.py",
    "chars": 5299,
    "preview": "#%%\r\n\"\"\"\r\nCreated on June 27 2021\r\nConstruction of a yield curve for a given set of swap instruments\r\n\r\nThis code is pur"
  },
  {
    "path": "Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/HW_CapletsAndFloorlets.py",
    "chars": 10763,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nCaplets and floorlets under the Hull-White Model\r\n\r\nThis code is purely educational a"
  },
  {
    "path": "Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/JamshidianTrick.py",
    "chars": 1706,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 18 2021\r\nJamshidian's trick for handling E max sum -> sum E max \r\n\r\nThis code is purely educat"
  },
  {
    "path": "Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/ShiftedLognormal.py",
    "chars": 6530,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 12 2021\r\nShifted GBM and pricing of caplets/floorlets\r\n\r\nThis code is purely educational and c"
  },
  {
    "path": "Lecture 08-Mortgages and Prepayments/Materials/AnnuityMortgage.py",
    "chars": 2461,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nAnnuity mortgage- payment profile\r\n\r\nThis code is purely educational and comes from "
  },
  {
    "path": "Lecture 08-Mortgages and Prepayments/Materials/BulletMortgage.py",
    "chars": 2331,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nBullet mortgage- payment profile\r\n\r\nThis code is purely educational and comes from \""
  },
  {
    "path": "Lecture 08-Mortgages and Prepayments/Materials/Incentives.py",
    "chars": 2385,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nIncentive function as a function of a swap rate or the differential w.r.t. \"old\" mor"
  },
  {
    "path": "Lecture 08-Mortgages and Prepayments/Materials/StochasticAmortizingSwap.py",
    "chars": 8790,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nStochastic amortization given the incentive function and irrational/rational behavio"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/BSHW_Comparison.py",
    "chars": 8042,
    "preview": "#%%\r\n\"\"\"\r\nCreated on Thu Jan 04 2019\r\nThe BSHW model and implied volatilities obtained with the COS method and compariso"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/BSHW_ImpliedVolatility.py",
    "chars": 5529,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nThe BSHW model and implied volatilities term structure computerion\r\n\r\nThis code is p"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/H1_HW_COS_vs_MC.py",
    "chars": 13837,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nThe Heston-Hull-White model and pricing European Options with Monte Carlo and the CO"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/SZHW_ImpliedVolatilities.py",
    "chars": 13072,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nThe SZHW model and implied volatilities\r\n\r\nThis code is purely educational and comes"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/SZHW_MonteCarlo_DiversificationProduct.py",
    "chars": 7034,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nThe SZHW model and pricing of diversification product\r\n\r\nThis code is purely educati"
  },
  {
    "path": "Lecture 10-Foreign Exchange (FX) and Inflation/Materials/H1_HW_COS_vs_MC_FX.py",
    "chars": 13175,
    "preview": "#%%\r\n\"\"\"\r\nCreated on August 25 2021\r\nThe Heston Hull-White model used for pricing of European type of FX options using \r"
  },
  {
    "path": "Lecture 11-Market Model and Convexity Adjustments/Materials/ConvexityCorrection.py",
    "chars": 5943,
    "preview": "#%%\r\n\"\"\"\r\nCreated on August 25 2021\r\nConvexity correction exercise\r\n\r\nThis code is purely educational and comes from \"Fi"
  },
  {
    "path": "Lecture 11-Market Model and Convexity Adjustments/Materials/DD_ImpliedVolatility.py",
    "chars": 4424,
    "preview": "#%%\r\n\"\"\"\r\nCreated on August 25 2021\r\nDisplaced Diffusion and implied volatilities\r\n\r\nThis code is purely educational and"
  },
  {
    "path": "Lecture 12-Valuation Adjustments- xVA (CVA,BCVA and FVA)/Materials/Exposures_HW_Netting.py",
    "chars": 10201,
    "preview": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nExposures for an IR swaps, under the Hull-White model - case of netting\r\n\r\nThis code"
  },
  {
    "path": "Lecture 13-Value-at-Risk and Expected Shortfall/Materials/HistoricalVaR_Calculation.py",
    "chars": 8066,
    "preview": "#%%\r\n\"\"\"\r\nCreated on August 27 2021\r\n\r\nHistorical Value-at-Risk Calculation based on real market data \r\nhttps://www.trea"
  },
  {
    "path": "Lecture 13-Value-at-Risk and Expected Shortfall/Materials/MonteCarloVaR.py",
    "chars": 9434,
    "preview": "#%%\r\n\"\"\"\r\nCreated on August 25  2021\r\nValue-At-Risk computation based on Monte Carlo simulation of the Hull-White model\r"
  },
  {
    "path": "README.md",
    "chars": 1268,
    "preview": "# Financial Engineering Course\nHere you will find materials for the course of \"Financial Engineering: Interest rates & x"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the LechGrzelak/FinancialEngineering_IR_xVA GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (275.6 KB), approximately 107.2k tokens, and a symbol index with 275 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!