[
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2024, leszek\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Lecture 02-Understanding of Filtrations and Measures/Materials/Black_Scholes_Jumps.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nImpact of conditional expectation pricing (Black-Scholes with Jump volatility)\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport enum\r\nimport scipy.stats as st\r\n\r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n    \r\ndef GeneratePaths(NoOfPaths,NoOfSteps,S0,T,muJ,sigmaJ,r):    \r\n    # Create empty matrices for Poisson process and for compensated Poisson process\r\n    X = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    S = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    time = np.zeros([NoOfSteps+1])\r\n                \r\n    dt = T / float(NoOfSteps)\r\n    X[:,0] = np.log(S0)\r\n    S[:,0] = S0\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    J = np.random.normal(muJ,sigmaJ,[NoOfPaths,NoOfSteps])\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n            \r\n        X[:,i+1]  = X[:,i] + (r - 0.5*J[:,i]**2.0)*dt +J[:,i]*np.sqrt(dt)* Z[:,i]\r\n        time[i+1] = time[i] +dt\r\n        \r\n    S = np.exp(X)\r\n    paths = {\"time\":time,\"X\":X,\"S\":S,\"J\":J}\r\n    return paths\r\n\r\ndef EUOptionPriceFromMCPaths(CP,S,K,T,r):\r\n    # S is a vector of Monte Carlo samples at T\r\n    if CP == OptionType.CALL:\r\n        return np.exp(-r*T)*np.mean(np.maximum(S-K,0.0))\r\n    elif CP == OptionType.PUT:\r\n        return np.exp(-r*T)*np.mean(np.maximum(K-S,0.0))\r\n\r\ndef BS_Call_Put_Option_Price(CP,S_0,K,sigma,t,T,r):\r\n    K = np.array(K).reshape([len(K),1])\r\n    d1 = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0))\r\n    * (T-t)) / (sigma * np.sqrt(T-t))\r\n    d2 = d1 - sigma * np.sqrt(T-t)\r\n    if CP == OptionType.CALL:\r\n        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * (T-t))\r\n    elif CP == OptionType.PUT:\r\n        value = st.norm.cdf(-d2) * K * np.exp(-r * (T-t)) - st.norm.cdf(-d1)*S_0\r\n    return value\r\n\r\ndef CallOption_CondExpectation(NoOfPaths,T,S0,K,J,r):\r\n    \r\n    # Jumps at time T\r\n    J_i = J[:,-1]\r\n    \r\n    result = np.zeros([NoOfPaths])\r\n    \r\n    for j in range(0,NoOfPaths):\r\n        sigma = J_i[j]\r\n        result[j] = BS_Call_Put_Option_Price(OptionType.CALL,S0,[K],sigma,0.0,T,r)\r\n        \r\n    return np.mean(result)\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 25\r\n    NoOfSteps = 500\r\n    T = 5\r\n    muJ = 0.3\r\n    sigmaJ = 0.005\r\n    \r\n    S0 =100\r\n    r  =0.00\r\n    Paths = GeneratePaths(NoOfPaths,NoOfSteps,S0, T,muJ,sigmaJ,r)\r\n    timeGrid = Paths[\"time\"]\r\n    X = Paths[\"X\"]\r\n    S = Paths[\"S\"]\r\n           \r\n    plt.figure(1)\r\n    plt.plot(timeGrid, np.transpose(X))   \r\n    plt.grid()\r\n    plt.xlabel(\"time\")\r\n    plt.ylabel(\"X(t)\")\r\n    \r\n    plt.figure(2)\r\n    plt.plot(timeGrid, np.transpose(S))   \r\n    plt.grid()\r\n    plt.xlabel(\"time\")\r\n    plt.ylabel(\"S(t)\")\r\n    \r\n    # Check the convergence for a given strike\r\n    K = 80\r\n    CP =OptionType.CALL\r\n    \r\n    NGrid = range(100,10000,1000)\r\n    NoOfRuns = len(NGrid)\r\n    \r\n    resultMC = np.zeros([NoOfRuns])\r\n    resultCondExp = np.zeros([NoOfRuns])\r\n       \r\n    for (i,N) in enumerate(NGrid):\r\n            print(N)\r\n            Paths = GeneratePaths(N,NoOfSteps,S0, T,muJ,sigmaJ,r)\r\n            timeGrid = Paths[\"time\"]\r\n            S = Paths[\"S\"]\r\n            resultMC[i] = EUOptionPriceFromMCPaths(CP,S[:,-1],K,T,r)\r\n            \r\n            J = Paths[\"J\"]\r\n\r\n            resultCondExp[i]=CallOption_CondExpectation(N,T,S0,K,J,r)\r\n    \r\n    plt.figure(3)\r\n    plt.plot(NGrid,resultMC)  \r\n    plt.plot(NGrid,resultCondExp)\r\n    plt.legend(['MC','Conditional Expectation'])\r\n    plt.title('Call Option Price- Convergence')\r\n    plt.xlabel('Number of Paths')\r\n    plt.ylabel('Option price for a given strike, K')\r\n    plt.grid()\r\n                       \r\nmainCalculation()"
  },
  {
    "path": "Lecture 02-Understanding of Filtrations and Measures/Materials/Martingale.py",
    "content": "#%%\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 educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nt = 10 \r\ns = 5\r\nNoOfPaths=1000\r\nNoOfSteps=10\r\n\r\n# First part to caclulate E(W(t)|F(0)) = W(0)=0\r\ndef martingaleA():\r\n    W_t = np.random.normal(0.0,pow(t,0.5),[NoOfPaths,1])\r\n    E_W_t = np.mean(W_t)\r\n    print(\"mean value equals to: %.2f while the expected value is W(0) =%0.2f \" %(E_W_t,0.0))\r\n    \r\n# Second part requiring nested Monte Carlo simulation  E(W(t)|F(s)) = W(s)\r\ndef martingaleB():    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths,NoOfSteps+1])\r\n        \r\n    # time-step from [t0,s]\r\n    dt1 = s / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + pow(dt1,0.5)*Z[:,i]\r\n            \r\n    #W_s is the last column of W\r\n    W_s = W[:,-1]\r\n    #for every path W(s) we perform sub-simulation until time t and calculate\r\n    #the expectation\r\n    # time-step from [s,t]\r\n    dt2     = (t-s)/float(NoOfSteps);\r\n    W_t     = np.zeros([NoOfPaths,NoOfSteps+1]);\r\n    \r\n    #Store the results\r\n    E_W_t = np.zeros([NoOfPaths])\r\n    Error=[]\r\n    for i in range(0,NoOfPaths):\r\n        #Sub-simulation from time \"s\" until \"t\"\r\n        W_t[:,0] = W_s[i];\r\n        Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n        for j in range(0,NoOfSteps):\r\n            #this is a scaling that ensures that Z has mean 0 and variance 1\r\n            Z[:,j] = (Z[:,j]-np.mean(Z[:,j])) / np.std(Z[:,j]);\r\n            #path simulation, from \"s\" until \"t\"\r\n            W_t[:,j+1] = W_t[:,j] + pow(dt2,0.5)*Z[:,j];        \r\n            \r\n        E_W_t[i]=np.mean(W_t[:,-1])\r\n        Error.append(E_W_t[i]-W_s[i])\r\n        \r\n        #Generate a plot for the first path\r\n        if i==0:\r\n            plt.plot(np.linspace(0,s,NoOfSteps+1),W[0,:])\r\n            for j in range(0,NoOfPaths):\r\n                plt.plot(np.linspace(s,t,NoOfSteps+1),W_t[j,:])\r\n            plt.xlabel(\"time\")\r\n            plt.ylabel(\"W(t)\")\r\n            plt.grid()\r\n        \r\n    print(Error)\r\n    error = np.max(np.abs(E_W_t-W_s))\r\n    print(\"The error is equal to: %.18f\"%(error))\r\n    \r\nmartingaleB()\r\n    "
  },
  {
    "path": "Lecture 03-The HJM Framework/Materials/CIR_IR_paths.py",
    "content": "#%%\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 from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\n\r\ndef GeneratePathsCIREuler(NoOfPaths,NoOfSteps,T,lambd,r0,theta,gamma):    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta - R[:,i]) * dt + gamma* np.sqrt(R[:,i]) * (W[:,i+1]-W[:,i])\r\n        \r\n        # Truncated boundary condition\r\n        R[:,i+1] = np.maximum(R[:,i+1],0.0)\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\ndef mainCalculation():\r\n    NoOfPaths = 1\r\n    NoOfSteps = 500\r\n    T         = 50.0\r\n    lambd     = 0.1\r\n    gamma     = 0.05\r\n    r0        = 0.05\r\n    theta     = 0.05\r\n    \r\n    # Effect of mean reversion lambda\r\n    plt.figure(1) \r\n    legend = []\r\n    lambdVec = [0.01, 0.2, 5.0]\r\n    for lambdTemp in lambdVec:    \r\n        np.random.seed(2)\r\n        Paths = GeneratePathsCIREuler(NoOfPaths,NoOfSteps,T,lambdTemp,r0,theta,gamma)\r\n        legend.append('lambda={0}'.format(lambdTemp))\r\n        timeGrid = Paths[\"time\"]\r\n        R = Paths[\"R\"]\r\n        plt.plot(timeGrid, np.transpose(R))   \r\n    plt.grid()\r\n    plt.xlabel(\"time\")\r\n    plt.ylabel(\"R(t)\")\r\n    plt.legend(legend)\r\n        \r\n    # Effect of the volatility\r\n    plt.figure(2)    \r\n    legend = []\r\n    gammaVec = [0.1, 0.2, 0.3]\r\n    for gammaTemp in gammaVec:\r\n        np.random.seed(2)\r\n        Paths = GeneratePathsCIREuler(NoOfPaths,NoOfSteps,T,lambd,r0,theta,gammaTemp)\r\n        legend.append('gamma={0}'.format(gammaTemp))\r\n        timeGrid = Paths[\"time\"]\r\n        R = Paths[\"R\"]\r\n        plt.plot(timeGrid, np.transpose(R))   \r\n    plt.grid()\r\n    plt.xlabel(\"time\")\r\n    plt.ylabel(\"R(t)\")\r\n    plt.legend(legend)\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 03-The HJM Framework/Materials/Ho-Lee-ZCBs.py",
    "content": "#%%\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 purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\n\r\ndef f0T(t,P0T):\r\n    # time-step needed for differentiation\r\n    dt = 0.01    \r\n    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    return expr\r\n\r\ndef GeneratePathsHoLeeEuler(NoOfPaths,NoOfSteps,T,P0T,sigma):    \r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.01,P0T)\r\n    theta = lambda t: (f0T(t+dt,P0T)-f0T(t-dt,P0T))/(2.0*dt) + sigma**2.0*t \r\n     \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M[:,0]= 1.0\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + theta(time[i]) * dt + sigma* (W[:,i+1]-W[:,i])\r\n        M[:,i+1] = M[:,i] * np.exp((R[:,i+1]+R[:,i])*0.5*dt)\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R,\"M\":M}\r\n    return paths\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 25000\r\n    NoOfSteps = 500\r\n       \r\n    sigma = 0.007\r\n        \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.1*T)\r\n       \r\n    # In this experiment we compare ZCB from the Market and Monte Carlo\r\n    \"Monte Carlo part\"   \r\n    T = 40\r\n    paths= GeneratePathsHoLeeEuler(NoOfPaths,NoOfSteps,T,P0T,sigma)\r\n    M = paths[\"M\"]\r\n    ti = paths[\"time\"]\r\n        \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    \r\n    P_t = np.zeros([NoOfSteps+1])\r\n    for i in range(0,NoOfSteps+1):\r\n        P_t[i] = np.mean(1.0/M[:,i])\r\n   \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.xlabel('T')\r\n    plt.ylabel('P(0,T)')\r\n    plt.plot(ti,P0T(ti))\r\n    plt.plot(ti,P_t,'--r')\r\n    plt.legend(['P(0,t) market','P(0,t) Monte Carlo'])\r\n    plt.title('ZCBs from Ho-Lee Model')\r\n    \r\nmainCalculation()\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "Lecture 03-The HJM Framework/Materials/Hull-White-Paths.py",
    "content": "#%%\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 comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) + \\\r\n    eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      \r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 1\r\n    NoOfSteps = 5000\r\n    T         = 50.0\r\n    lambd     = 0.5\r\n    eta       = 0.01\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.05*T) \r\n\r\n    # Effect of mean reversion lambda\r\n    plt.figure(1) \r\n    legend = []\r\n    lambdVec = [-0.01, 0.2, 5.0]\r\n    for lambdTemp in lambdVec:    \r\n        np.random.seed(2)\r\n        Paths = GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambdTemp, eta)\r\n        legend.append('lambda={0}'.format(lambdTemp))\r\n        timeGrid = Paths[\"time\"]\r\n        R = Paths[\"R\"]\r\n        plt.plot(timeGrid, np.transpose(R))   \r\n    plt.grid()\r\n    plt.xlabel(\"time\")\r\n    plt.ylabel(\"R(t)\")\r\n    plt.legend(legend)\r\n        \r\n    # Effect of the volatility\r\n    plt.figure(2)    \r\n    legend = []\r\n    etaVec = [0.1, 0.2, 0.3]\r\n    for etaTemp in etaVec:\r\n        np.random.seed(2)\r\n        Paths = GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, etaTemp)\r\n        legend.append('eta={0}'.format(etaTemp))\r\n        timeGrid = Paths[\"time\"]\r\n        R = Paths[\"R\"]\r\n        plt.plot(timeGrid, np.transpose(R))   \r\n    plt.grid()\r\n    plt.xlabel(\"time\")\r\n    plt.ylabel(\"R(t)\")\r\n    plt.legend(legend)\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 03-The HJM Framework/Materials/Hull-White-ZCBs.py",
    "content": "#%%\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 comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\n\r\ndef f0T(t,P0T):\r\n    # time-step needed for differentiation\r\n    dt = 0.01    \r\n    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    return expr\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.01,P0T)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M[:,0]= 1.0\r\n    R[:,0]= r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        M[:,i+1] = M[:,i] * np.exp((R[:,i+1]+R[:,i])*0.5*dt)\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R,\"M\":M}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.01    \r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 25000\r\n    NoOfSteps = 25\r\n       \r\n    lambd = 0.02\r\n    eta   = 0.02\r\n\r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.1*T)\r\n       \r\n    # In this experiment we compare ZCB from the Market and Monte Carlo\r\n    \"Monte Carlo part\"   \r\n    T = 40\r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta)\r\n    M = paths[\"M\"]\r\n    ti = paths[\"time\"]\r\n    #dt = timeGrid[1]-timeGrid[0]\r\n    \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo the Market  \r\n    P_tMC = np.zeros([NoOfSteps+1])\r\n    for i in range(0,NoOfSteps+1):\r\n        P_tMC[i] = np.mean(1.0/M[:,i])\r\n  \r\n\r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.xlabel('T')\r\n    plt.ylabel('P(0,T)')\r\n    plt.plot(ti,P0T(ti))\r\n    plt.plot(ti,P_tMC,'--r')\r\n    plt.legend(['P(0,t) market','P(0,t) Monte Carlo'])\r\n    plt.title('ZCBs from Hull-White Model')\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull-White-CompRateSim.py",
    "content": "#%%\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 educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\n#from scipy.interpolate import interp1d\r\nfrom scipy import interpolate\r\n\r\ndef f0T(t,P0T):\r\n    # time-step needed for differentiation\r\n    dt = 0.01    \r\n    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    return expr\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.01,P0T)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.01    \r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW2F_ZCB(lambd1,lambd2,eta1,eta2,rho,P0T,T1,T2,xT1,yT1):\r\n    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))+\\\r\n                   +(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))+\\\r\n            + 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))\r\n            \r\n    intPhi = - np.log(P0T(T2)/P0T(T1)*np.exp(-0.5*(V(0,T2)-V(0,T1))))        \r\n    \r\n    A = 1.0/lambd1 * (1.0-np.exp(-lambd1 * (T2-T1)))\r\n    B = 1.0/lambd2 * (1.0-np.exp(-lambd2 * (T2-T1)))\r\n    \r\n    return np.exp(- intPhi -A * xT1 - B * yT1 + 0.5 * V(T1,T2))\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    return np.exp(A_r + B_r *rT1)\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    r0 = f0T(0.001,P0T)\r\n    return r0\r\n\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 20000\r\n    NoOfSteps = 100\r\n       \r\n    #HW1F\r\n    lambd     = 0.01\r\n    eta       = 0.002\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    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]\r\n    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]\r\n    interpolator =  interpolate.splrep(ti, np.log(Pi), s=0.00001)\r\n    P0T = lambda T: np.exp(interpolate.splev(T, interpolator, der=0))\r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 20\r\n    T_end0 = 49.0\r\n    Tgrid= np.linspace(0.1,T_end0,N)\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy_1FHW= np.zeros ([N,1])\r\n    Yield = np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy_1FHW[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Exact[i] = P0T(Ti)\r\n        Yield[i] = -np.log(Proxy_1FHW[i])/Ti\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.xlabel('T')\r\n    plt.xlabel('ZCB, P(0,t)')\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy_1FHW,'--r')\r\n    plt.legend([\"Analytcal ZCB\",\"ZCB- 1F Model\",\"ZCB- 2F Model\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n    \r\n     # Yield \r\n    plt.figure(2)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Yield,'-k')\r\n    plt.xlabel('T')\r\n    plt.ylabel('yield, r(0,T)')\r\n    \r\n    \"Monte Carlo part\"   \r\n    T_end = 10.0\r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T_end ,P0T, lambd, eta)\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n    \r\n    # Yield Curves on the last point\r\n    plt.figure(3)\r\n    plt.xlabel('time')\r\n    plt.ylabel('r(t)')\r\n    plt.title('MC Paths + Yield Curve (Hull-White)')\r\n    plt.grid()\r\n    T_end2 = T_end + 40.0\r\n    Tgrid2= np.linspace(T_end+0.001,T_end2-0.01,N)\r\n    ZCB = np.zeros([N,1])\r\n    r_T = r[:,-1]\r\n    for i in range(0,20):\r\n        for j,Tj in enumerate(Tgrid2):\r\n            ZCB[j] = HW_ZCB(lambd,eta,P0T,T_end,Tj,r_T[i])\r\n            Yield[j] = -np.log(ZCB[j])/(Tj-T_end)\r\n        plt.plot(Tgrid2, Yield)\r\n        plt.plot(timeGrid,r[i,:])\r\n\r\nmainCalculation()"
  },
  {
    "path": "Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull-White-ZCBs2.py",
    "content": "#%%\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 is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\n\r\ndef f0T(t,P0T):\r\n    # time-step needed for differentiation\r\n    dt = 0.01    \r\n    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    return expr\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.01,P0T)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M[:,0]= 1.0\r\n    R[:,0]= r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        M[:,i+1] = M[:,i] * np.exp((R[:,i+1]+R[:,i])*0.5*dt)\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R,\"M\":M}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.01    \r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n\r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)  \r\n    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)\r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    return np.exp(A_r + B_r *rT1)\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 20000\r\n    NoOfSteps = 25\r\n       \r\n    lambd = 0.04\r\n    eta   = 0.01\r\n\r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.1*T)\r\n    \r\n    # Initial r_0\r\n    r0 = f0T(0.01,P0T)    \r\n       \r\n    # In this experiment we compare ZCB from the Market and Monte Carlo\r\n    \"Monte Carlo part\"   \r\n    T = 40\r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta)\r\n    M = paths[\"M\"]\r\n    ti = paths[\"time\"]\r\n    #dt = timeGrid[1]-timeGrid[0]\r\n    \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo the Market  \r\n    P_tMC = np.zeros([NoOfSteps+1])\r\n    for i in range(0,NoOfSteps+1):\r\n        P_tMC[i] = np.mean(1.0/M[:,i])\r\n   \r\n    # Analytical expression for ZCB using the Affine properties of the HW model\r\n    P_tHW = np.zeros([NoOfSteps+1])\r\n    for i in range(0,NoOfSteps+1):\r\n        P_tHW[i] = HW_ZCB(lambd,eta,P0T,0.0,ti[i],r0)\r\n   \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.xlabel('T')\r\n    plt.ylabel('P(0,T)')\r\n    plt.plot(ti,P0T(ti))\r\n    plt.plot(ti,P_tMC,'--r')\r\n    plt.plot(ti,P_tHW,'.k')\r\n    plt.legend(['P(0,t) market','P(0,t) Monte Carlo','P(0,t) from Affine form'])\r\n    plt.title('ZCBs from Hull-White Model')\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 04-Yield Curve Dynamics under Short Rate/Materials/Hull_White_1F_2F_Comparison.py",
    "content": "#%%\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 purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\nfrom scipy import interpolate\r\n\r\ndef GeneratePathsHW2FEuler(NoOfPaths,NoOfSteps,T,P0T, lambd1,lambd2, eta1,eta2,rho):    \r\n    # time-step needed for differentiation\r\n    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))+\\\r\n                                +(eta2**2.0)/(2.0*lambd2**2.0)*(1.0-np.exp(-lambd2*t))*(1.0-np.exp(-lambd2*t))+\\\r\n                           + rho*eta1*eta2/(lambd1*lambd2)*(1.0-np.exp(-lambd1*t))*(1.0-np.exp(-lambd2*t))\r\n    \r\n    Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    Z2 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W1 = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    W2 = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    X = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    Y = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0] = phi(0)\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i])\r\n            Z2[:,i] = (Z2[:,i] - np.mean(Z2[:,i])) / np.std(Z2[:,i])\r\n            Z2[:,i] = rho * Z1[:,i] + np.sqrt(1.0-rho**2)*Z2[:,i]\r\n            \r\n        W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i]\r\n        W2[:,i+1] = W2[:,i] + np.power(dt, 0.5)*Z2[:,i]\r\n        \r\n        X[:,i+1] = X[:,i] - lambd1*X[:,i] * dt + eta1* (W1[:,i+1]-W1[:,i])\r\n        Y[:,i+1] = Y[:,i] - lambd2*Y[:,i] * dt + eta2* (W2[:,i+1]-W2[:,i])\r\n        time[i+1] = time[i] +dt\r\n\r\n        R[:,i+1] = X[:,i+1] + Y[:,i+1] + phi(time[i+1])\r\n    \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R,\"X\":X,\"Y\":Y}\r\n    return paths\r\n\r\ndef f0T(t,P0T):\r\n    # time-step needed for differentiation\r\n    dt = 0.01    \r\n    expr = - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    return expr\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.01,P0T)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.01    \r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW2F_ZCB(lambd1,lambd2,eta1,eta2,rho,P0T,T1,T2,xT1,yT1):\r\n    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))+\\\r\n                   +(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))+\\\r\n            + 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))\r\n            \r\n    intPhi = - np.log(P0T(T2)/P0T(T1)*np.exp(-0.5*(V(0,T2)-V(0,T1))))        \r\n    \r\n    A = 1.0/lambd1 * (1.0-np.exp(-lambd1 * (T2-T1)))\r\n    B = 1.0/lambd2 * (1.0-np.exp(-lambd2 * (T2-T1)))\r\n    \r\n    return np.exp(- intPhi -A * xT1 - B * yT1 + 0.5 * V(T1,T2))\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    return np.exp(A_r + B_r *rT1)\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    r0 = f0T(0.001,P0T)\r\n    return r0\r\n\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 2000\r\n    NoOfSteps = 100\r\n       \r\n    #HW1F\r\n    lambd     = 0.01\r\n    eta       = 0.002\r\n    \r\n    #HW2F \r\n    lambd2 = 0.1\r\n    eta2   = 0.002\r\n    rho    = -0.2\r\n    \r\n    # We define a ZCB curve (obtained from the market), 1-D points with a B-spline curve.\r\n    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]\r\n    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]\r\n    interpolator =  interpolate.splrep(ti, Pi, s=0.0001)\r\n    P0T = lambda T: interpolate.splev(T, interpolator, der=0)\r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 20\r\n    T_end0 = 39.0\r\n    Tgrid= np.linspace(0.1,T_end0,N)\r\n\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy_1FHW= np.zeros ([N,1])\r\n    Proxy_2FHW= np.zeros ([N,1])\r\n    Yield = np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy_1FHW[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Proxy_2FHW[i] = HW2F_ZCB(lambd,lambd2,eta,eta2,rho,P0T,0.0,Ti,0.0,0.0)\r\n        Exact[i] = P0T(Ti)\r\n        Yield[i] = -np.log(Proxy_1FHW[i])/Ti\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy_1FHW,'--r')\r\n    plt.plot(Tgrid,Proxy_2FHW,'.k')\r\n    plt.legend([\"Analytcal ZCB\",\"ZCB- 1F Model\",\"ZCB- 2F Model\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n     \r\n    \"Monte Carlo part\"   \r\n    T_end = 10.0\r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T_end ,P0T, lambd, eta)\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n        \r\n    # Yield \r\n    #r = -np.log(Proxy)/T\r\n    plt.figure(2)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Yield,'-k')\r\n    \r\n    # Yield Curves on the last point\r\n    plt.figure(3)\r\n    plt.xlabel('time')\r\n    plt.ylabel('r(t)')\r\n    plt.title('MC Paths + Yield Curve (Hull-White)')\r\n    plt.grid()\r\n    T_end2 = T_end + 40.0\r\n    Tgrid2= np.linspace(T_end+0.001,T_end2-0.01,N)\r\n    ZCB = np.zeros([N,1])\r\n    r_T = r[:,-1]\r\n    for i in range(0,20):\r\n        for j,Tj in enumerate(Tgrid2):\r\n            ZCB[j] = HW_ZCB(lambd,eta,P0T,T_end,Tj,r_T[i])\r\n            Yield[j] = -np.log(ZCB[j])/(Tj-T_end)\r\n        plt.plot(Tgrid2, Yield)\r\n        plt.plot(timeGrid,r[i,:])\r\n        \r\n    # Analysis for the 2F model\r\n    paths= GeneratePathsHW2FEuler(NoOfPaths,NoOfSteps,T_end,P0T, lambd,lambd2, eta,eta2,rho)\r\n    x = paths[\"X\"]\r\n    y = paths[\"Y\"]\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n       \r\n     # Yield Curves on the last point\r\n    plt.figure(4)\r\n    plt.xlabel('time')\r\n    plt.ylabel('r(t)')\r\n    plt.title('MC Paths + Yield Curve (Hull-White 2F)')\r\n    plt.grid()\r\n    x_T = x[:,-1]\r\n    y_T = y[:,-1]\r\n    r_T = r[:,-1]\r\n    Tgrid2= np.linspace(T_end+0.001,T_end2,N)\r\n    ZCB = np.zeros([N,1])\r\n    for i in range(0,20):\r\n        for j,Tj in enumerate(Tgrid2):\r\n            ZCB[j] = HW2F_ZCB(lambd,lambd2,eta,eta2,rho,P0T,T_end,Tj,x_T[i],y_T[i])\r\n            #HW_ZCB(lambd,eta,P0T,T_end,Tj,r_T[i])\r\n            Yield[j] = -np.log(ZCB[j])/(Tj-T_end)\r\n        plt.plot(timeGrid,r[i,:])\r\n        plt.plot(Tgrid2, Yield)\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 05-Interest Rate Products/Materials/HW_Caplets.py",
    "content": "#%%\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 \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport enum \r\nimport matplotlib.pyplot as plt\r\nimport scipy.stats as st\r\nimport scipy.integrate as integrate\r\n\r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    return np.exp(A_r + B_r *rT1)\r\n\r\ndef HWMean_r(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,2500)\r\n    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))\r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    return r_mean\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    return r0\r\n\r\ndef HW_Mu_FrwdMeasure(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,500)\r\n    \r\n    theta_hat =lambda t,T:  theta(t) + eta*eta / lambd *1.0/lambd * (np.exp(-lambd*(T-t))-1.0)\r\n    \r\n    temp =lambda z: theta_hat(z,T) * np.exp(-lambd*(T-z))\r\n    \r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    \r\n    return r_mean\r\n\r\ndef HWVar_r(lambd,eta,T):\r\n    return eta*eta/(2.0*lambd) *( 1.0-np.exp(-2.0*lambd *T))\r\n\r\ndef HWDensity(P0T,lambd,eta,T):\r\n    r_mean = HWMean_r(P0T,lambd,eta,T)\r\n    r_var = HWVar_r(lambd,eta,T)\r\n    return lambda x: st.norm.pdf(x,r_mean,np.sqrt(r_var))\r\n\r\ndef HW_CapletFloorletPrice(CP,N,K,lambd,eta,P0T,T1,T2):\r\n    if CP == OptionType.CALL:\r\n        N_new = N * (1.0+(T2-T1)*K)\r\n        K_new = 1.0 + (T2-T1)*K\r\n        caplet = N_new*HW_ZCB_CallPutPrice(OptionType.PUT,1.0/K_new,lambd,eta,P0T,T1,T2)\r\n        value= caplet\r\n    elif CP==OptionType.PUT:\r\n        value = 0.0 # In the homework assignmnet you need to fill this part in\r\n    return value\r\n    \r\ndef HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    \r\n    mu_r = HW_Mu_FrwdMeasure(P0T,lambd,eta,T1)\r\n    v_r =  np.sqrt(HWVar_r(lambd,eta,T1))\r\n    \r\n    K_hat = K * np.exp(-A_r)\r\n    \r\n    a = (np.log(K_hat) - B_r*mu_r)/(B_r*v_r)\r\n    \r\n    d1 = a - B_r*v_r\r\n    d2 = d1 +  B_r*v_r\r\n    \r\n    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)    \r\n    value =P0T(T1) * np.exp(A_r) * term1 \r\n    \r\n    if CP == OptionType.CALL:\r\n        return value\r\n    elif CP==OptionType.PUT:\r\n        return value - P0T(T2) + K*P0T(T1)\r\n\r\n\r\ndef mainCalculation():\r\n    CP= OptionType.CALL\r\n    NoOfPaths = 20000\r\n    NoOfSteps = 1000\r\n        \r\n    lambd     = 0.02\r\n\r\n    eta       = 0.02\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.1*T)#np.exp(-0.03*T*T-0.1*T)\r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 25\r\n    T_end = 50\r\n    Tgrid= np.linspace(0,T_end,N)\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy= np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Exact[i] = P0T(Ti)\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy,'--r')\r\n    plt.legend([\"Analytcal ZCB\",\"Monte Carlo ZCB\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n\r\n    # In this experiment we compare Monte Carlo results for \r\n    T1 = 4.0\r\n    T2 = 8.0\r\n    \r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T1 ,P0T, lambd, eta)\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n    dt = timeGrid[1]-timeGrid[0]\r\n    \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    \r\n    M_t = np.zeros([NoOfPaths,NoOfSteps])\r\n    for i in range(0,NoOfPaths):\r\n        M_t[i,:] = np.exp(np.cumsum(r[i,:-1])*dt)\r\n        \r\n    KVec = np.linspace(0.01,1.7,50)\r\n    Price_MC_V = np.zeros([len(KVec),1])\r\n    Price_Th_V =np.zeros([len(KVec),1])\r\n    P_T1_T2 = HW_ZCB(lambd,eta,P0T,T1,T2,r[:,-1])\r\n    for i,K in enumerate(KVec):\r\n        if CP==OptionType.CALL:\r\n            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(P_T1_T2-K,0.0)) \r\n        elif CP==OptionType.PUT:\r\n            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(K-P_T1_T2,0.0)) \r\n        Price_Th_V[i] =HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2)#HW_ZCB_CallPrice(K,lambd,eta,P0T,T1,T2)\r\n        \r\n    plt.figure(2)\r\n    plt.grid()\r\n    plt.plot(KVec,Price_MC_V)\r\n    plt.plot(KVec,Price_Th_V,'--r')\r\n    plt.legend(['Monte Carlo','Theoretical'])\r\n    plt.title('Option on ZCB')\r\n\r\n    # Effect of the HW model parameters on Implied Volatilities\r\n    # define a forward rate between T1 and T2\r\n    frwd = 1.0/(T2-T1) *(P0T(T1)/P0T(T2)-1.0)\r\n    K = np.linspace(frwd/2.0,3.0*frwd,25)\r\n    Notional = 1.0\r\n    \r\n    capletPrice = np.zeros(len(K))\r\n    for idx in range(0,len(K)):\r\n           capletPrice[idx] = HW_CapletFloorletPrice(CP,Notional,K[idx],lambd,eta,P0T,T1,T2)\r\n           \r\n    plt.figure(3)\r\n    plt.title('Caplet Price')\r\n    plt.plot(K,capletPrice)\r\n    plt.xlabel('strike')\r\n    plt.ylabel('Caplet Price')\r\n    plt.grid()\r\n    \r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 05-Interest Rate Products/Materials/HW_OptionsOnZCBs.py",
    "content": "#%%\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 comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport enum \r\nimport matplotlib.pyplot as plt\r\nimport scipy.stats as st\r\nimport scipy.integrate as integrate\r\nfrom scipy import interpolate\r\n\r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    return np.exp(A_r + B_r *rT1)\r\n\r\ndef HWMean_r(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,2500)\r\n    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))\r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    return r_mean\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    return r0\r\n\r\ndef HW_Mu_FrwdMeasure(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,500)\r\n    \r\n    theta_hat =lambda t,T:  theta(t) + eta*eta / lambd *1.0/lambd * (np.exp(-lambd*(T-t))-1.0)\r\n    \r\n    temp =lambda z: theta_hat(z,T) * np.exp(-lambd*(T-z))\r\n    \r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    \r\n    return r_mean\r\n\r\ndef HWVar_r(lambd,eta,T):\r\n    return eta*eta/(2.0*lambd) *( 1.0-np.exp(-2.0*lambd *T))\r\n\r\ndef HWDensity(P0T,lambd,eta,T):\r\n    r_mean = HWMean_r(P0T,lambd,eta,T)\r\n    r_var = HWVar_r(lambd,eta,T)\r\n    return lambda x: st.norm.pdf(x,r_mean,np.sqrt(r_var))\r\n\r\ndef HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    \r\n    mu_r = HW_Mu_FrwdMeasure(P0T,lambd,eta,T1)\r\n    v_r =  np.sqrt(HWVar_r(lambd,eta,T1))\r\n    \r\n    K_hat = K * np.exp(-A_r)\r\n    \r\n    a = (np.log(K_hat) - B_r*mu_r)/(B_r*v_r)\r\n    \r\n    d1 = a - B_r*v_r\r\n    d2 = d1 +  B_r*v_r\r\n    \r\n    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)    \r\n    value =P0T(T1) * np.exp(A_r) * term1 \r\n    \r\n    if CP == OptionType.CALL:\r\n        return value\r\n    elif CP==OptionType.PUT:\r\n        return value - P0T(T2) + K*P0T(T1)\r\n\r\ndef mainCalculation():\r\n    CP= OptionType.CALL\r\n    NoOfPaths = 20000\r\n    NoOfSteps = 1000\r\n        \r\n    lambd     = 0.02\r\n    eta       = 0.02\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    # We define a ZCB curve (obtained from the market), 1-D points with a B-spline curve.\r\n    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]\r\n    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]\r\n    interpolator =  interpolate.splrep(ti, Pi, s=0.0001)\r\n    P0T = lambda T: interpolate.splev(T, interpolator, der=0)\r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 25\r\n    T_end = 50\r\n    Tgrid= np.linspace(0,T_end,N)\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy= np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Exact[i] = P0T(Ti)\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy,'--r')\r\n    plt.legend([\"Analytcal ZCB\",\"Monte Carlo ZCB\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n\r\n    # In this experiment we compare Monte Carlo results for \r\n    T1 = 4.0\r\n    T2 = 8.0\r\n    \r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T1 ,P0T, lambd, eta)\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n    dt = timeGrid[1]-timeGrid[0]\r\n    \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    \r\n    M_t = np.zeros([NoOfPaths,NoOfSteps])\r\n    for i in range(0,NoOfPaths):\r\n        M_t[i,:] = np.exp(np.cumsum(r[i,:-1])*dt)\r\n        \r\n    KVec = np.linspace(0.01,1.7,50)\r\n    Price_MC_V = np.zeros([len(KVec),1])\r\n    Price_Th_V =np.zeros([len(KVec),1])\r\n    P_T1_T2 = HW_ZCB(lambd,eta,P0T,T1,T2,r[:,-1])\r\n    for i,K in enumerate(KVec):\r\n        if CP==OptionType.CALL:\r\n            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(P_T1_T2-K,0.0)) \r\n        elif CP==OptionType.PUT:\r\n            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(K-P_T1_T2,0.0)) \r\n        Price_Th_V[i] =HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2)\r\n        \r\n    plt.figure(2)\r\n    plt.grid()\r\n    plt.plot(KVec,Price_MC_V)\r\n    plt.plot(KVec,Price_Th_V,'--r')\r\n    plt.legend(['Monte Carlo','Theoretical'])\r\n    plt.title('Call Option on ZCB')\r\n    plt.xlabel('Strike')\r\n    plt.ylabel('Option value')\r\n\r\nmainCalculation()"
  },
  {
    "path": "Lecture 05-Interest Rate Products/Materials/Swaps_HW.py",
    "content": "#%%\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 purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport enum \r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\nfrom scipy import interpolate\r\nfrom scipy.optimize import newton\r\n\r\n# This class defines puts and calls\r\nclass OptionTypeSwap(enum.Enum):\r\n    RECEIVER = 1.0\r\n    PAYER = -1.0\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    n = np.size(rT1) \r\n        \r\n    if T1<T2:\r\n        B_r = HW_B(lambd,eta,T1,T2)\r\n        A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n        return np.exp(A_r + B_r *rT1)\r\n    else:\r\n        return np.ones([n])\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    return r0\r\n\r\ndef SwapPrice(CP,notional,K,t,Ti,Tm,n,P0T):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n\r\n    if n == 1:\r\n        ti_grid =np.array([Ti,Tm])\r\n    else:\r\n        ti_grid = np.linspace(Ti,Tm,n)\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    # overwrite Ti if t>Ti\r\n    prevTi = ti_grid[np.where(ti_grid<t)]\r\n    if np.size(prevTi) > 0: #prevTi != []:\r\n        Ti = prevTi[-1]\r\n    \r\n    # Now we need to handle the case when some payments are already done\r\n    ti_grid = ti_grid[np.where(ti_grid>t)]          \r\n\r\n    temp= 0.0\r\n    \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if ti>Ti:\r\n            temp = temp + tau * P0T(ti)\r\n\r\n    if CP==OptionTypeSwap.PAYER:\r\n        swap = (P0T(Ti) - P0T(Tm)) - K * temp\r\n    elif CP==OptionTypeSwap.RECEIVER:\r\n        swap = K * temp- (P0T(Ti) - P0T(Tm))\r\n    \r\n    return swap*notional\r\n\r\ndef HW_SwapPrice(CP,notional,K,t,Ti,Tm,n,r_t,P0T,lambd,eta):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n\r\n    if n == 1:\r\n        ti_grid =np.array([Ti,Tm])\r\n    else:\r\n        ti_grid = np.linspace(Ti,Tm,n)\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    # overwrite Ti if t>Ti\r\n    prevTi = ti_grid[np.where(ti_grid<t)]\r\n    if np.size(prevTi) > 0: #prevTi != []:\r\n        Ti = prevTi[-1]\r\n    \r\n    # Now we need to handle the case when some payments are already done\r\n    ti_grid = ti_grid[np.where(ti_grid>t)]          \r\n\r\n    temp= np.zeros(np.size(r_t));\r\n    \r\n    P_t_TiLambda = lambda Ti : HW_ZCB(lambd,eta,P0T,t,Ti,r_t)\r\n    \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if ti>Ti:\r\n            temp = temp + tau * P_t_TiLambda(ti)\r\n            \r\n    P_t_Ti = P_t_TiLambda(Ti)\r\n    P_t_Tm = P_t_TiLambda(Tm)\r\n    \r\n    if CP==OptionTypeSwap.PAYER:\r\n        swap = (P_t_Ti - P_t_Tm) - K * temp\r\n    elif CP==OptionTypeSwap.RECEIVER:\r\n        swap = K * temp- (P_t_Ti - P_t_Tm)\r\n    \r\n    return swap*notional\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 2000\r\n    NoOfSteps = 1000\r\n    CP = OptionTypeSwap.PAYER\r\n    lambd     = 0.5\r\n    eta       = 0.03\r\n    notional  = 10000.0 \r\n    \r\n    # We define a ZCB curve (obtained from the market), 1-D points with a B-spline curve.\r\n    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]\r\n    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]\r\n    interpolator =  interpolate.splrep(ti, Pi, s=0.0001)\r\n    P0T = lambda T: interpolate.splev(T, interpolator, der=0)\r\n    \r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 25\r\n    T_end = 50\r\n    Tgrid= np.linspace(0,T_end,N)\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy= np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Exact[i] = P0T(Ti)\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy,'--r')\r\n    plt.legend([\"Analytcal ZCB\",\"Monte Carlo ZCB\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n    \r\n    \r\n    # Here we simulate the exposure profiles for a swap, using the HW model    \r\n    # Swap settings\r\n    K = np.linspace(-0.1,0.1,25)  # strike\r\n    Ti = 1.0  # begining of the swap\r\n    Tm = 10.0 # end date of the swap \r\n    n = 10    # number of payments between Ti and Tm\r\n    \r\n\r\n    paths= GeneratePathsHWEuler(NoOfPaths, NoOfSteps,Tm + 1.0, P0T, lambd, eta)\r\n\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n    dt = timeGrid[1]-timeGrid[0]\r\n    \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    \r\n    M_t = np.zeros([NoOfPaths,NoOfSteps])\r\n            \r\n    for i in range(0,NoOfPaths):\r\n        M_t[i,:] = np.exp(np.cumsum(r[i,0:-1])*dt)\r\n        \r\n    # Portfolio without netting    \r\n    VSwapHW = np.zeros(len(K))\r\n    VSwap = np.zeros(len(K))\r\n    t0= 0\r\n    for (idx,Ki) in enumerate(K):\r\n        VHW = HW_SwapPrice(CP,notional,Ki,t0,Ti,Tm,n,r0,P0T,lambd,eta)\r\n        V = SwapPrice(CP,notional,Ki,t0,Ti,Tm,n,P0T) \r\n        VSwap[idx] = V\r\n        VSwapHW[idx] = VHW[0]\r\n    \r\n    plt.figure(2)\r\n    plt.plot(K,VSwap)\r\n    plt.plot(K,VSwapHW,'--r')\r\n    plt.grid()\r\n    plt.xlabel('strike,K')\r\n    plt.ylabel('Swap value')\r\n    plt.title('Swap pricing')\r\n    \r\n    # Computation for Swap Par\r\n    K=0.0\r\n    print('Swap price for K = 0 is {0}'.format(SwapPrice(CP,notional,K,t0,Ti,Tm,n,P0T)))\r\n    \r\n    # Determine a part swap\r\n    func = lambda k: SwapPrice(CP,notional,k,t0,Ti,Tm,n,P0T)\r\n    K_par = newton(func, 0.0)\r\n    print('Swap price for K_par = {0} is {1}'.format(K_par,SwapPrice(CP,notional,K_par,t0,Ti,Tm,n,P0T)))\r\n    \r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/MultiCurveBuild.py",
    "content": "#%%\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 purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport enum \r\nfrom copy import deepcopy\r\nimport matplotlib.pyplot as plt\r\nfrom scipy.interpolate import splrep, splev, interp1d\r\n\r\n# This class defines puts and calls\r\nclass OptionTypeSwap(enum.Enum):\r\n    RECEIVER = 1.0\r\n    PAYER = -1.0\r\n    \r\ndef IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n    ti_grid = np.linspace(Ti,Tm,int(n))\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    temp= 0.0\r\n        \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if idx>0:\r\n            temp = temp + tau * P0T(ti)\r\n            \r\n    P_t_Ti = P0T(Ti)\r\n    P_t_Tm = P0T(Tm)\r\n    \r\n    if CP==OptionTypeSwap.PAYER:\r\n        swap = (P_t_Ti - P_t_Tm) - K * temp\r\n    elif CP==OptionTypeSwap.RECEIVER:\r\n        swap = K * temp - (P_t_Ti - P_t_Tm)\r\n    \r\n    return swap * notional\r\n\r\n\r\ndef IRSwapMultiCurve(CP,notional,K,t,Ti,Tm,n,P0T,P0TFrd):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n    ti_grid = np.linspace(Ti,Tm,int(n))\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    swap = 0.0\r\n        \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        #L(t_0,t_{k-1},t_{k}) from the forward curve\r\n        if idx>0:\r\n            L_frwd = 1.0/tau * (P0TFrd(ti_grid[idx-1])-P0TFrd(ti_grid[idx])) / P0TFrd(ti_grid[idx])\r\n            swap = swap + tau * P0T(ti_grid[idx]) * (L_frwd - K)\r\n            \r\n    return swap * notional\r\n\r\n\r\ndef P0TModel(t,ti,ri,method):\r\n    rInterp = method(ti,ri)\r\n    r = rInterp(t)\r\n    return np.exp(-r*t)\r\n\r\ndef YieldCurve(instruments, maturities, r0, method, tol):\r\n    r0 = deepcopy(r0)\r\n    ri = MultivariateNewtonRaphson(r0, maturities, instruments, method, tol=tol)\r\n    return ri\r\n\r\ndef MultivariateNewtonRaphson(ri, ti, instruments, method, tol):\r\n    err = 10e10\r\n    idx = 0\r\n    while err > tol:\r\n        idx = idx +1\r\n        values = EvaluateInstruments(ti,ri,instruments,method)\r\n        J = Jacobian(ti,ri, instruments, method)\r\n        J_inv = np.linalg.inv(J)\r\n        err = - np.dot(J_inv, values)\r\n        ri[0:] = ri[0:] + err \r\n        err = np.linalg.norm(err)\r\n        print('index in the loop is',idx,' Error is ', err)\r\n    return ri\r\n\r\ndef Jacobian(ti, ri, instruments, method):\r\n    eps = 1e-05\r\n    swap_num = len(ti)\r\n    J = np.zeros([swap_num, swap_num])\r\n    val = EvaluateInstruments(ti,ri,instruments,method)\r\n    ri_up = deepcopy(ri)\r\n    \r\n    for j in range(0, len(ri)):\r\n        ri_up[j] = ri[j] + eps  \r\n        val_up = EvaluateInstruments(ti,ri_up,instruments,method)\r\n        ri_up[j] = ri[j]\r\n        dv = (val_up - val) / eps\r\n        J[:, j] = dv[:]\r\n    return J\r\n\r\ndef EvaluateInstruments(ti,ri,instruments,method):\r\n    P0Ttemp = lambda t: P0TModel(t,ti,ri,method)\r\n    val = np.zeros(len(instruments))\r\n    for i in range(0,len(instruments)):\r\n        val[i] = instruments[i](P0Ttemp)\r\n    return val\r\n\r\ndef linear_interpolation(ti,ri):\r\n    interpolator = lambda t: np.interp(t, ti, ri)\r\n    return interpolator\r\n\r\ndef spline_interpolate(ti,ri):\r\n    interpolator = splrep(ti, ri, s=0.01)\r\n    interp = lambda t: splev(t,interpolator)\r\n    return interp\r\n\r\ndef scipy_1d_interpolate(ti, ri):\r\n    interpolator = lambda t: interp1d(ti, ri, kind='quadratic')(t)\r\n    return interpolator\r\n\r\n#def ComputeDelta(instrument,)\r\n\r\ndef mainCode():\r\n    \r\n    # Convergence tolerance\r\n    tol = 1.0e-15\r\n    # Initial guess for the spine points\r\n    r0 = np.array([0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01])   \r\n    # Interpolation method\r\n    method = linear_interpolation\r\n    \r\n    # Construct swaps that are used for building of the yield curve\r\n    K   = np.array([0.04/100.0,\t0.16/100.0,\t0.31/100.0,\t0.81/100.0,\t1.28/100.0,\t1.62/100.0,\t2.22/100.0,\t2.30/100.0])\r\n    mat = np.array([1.0,2.0,3.0,5.0,7.0,10.0,20.0,30.0])\r\n    \r\n    #                   IRSwap(CP,           notional,K,   t,   Ti,Tm,   n,P0T)\r\n    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[0],0.0,0.0,mat[0],4*mat[0],P0T)\r\n    swap2 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[1],0.0,0.0,mat[1],5*mat[1],P0T)\r\n    swap3 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[2],0.0,0.0,mat[2],6*mat[2],P0T)\r\n    swap4 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[3],0.0,0.0,mat[3],7*mat[3],P0T)\r\n    swap5 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[4],0.0,0.0,mat[4],8*mat[4],P0T)\r\n    swap6 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[5],0.0,0.0,mat[5],9*mat[5],P0T)\r\n    swap7 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[6],0.0,0.0,mat[6],10*mat[6],P0T)\r\n    swap8 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[7],0.0,0.0,mat[7],11*mat[7],P0T)\r\n    instruments = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8]\r\n    \r\n    # determine optimal spine points\r\n    ri = YieldCurve(instruments, mat, r0, method, tol)\r\n    print('\\n Spine points are',ri,'\\n')\r\n    \r\n    # Build a ZCB-curve/yield curve from the spine points\r\n    P0T_Initial = lambda t: P0TModel(t,mat,r0,method)\r\n    P0T = lambda t: P0TModel(t,mat,ri,method)\r\n    \r\n    # price back the swaps\r\n    swapsModel = np.zeros(len(instruments))\r\n    swapsInitial = np.zeros(len(instruments))\r\n    for i in range(0,len(instruments)):\r\n        swapsModel[i] = instruments[i](P0T)\r\n        swapsInitial[i] = instruments[i](P0T_Initial)\r\n    \r\n    print('Prices for Pas Swaps (initial) = ',swapsInitial,'\\n')\r\n    print('Prices for Par Swaps = ',swapsModel,'\\n')\r\n    \r\n    \r\n    # Multi Curve extension- simple sanity check\r\n    P0TFrd = deepcopy(P0T)\r\n    Ktest = 0.2\r\n    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,Ktest,0.0,0.0,mat[0],4*mat[0],P0T)\r\n    swap1MC = lambda P0T: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,Ktest,0.0,0.0,mat[0],4*mat[0],P0T,P0TFrd)\r\n    print('Sanity check: swap1 = {0}, swap2 = {1}'.format(swap1(P0T),swap1MC(P0T)))\r\n    \r\n    # Forward curve settings\r\n    r0Frwd = np.array([0.01, 0.01, 0.01, 0.01])   \r\n    KFrwd   = np.array([0.09/100.0,\t0.26/100.0,\t0.37/100.0,\t1.91/100.0])\r\n    matFrwd = np.array([1.0, 2.0, 3.0, 5.0])\r\n    \r\n    # At this point we already know P(0,T) for the discount curve\r\n    P0TDiscount = lambda t: P0TModel(t,mat,ri,method)\r\n    swap1Frwd = lambda P0TFrwd: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,KFrwd[0],0.0,0.0,matFrwd[0],4*matFrwd[0],P0TDiscount,P0TFrwd)\r\n    swap2Frwd = lambda P0TFrwd: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,KFrwd[1],0.0,0.0,matFrwd[1],5*matFrwd[1],P0TDiscount,P0TFrwd)\r\n    swap3Frwd = lambda P0TFrwd: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,KFrwd[2],0.0,0.0,matFrwd[2],6*matFrwd[2],P0TDiscount,P0TFrwd)\r\n    swap4Frwd = lambda P0TFrwd: IRSwapMultiCurve(OptionTypeSwap.PAYER,1,KFrwd[3],0.0,0.0,matFrwd[3],7*matFrwd[3],P0TDiscount,P0TFrwd)\r\n    \r\n    instrumentsFrwd = [swap1Frwd,swap2Frwd,swap3Frwd,swap4Frwd]\r\n    \r\n    # determine optimal spine points for the forward curve\r\n    riFrwd = YieldCurve(instrumentsFrwd, matFrwd, r0Frwd, method, tol)\r\n    print('\\n Frwd Spine points are',riFrwd,'\\n')\r\n    \r\n    # Build a ZCB-curve/yield curve from the spine points\r\n    P0TFrwd_Initial = lambda t: P0TModel(t,matFrwd,r0Frwd,method)\r\n    P0TFrwd         = lambda t: P0TModel(t,matFrwd,riFrwd,method)\r\n    # price back the swaps\r\n    swapsModelFrwd   = np.zeros(len(instrumentsFrwd))\r\n    swapsInitialFrwd = np.zeros(len(instrumentsFrwd))\r\n    \r\n    for i in range(0,len(instrumentsFrwd)):\r\n        swapsModelFrwd[i]   = instrumentsFrwd[i](P0TFrwd)\r\n        swapsInitialFrwd[i] = instrumentsFrwd[i](P0TFrwd_Initial)\r\n    \r\n    print('Prices for Pas Swaps (initial) = ',swapsInitialFrwd,'\\n')\r\n    print('Prices for Par Swaps = ',swapsModelFrwd,'\\n')\r\n    \r\n    print(swap1Frwd(P0TFrwd))\r\n    \r\n    t = np.linspace(0,10,100)\r\n    plt.figure()\r\n    plt.plot(t,P0TDiscount(t),'--r')\r\n    plt.plot(t,P0TFrwd(t),'-b')\r\n    plt.legend(['discount','forecast'])\r\n    \r\n    return 0.0\r\n\r\nmainCode()\r\n    \r\n    "
  },
  {
    "path": "Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/YieldCurveBuildGreeks.py",
    "content": "#%%\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 purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport enum \r\nfrom copy import deepcopy\r\nfrom scipy.interpolate import splrep, splev, interp1d\r\n\r\n# This class defines puts and calls\r\nclass OptionTypeSwap(enum.Enum):\r\n    RECEIVER = 1.0\r\n    PAYER = -1.0\r\n    \r\ndef IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n    ti_grid = np.linspace(Ti,Tm,int(n))\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    # overwrite Ti if t>Ti\r\n    prevTi = ti_grid[np.where(ti_grid<t)]\r\n    if np.size(prevTi) > 0: \r\n        Ti = prevTi[-1]\r\n    \r\n    # Now we need to handle the case when some payments are already done\r\n    ti_grid = ti_grid[np.where(ti_grid>t)]           \r\n\r\n    temp= 0.0\r\n        \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if ti>Ti:\r\n            temp = temp + tau * P0T(ti)\r\n            \r\n    P_t_Ti = P0T(Ti)\r\n    P_t_Tm = P0T(Tm)\r\n    \r\n    if CP==OptionTypeSwap.PAYER:\r\n        swap = (P_t_Ti - P_t_Tm) - K * temp\r\n    elif CP==OptionTypeSwap.RECEIVER:\r\n        swap = K * temp - (P_t_Ti - P_t_Tm)\r\n    \r\n    return swap * notional\r\n\r\ndef P0TModel(t,ti,ri,method):\r\n    rInterp = method(ti,ri)\r\n    if t>=ti[-1]:\r\n        r = ri[-1]\r\n    if t<=ti[0]:\r\n        r= ri[0]\r\n    if t>ti[0] and t<ti[-1]:\r\n        r = rInterp(t)\r\n    \r\n    return np.exp(-r*t)\r\n\r\ndef YieldCurve(instruments, maturities, r0, method, tol):\r\n    r0 = deepcopy(r0)\r\n    ri = MultivariateNewtonRaphson(r0, maturities, instruments, method, tol=tol)\r\n    return ri\r\n\r\ndef MultivariateNewtonRaphson(ri, ti, instruments, method, tol):\r\n    err = 10e10\r\n    idx = 0\r\n    while err > tol:\r\n        idx = idx +1\r\n        values = EvaluateInstruments(ti,ri,instruments,method)\r\n        J = Jacobian(ti,ri, instruments, method)\r\n        J_inv = np.linalg.inv(J)\r\n        err = - np.dot(J_inv, values)\r\n        ri[0:] = ri[0:] + err \r\n        err = np.linalg.norm(err)\r\n        print('index in the loop is',idx,' Error is ', err)\r\n    return ri\r\n\r\ndef Jacobian(ti, ri, instruments, method):\r\n    eps = 1e-05\r\n    swap_num = len(ti)\r\n    J = np.zeros([swap_num, swap_num])\r\n    val = EvaluateInstruments(ti,ri,instruments,method)\r\n    ri_up = deepcopy(ri)\r\n    \r\n    for j in range(0, len(ri)):\r\n        ri_up[j] = ri[j] + eps  \r\n        val_up = EvaluateInstruments(ti,ri_up,instruments,method)\r\n        ri_up[j] = ri[j]\r\n        dv = (val_up - val) / eps\r\n        J[:, j] = dv[:]\r\n    return J\r\n\r\ndef EvaluateInstruments(ti,ri,instruments,method):\r\n    P0Ttemp = lambda t: P0TModel(t,ti,ri,method)\r\n    val = np.zeros(len(instruments))\r\n    for i in range(0,len(instruments)):\r\n        val[i] = instruments[i](P0Ttemp)\r\n    return val\r\n\r\ndef linear_interpolation(ti,ri):\r\n    interpolator = lambda t: np.interp(t, ti, ri)\r\n    return interpolator\r\n\r\ndef quadratic_interpolation(ti, ri):\r\n    interpolator = interp1d(ti, ri, kind='quadratic')\r\n    return interpolator\r\n\r\ndef cubic_interpolation(ti, ri):\r\n    interpolator = interp1d(ti, ri, kind='cubic')\r\n    return interpolator\r\n\r\n#def ComputeDelta(instrument,)\r\n\r\ndef BuildInstruments(K,mat):\r\n    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[0],0.0,0.0,mat[0],4*mat[0],P0T)\r\n    swap2 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[1],0.0,0.0,mat[1],4*mat[1],P0T)\r\n    swap3 = lambda P0T: IRSwap(OptionTypeSwap.RECEIVER,1,K[2],0.0,0.0,mat[2],4*mat[2],P0T)\r\n    swap4 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[3],0.0,0.0,mat[3],4*mat[3],P0T)\r\n    swap5 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[4],0.0,0.0,mat[4],4*mat[4],P0T)\r\n    swap6 = lambda P0T: IRSwap(OptionTypeSwap.RECEIVER,1,K[5],0.0,0.0,mat[5],4*mat[5],P0T)\r\n    swap7 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[6],0.0,0.0,mat[6],4*mat[6],P0T)\r\n    swap8 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[7],0.0,0.0,mat[7],4*mat[7],P0T)\r\n    instruments = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8]\r\n    return instruments\r\n\r\ndef mainCode():\r\n    \r\n    # Convergence tolerance\r\n    tol = 1.0e-8\r\n    # Initial guess for the spine points\r\n    r0 = np.array([0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01])   \r\n    # Interpolation method\r\n    method = cubic_interpolation\r\n    \r\n    # Construct swaps that are used for building of the yield curve\r\n    K   = np.array([0.02, 0.03, 0.04, 0.05, 0.06, 0.07,0.08,0.09])\r\n    mat = np.array([1.0,2.0,3.0,5.0,7.0,10.0,20.0,30.0])\r\n    \r\n    instruments = BuildInstruments(K,mat)\r\n    ri = YieldCurve(instruments, mat, r0, method, tol)\r\n    P0T = lambda t: P0TModel(t,mat,ri,method)\r\n        \r\n    # Define off market Swap \r\n    SwapLambda = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,0.03,0.0,0.0,4,6*mat[0],P0T)\r\n    swap= SwapLambda(P0T)\r\n    print('Swap price = ',swap)\r\n    \r\n    dK = 0.0001\r\n    delta = np.zeros(len(K))\r\n    K_new = K\r\n    for i in range(0,len(K)):\r\n        K_new[i] = K_new[i] + dK\r\n        instruments = BuildInstruments(K_new,mat)\r\n        ri = YieldCurve(instruments, mat, r0, method, tol)\r\n        P0T_new = lambda t: P0TModel(t,mat,ri,method)\r\n        swap_shock = SwapLambda(P0T_new)\r\n        delta[i] = (swap_shock-swap)/dK\r\n        K_new[i] = K_new[i] - dK\r\n    \r\n    print(delta)\r\n    return 0.0\r\n\r\nmainCode()\r\n    \r\n    "
  },
  {
    "path": "Lecture 06-Construction of Yield Curve and Multi-Curve/Materials/YieldCurveBuild_Treasury.py",
    "content": "#%%\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 purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport enum \r\n\r\nfrom copy import deepcopy\r\nfrom scipy.interpolate import splrep, splev, interp1d\r\n\r\n# This class defines puts and calls\r\nclass OptionTypeSwap(enum.Enum):\r\n    RECEIVER = 1.0\r\n    PAYER = -1.0\r\n    \r\ndef IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n    ti_grid = np.linspace(Ti,Tm,int(n))\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    # overwrite Ti if t>Ti\r\n    prevTi = ti_grid[np.where(ti_grid<t)]\r\n    if np.size(prevTi) > 0: #prevTi != []:\r\n        Ti = prevTi[-1]\r\n    \r\n    # Now we need to handle the case when some payments are already done\r\n    ti_grid = ti_grid[np.where(ti_grid>t)]           \r\n\r\n    temp= 0.0\r\n        \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if ti>Ti:\r\n            temp = temp + tau * P0T(ti)\r\n            \r\n    P_t_Ti = P0T(Ti)\r\n    P_t_Tm = P0T(Tm)\r\n    \r\n    if CP==OptionTypeSwap.PAYER:\r\n        swap = (P_t_Ti - P_t_Tm) - K * temp\r\n    elif CP==OptionTypeSwap.RECEIVER:\r\n        swap = K * temp - (P_t_Ti - P_t_Tm)\r\n    \r\n    return swap * notional\r\n\r\ndef P0TModel(t,ti,ri,method):\r\n    rInterp = method(ti,ri)\r\n    r = rInterp(t)\r\n    return np.exp(-r*t)\r\n\r\ndef YieldCurve(instruments, maturities, r0, method, tol):\r\n    r0 = deepcopy(r0)\r\n    ri = MultivariateNewtonRaphson(r0, maturities, instruments, method, tol=tol)\r\n    return ri\r\n\r\ndef MultivariateNewtonRaphson(ri, ti, instruments, method, tol):\r\n    err = 10e10\r\n    idx = 0\r\n    while err > tol:\r\n        idx = idx +1\r\n        values = EvaluateInstruments(ti,ri,instruments,method)\r\n        J = Jacobian(ti,ri, instruments, method)\r\n        J_inv = np.linalg.inv(J)\r\n        err = - np.dot(J_inv, values)\r\n        ri[0:] = ri[0:] + err \r\n        err = np.linalg.norm(err)\r\n        print('index in the loop is',idx,' Error is ', err)\r\n    return ri\r\n\r\ndef Jacobian(ti, ri, instruments, method):\r\n    eps = 1e-05\r\n    swap_num = len(ti)\r\n    J = np.zeros([swap_num, swap_num])\r\n    val = EvaluateInstruments(ti,ri,instruments,method)\r\n    ri_up = deepcopy(ri)\r\n    \r\n    for j in range(0, len(ri)):\r\n        ri_up[j] = ri[j] + eps  \r\n        val_up = EvaluateInstruments(ti,ri_up,instruments,method)\r\n        ri_up[j] = ri[j]\r\n        dv = (val_up - val) / eps\r\n        J[:, j] = dv[:]\r\n    return J\r\n\r\ndef EvaluateInstruments(ti,ri,instruments,method):\r\n    P0Ttemp = lambda t: P0TModel(t,ti,ri,method)\r\n    val = np.zeros(len(instruments))\r\n    for i in range(0,len(instruments)):\r\n        val[i] = instruments[i](P0Ttemp)\r\n    return val\r\n\r\ndef linear_interpolation(ti,ri):\r\n    interpolator = lambda t: np.interp(t, ti, ri)\r\n    return interpolator\r\n\r\ndef mainCode():\r\n    \r\n    # Convergence tolerance\r\n    tol = 1.0e-15\r\n    # Initial guess for the spine points\r\n    r0 = np.array([0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01])   \r\n    # Interpolation method\r\n    method = linear_interpolation\r\n    \r\n    # Construct swaps that are used for building of the yield curve\r\n    K   = np.array([0.04/100.0,\t0.16/100.0,\t0.31/100.0,\t0.81/100.0,\t1.28/100.0,\t1.62/100.0,\t2.22/100.0,\t2.30/100.0])\r\n    mat = np.array([1.0,2.0,3.0,5.0,7.0,10.0,20.0,30.0])\r\n    \r\n    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[0],0.0,0.0,mat[0],4*mat[0],P0T)\r\n    swap2 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[1],0.0,0.0,mat[1],4*mat[1],P0T)\r\n    swap3 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[2],0.0,0.0,mat[2],4*mat[2],P0T)\r\n    swap4 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[3],0.0,0.0,mat[3],4*mat[3],P0T)\r\n    swap5 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[4],0.0,0.0,mat[4],4*mat[4],P0T)\r\n    swap6 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[5],0.0,0.0,mat[5],4*mat[5],P0T)\r\n    swap7 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[6],0.0,0.0,mat[6],4*mat[6],P0T)\r\n    swap8 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[7],0.0,0.0,mat[7],4*mat[7],P0T)\r\n    instruments = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8]\r\n    \r\n    # determine optimal spine points\r\n    ri = YieldCurve(instruments, mat, r0, method, tol)\r\n    print('\\n Spine points are',ri,'\\n')\r\n    \r\n    # Build a ZCB-curve/yield curve from the spine points\r\n    P0T_Initial = lambda t: P0TModel(t,mat,r0,method)\r\n    P0T = lambda t: P0TModel(t,mat,ri,method)\r\n    \r\n    # price back the swaps\r\n    swapsModel = np.zeros(len(instruments))\r\n    swapsInitial = np.zeros(len(instruments))\r\n    for i in range(0,len(instruments)):\r\n        swapsInitial[i] = instruments[i](P0T_Initial)\r\n        swapsModel[i] = instruments[i](P0T)\r\n    \r\n    print('Prices for Pas Swaps (initial) = ',swapsInitial,'\\n')\r\n    print('Prices for Par Swaps = ',swapsModel,'\\n')\r\n    \r\n    return 0.0\r\n\r\nmainCode()\r\n    \r\n    "
  },
  {
    "path": "Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/HW_CapletsAndFloorlets.py",
    "content": "#%%\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 and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport enum \r\nimport matplotlib.pyplot as plt\r\nimport scipy.stats as st\r\nimport scipy.integrate as integrate\r\nimport scipy.optimize as optimize\r\n\r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    return np.exp(A_r + B_r *rT1)\r\n\r\ndef HWMean_r(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,2500)\r\n    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))\r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    return r_mean\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    return r0\r\n\r\ndef HW_Mu_FrwdMeasure(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,500)\r\n    \r\n    theta_hat =lambda t,T:  theta(t) + eta*eta / lambd *1.0/lambd * (np.exp(-lambd*(T-t))-1.0)\r\n    \r\n    temp =lambda z: theta_hat(z,T) * np.exp(-lambd*(T-z))\r\n    \r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    \r\n    return r_mean\r\n\r\ndef HWVar_r(lambd,eta,T):\r\n    return eta*eta/(2.0*lambd) *( 1.0-np.exp(-2.0*lambd *T))\r\n\r\ndef HWDensity(P0T,lambd,eta,T):\r\n    r_mean = HWMean_r(P0T,lambd,eta,T)\r\n    r_var = HWVar_r(lambd,eta,T)\r\n    return lambda x: st.norm.pdf(x,r_mean,np.sqrt(r_var))\r\n\r\ndef HW_CapletFloorletPrice(CP,N,K,lambd,eta,P0T,T1,T2):\r\n    if CP == OptionType.CALL:\r\n        N_new = N * (1.0+(T2-T1)*K)\r\n        K_new = 1.0 + (T2-T1)*K\r\n        caplet = N_new*HW_ZCB_CallPutPrice(OptionType.PUT,1.0/K_new,lambd,eta,P0T,T1,T2)\r\n        value= caplet\r\n    elif CP==OptionType.PUT:\r\n        N_new = N * (1.0+ (T2-T1)*K)\r\n        K_new = 1.0 + (T2-T1)*K\r\n        floorlet = N_new*HW_ZCB_CallPutPrice(OptionType.CALL,1.0/K_new,lambd,eta,P0T,T1,T2)\r\n        value = floorlet\r\n    return value\r\n    \r\ndef HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    \r\n    mu_r = HW_Mu_FrwdMeasure(P0T,lambd,eta,T1)\r\n    v_r =  np.sqrt(HWVar_r(lambd,eta,T1))\r\n    \r\n    K_hat = K * np.exp(-A_r)\r\n    \r\n    a = (np.log(K_hat) - B_r*mu_r)/(B_r*v_r)\r\n    \r\n    d1 = a - B_r*v_r\r\n    d2 = d1 +  B_r*v_r\r\n    \r\n    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)    \r\n    value =P0T(T1) * np.exp(A_r) * term1 \r\n    \r\n    if CP == OptionType.CALL:\r\n        return value\r\n    elif CP==OptionType.PUT:\r\n        return value - P0T(T2) + K*P0T(T1)\r\n\r\n# Black-Scholes Call option price\r\ndef BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):\r\n    if K is list:\r\n        K = np.array(K).reshape([len(K),1])\r\n    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * tau) / (sigma * np.sqrt(tau))\r\n    d2    = d1 - sigma * np.sqrt(tau)\r\n    if CP == OptionType.CALL:\r\n        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)\r\n    elif CP == OptionType.PUT:\r\n        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0\r\n    return value\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):\r\n    # To determine initial volatility we interpolate define a grid for sigma\r\n    # and interpolate on the inverse\r\n    sigmaGrid = np.linspace(0.0,5.0,5000)\r\n    optPriceGrid = BS_Call_Put_Option_Price(CP,S_0,K,sigmaGrid,T,0.0)\r\n    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)\r\n    print(\"Strike = {0}\".format(K))\r\n    print(\"Initial volatility = {0}\".format(sigmaInitial))\r\n    \r\n    # Use determined input for the local-search (final tuning)\r\n    func = lambda sigma: np.power(BS_Call_Put_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15)\r\n    print(\"Final volatility = {0}\".format(impliedVol))\r\n    if impliedVol > 2.0:\r\n        impliedVol = 0.0\r\n    return impliedVol\r\n\r\ndef mainCalculation():\r\n    CP= OptionType.CALL\r\n    NoOfPaths = 20000\r\n    NoOfSteps = 1000\r\n        \r\n    lambd     = 0.02\r\n    eta       = 0.02\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.1*T)#np.exp(-0.03*T*T-0.1*T)\r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 25\r\n    T_end = 50\r\n    Tgrid= np.linspace(0,T_end,N)\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy= np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Exact[i] = P0T(Ti)\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy,'--r')\r\n    plt.legend([\"Analytcal ZCB\",\"Monte Carlo ZCB\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n\r\n    # In this experiment we compare Monte Carlo results for \r\n    T1 = 4.0\r\n    T2 = 8.0\r\n    \r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T1 ,P0T, lambd, eta)\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n    dt = timeGrid[1]-timeGrid[0]\r\n    \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    \r\n    M_t = np.zeros([NoOfPaths,NoOfSteps])\r\n    for i in range(0,NoOfPaths):\r\n        M_t[i,:] = np.exp(np.cumsum(r[i,:-1])*dt)\r\n        \r\n    KVec = np.linspace(0.01,1.7,50)\r\n    Price_MC_V = np.zeros([len(KVec),1])\r\n    Price_Th_V =np.zeros([len(KVec),1])\r\n    P_T1_T2 = HW_ZCB(lambd,eta,P0T,T1,T2,r[:,-1])\r\n    for i,K in enumerate(KVec):\r\n        if CP==OptionType.CALL:\r\n            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(P_T1_T2-K,0.0)) \r\n        elif CP==OptionType.PUT:\r\n            Price_MC_V[i] =np.mean( 1.0/M_t[:,-1] * np.maximum(K-P_T1_T2,0.0)) \r\n        Price_Th_V[i] =HW_ZCB_CallPutPrice(CP,K,lambd,eta,P0T,T1,T2)#HW_ZCB_CallPrice(K,lambd,eta,P0T,T1,T2)\r\n        \r\n    plt.figure(2)\r\n    plt.grid()\r\n    plt.plot(KVec,Price_MC_V)\r\n    plt.plot(KVec,Price_Th_V,'--r')\r\n    plt.legend(['Monte Carlo','Theoretical'])\r\n    plt.title('Option on ZCB')\r\n\r\n    # Effect of the HW model parameters on Implied Volatilities\r\n    # define a forward rate between T1 and T2\r\n    frwd = 1.0/(T2-T1) *(P0T(T1)/P0T(T2)-1.0)\r\n    K = np.linspace(frwd/2.0,3.0*frwd,25)\r\n    \r\n    # Effect of eta\r\n    plt.figure(3)\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('implied volatility')\r\n    etaV =[0.01, 0.02, 0.03, 0.04]\r\n    legend = []\r\n    Notional = 1.0\r\n    for etaTemp in etaV:    \r\n       optPrice = HW_CapletFloorletPrice(CP,Notional,K,lambd,etaTemp,P0T,T1,T2)\r\n       # Implied volatilities\r\n       IV =np.zeros([len(K),1])\r\n       for idx in range(0,len(K)):\r\n           valFrwd = optPrice[idx]/P0T(T2)/(T2-T1)\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K[idx],T2,frwd)\r\n       plt.plot(K,IV*100.0)\r\n       #plt.plot(K,optPrice)\r\n       legend.append('eta={0}'.format(etaTemp))\r\n    plt.legend(legend)        \r\n    \r\n     # Effect of lambda\r\n    plt.figure(4)\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('implied volatility')\r\n    lambdaV = [0.01, 0.03, 0.05, 0.09]\r\n    legend = []\r\n    Notional = 1.0\r\n    for lambdTemp in lambdaV:    \r\n       optPrice = HW_CapletFloorletPrice(CP,Notional,K,lambdTemp,eta,P0T,T1,T2)\r\n       # Implied volatilities\r\n       IV =np.zeros([len(K),1])\r\n       for idx in range(0,len(K)):\r\n           valFrwd = optPrice[idx]/P0T(T2)/(T2-T1)\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K[idx],T2,frwd)\r\n       #plt.plot(K,optPrice)\r\n       plt.plot(K,IV*100.0)\r\n       legend.append('lambda={0}'.format(lambdTemp))\r\n    plt.legend(legend)  \r\n\r\n    print('frwd={0}'.format(frwd*P0T(T2)))\r\nmainCalculation()"
  },
  {
    "path": "Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/JamshidianTrick.py",
    "content": "#%%\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 educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.optimize as optimize\r\n\r\ndef PsiSum(psi,N,x):\r\n    temp = 0\r\n    for i in range(0,N):\r\n        temp = temp + psi(i,x)\r\n    return temp\r\n\r\ndef JamshidianTrick(psi,N,K):\r\n    A = lambda x: PsiSum(psi,N,x) - K\r\n    result = optimize.newton(A,0.1)\r\n    return result\r\n\r\ndef Main():\r\n    NoOfSamples = 1000\r\n    X =  np.random.normal(0.0,1.0,[NoOfSamples,1])\r\n    psi_i = lambda i,X: np.exp(-i*np.abs(X))\r\n    \r\n    # Number of terms\r\n    N = 15\r\n    \r\n    A = 0\r\n    for i in range(0,N):\r\n        A = A + psi_i(i,X)\r\n    \r\n    K = np.linspace(2,10,10)\r\n    resultMC = np.zeros(len(K))\r\n    for (i,Ki) in enumerate(K):\r\n        resultMC[i] = np.mean(np.maximum(A-Ki,0))\r\n    \r\n    # Jamshidians trick\r\n    resultJams = np.zeros(len(K))\r\n    for i,Ki in enumerate(K):\r\n        # Compute optimal K*\r\n        optX = JamshidianTrick(psi_i,N,Ki)\r\n        A = 0\r\n        for j in range(0,N):\r\n            A = A + np.mean(np.maximum(psi_i(j,X)-psi_i(j,optX),0))\r\n        resultJams[i] = A\r\n        \r\n    plt.figure()\r\n    plt.plot(K,resultMC)\r\n    plt.plot(K,resultJams,'--r')\r\n    plt.grid()\r\n    plt.xlabel('K')\r\n    plt.ylabel('expectation')\r\n    plt.legend(['Monte Carlo','Jamshidians trick'])\r\n    \r\nMain()"
  },
  {
    "path": "Lecture 07-Pricing of Swaptions and Negative Interest Rates/Materials/ShiftedLognormal.py",
    "content": "#%%\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 comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.stats as st\r\nimport enum \r\nimport scipy.optimize as optimize\r\n\r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\ndef GeneratePathsGBMShifted(NoOfPaths,NoOfSteps,T,r,sigma,S_0,shift):\r\n    S_0shift = S_0 + shift\r\n    if (S_0shift < 0.0):\r\n        raise('Shift is too small!')\r\n    \r\n    paths =GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0shift)\r\n    Sshifted = paths[\"S\"] - shift\r\n    time = paths[\"time\"]\r\n    return {\"time\":time,\"S\":Sshifted}      \r\n\r\ndef GeneratePathsGBM(NoOfPaths,NoOfSteps,T,r,sigma,S_0):    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    X = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    X[:,0] = np.log(S_0)\r\n    \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        X[:,i+1] = X[:,i] + (r - 0.5 * sigma * sigma) * dt + sigma * (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    #Compute exponent of ABM\r\n    S = np.exp(X)\r\n    paths = {\"time\":time,\"S\":S}\r\n    return paths\r\n\r\n# Shifted Black-Scholes Call option price\r\ndef BS_Call_Put_Option_Price_Shifted(CP,S_0,K,sigma,tau,r,shift):\r\n    K_new = K + shift\r\n    S_0_new = S_0 + shift\r\n    return BS_Call_Put_Option_Price(CP,S_0_new,K_new,sigma,tau,r)\r\n\r\n# Black-Scholes Call option price\r\ndef BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):\r\n    if K is list:\r\n        K = np.array(K).reshape([len(K),1])\r\n    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * tau) / (sigma * np.sqrt(tau))\r\n    d2    = d1 - sigma * np.sqrt(tau)\r\n    if CP == OptionType.CALL:\r\n        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)\r\n    elif CP == OptionType.PUT:\r\n        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0\r\n    return value\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):\r\n    # To determine initial volatility we interpolate define a grid for sigma\r\n    # and interpolate on the inverse\r\n    sigmaGrid = np.linspace(0.0,2.0,5000)\r\n    optPriceGrid = BS_Call_Put_Option_Price(CP,S_0,K,sigmaGrid,T,0.0)\r\n    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)\r\n    print(\"Initial volatility = {0}\".format(sigmaInitial))\r\n    \r\n    # Use determined input for the local-search (final tuning)\r\n    func = lambda sigma: np.power(BS_Call_Put_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15)\r\n    print(\"Final volatility = {0}\".format(impliedVol))\r\n    return impliedVol\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76Shifted(CP,marketPrice,K,T,S_0,shift):\r\n    # To determine initial volatility we interpolate define a grid for sigma\r\n    # and interpolate on the inverse\r\n    sigmaGrid = np.linspace(0.0,2.0,5000)\r\n    optPriceGrid = BS_Call_Put_Option_Price_Shifted(CP,S_0,K,sigmaGrid,T,0.0,shift)\r\n    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)\r\n    print(\"Initial volatility = {0}\".format(sigmaInitial))\r\n    \r\n    # Use determined input for the local-search (final tuning)\r\n    func = lambda sigma: np.power(BS_Call_Put_Option_Price_Shifted(CP,S_0,K,sigma,T,0.0,shift) - marketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15)\r\n    print(\"Final volatility = {0}\".format(impliedVol))\r\n    return impliedVol\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 10000\r\n    NoOfSteps = 500\r\n    T         = 3.0\r\n    sigma     = 0.2\r\n    L0        = -0.05\r\n    shift     = 0.1\r\n    K         = [0.95]\r\n    CP        = OptionType.CALL\r\n    \r\n    P0T =lambda T: np.exp(-0.1*T)\r\n    \r\n    np.random.seed(4)\r\n    Paths = GeneratePathsGBMShifted(NoOfPaths,NoOfSteps,T,0.0,sigma,L0,shift)\r\n    time  = Paths[\"time\"]\r\n    L     = Paths[\"S\"]\r\n    \r\n    print(np.mean(L[:,-1]))\r\n    \r\n    # Plot first few paths\r\n    plt.figure(1)\r\n    plt.plot(time,np.transpose(L[0:20,:]))\r\n    plt.grid()\r\n    \r\n    # Shifted lognormal for different shift parameters\r\n    plt.figure(2)\r\n    shiftV = [1.0,2.0,3.0,4.0,5.0]\r\n    legend = []\r\n    for shiftTemp in shiftV:\r\n        x = np.linspace(-shiftTemp,10,1000)\r\n        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)\r\n        pdf_x= lognnormPDF(x,T)\r\n        plt.plot(x,pdf_x)\r\n        legend.append('shift={0}'.format(shiftTemp))\r\n    plt.legend(legend)\r\n    plt.xlabel('x')\r\n    plt.ylabel('pdf')\r\n    plt.title('shifted lognormal density')\r\n    plt.grid()\r\n    \r\n    # Call/Put option prices, MC vs. Analytical\r\n    plt.figure(3)\r\n    K = np.linspace(-shift,np.abs(L0)*3,25)\r\n    optPriceMCV=np.zeros([len(K),1])\r\n    for idx in range(0,len(K)):\r\n        optPriceMCV[idx] =0.0\r\n        if CP == OptionType.CALL:\r\n            optPriceMCV[idx] = P0T(T)*np.mean(np.maximum(L[:,-1]-K[idx],0.0))\r\n        elif CP == OptionType.PUT:\r\n            optPriceMCV[idx] = P0T(T)*np.mean(np.maximum(K[idx]-L[:,-1],0.0))\r\n    \r\n    optPriceExact = P0T(T)*BS_Call_Put_Option_Price_Shifted(CP,L0,K,sigma,T,0.0,shift)\r\n    plt.plot(K,optPriceMCV)       \r\n    plt.plot(K,optPriceExact,'--r')       \r\n    plt.grid()\r\n    plt.xlabel('strike,K')\r\n    plt.ylabel('option price')\r\n    plt.legend(['Monte Carlo','Exact'])\r\n    \r\n    # Shift Effect on Option prices\r\n    plt.figure(4)\r\n    legend = []\r\n    for shiftTemp in [0.2,0.3,0.4,0.5]:    \r\n        K = np.linspace(-shiftTemp,np.abs(L0)*6.0,25)\r\n        optPriceExact = P0T(T)*BS_Call_Put_Option_Price_Shifted(CP,L0,K,sigma,T,0.0,shiftTemp)\r\n        plt.plot(K,optPriceExact)       \r\n        legend.append('shift={0}'.format(shiftTemp))\r\n    plt.grid()\r\n    plt.xlabel('strike,K')\r\n    plt.ylabel('option price')\r\n    plt.legend(legend)\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 08-Mortgages and Prepayments/Materials/AnnuityMortgage.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nAnnuity mortgage- payment profile\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak and Emanuele Casamassima\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\n\r\ndef Annuity(rate,notional,periods,CPR):\r\n    # it returns a matrix M such that\r\n    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_quote(t)  installment(t)]\r\n    # WARNING! here \"rate\" and \"periods\" are quite general, the choice of getting year/month/day.. steps, depends on the rate\r\n    # that the function receives. So, it is necessary to pass the correct rate to the function\r\n    M = np.zeros((periods + 1,6))\r\n    M[:,0] = np.arange(periods + 1) # we define the times\r\n    M[0,1] = notional\r\n    for t in range(1,periods + 1):\r\n        # we are computing the installment at time t knowing the oustanding at time (t-1)\r\n        remaining_periods = periods - (t - 1)  \r\n        \r\n        # Installment, C(t_i) \r\n        M[t,5] = rate * M[t-1,1]/(1 - 1/(1 + rate)**remaining_periods) \r\n        \r\n        # Interest rate payment, I(t_i) = K * N(t_{i})\r\n        M[t,4] = rate * M[t-1,1] \r\n        \r\n        # Notional payment, Q(t_i) = C(t_i) - I(t_i)\r\n        M[t,3] = M[t,5] - M[t,4] \r\n        \r\n        # Prepayment, P(t_i)= Lambda * (N(t_i) -Q(t_i))\r\n        M[t,2] = CPR * (M[t-1,1] - M[t,3]) \r\n        \r\n        # notional, N(t_{i+1}) = N(t_{i}) - lambda * (Q(t_{i} + P(t_i)))\r\n        M[t,1] = M[t-1,1] - M[t,3] - M[t,2] \r\n    return M\r\n\r\ndef mainCode():\r\n\r\n    # Initial notional\r\n    N0     = 1000000\r\n    \r\n    # Interest rates from a bank\r\n    r = 0.05\r\n    \r\n    # Prepayment rate, 0.1 = 10%\r\n    Lambda = 0.1\r\n\r\n    # For simplicity we assume 1 as unit (yearly payments of mortgage)\r\n    T_end = 30\r\n    M = Annuity(r,N0,T_end,Lambda)\r\n    \r\n    for i in range(0,T_end+1):\r\n        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]))\r\n    \r\n    plt.figure(1)\r\n    plt.plot(M[:,0],M[:,1],'.r')\r\n    plt.grid()\r\n    plt.xlabel('time')\r\n    plt.ylabel('notional')\r\n    return 0.0\r\n\r\nmainCode()\r\n    \r\n    "
  },
  {
    "path": "Lecture 08-Mortgages and Prepayments/Materials/BulletMortgage.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nBullet mortgage- payment profile\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak and Emanuele Casamassima\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\n\r\ndef Bullet(rate,notional,periods,CPR):\r\n    # it returns a matrix M such that\r\n    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_(t)  installment(t)]\r\n    # WARNING! here \"rate\" and \"periods\" are quite general, the choice of getting year/month/day.. steps, depends on the rate\r\n    # that the function receives. So, it is necessary to pass the correct rate to the function\r\n    M = np.zeros((periods + 1,6))\r\n    M[:,0] = np.arange(periods + 1) # we define the times\r\n    M[0,1] = notional\r\n    for t in range(1,periods):\r\n        M[t,4] = rate*M[t-1,1]      # interest quote\r\n        M[t,3] = 0                  # repayment, 0 for bullet mortgage\r\n        scheduled_oustanding = M[t-1,1] - M[t,3]\r\n        M[t,2] = scheduled_oustanding * CPR    # prepayment\r\n        M[t,1] = scheduled_oustanding - M[t,2] # notional(t) = notional(t-1) - (repayment + prepayment)\r\n        M[t,5] = M[t,4] + M[t,2] + M[t,3]\r\n        \r\n    M[periods,4] = rate*M[periods-1,1] # interest quote\r\n    M[periods,3] = M[periods-1,1]      # notional quote\r\n    M[periods,5] = M[periods,4] + M[periods,2] + M[periods,3]\r\n    return M\r\n\r\ndef mainCode():\r\n\r\n    # Initial notional\r\n    N0     = 1000000\r\n    \r\n    # Interest rates from a bank\r\n    r = 0.05\r\n    \r\n    # Prepayment rate, 0.1 = 10%\r\n    Lambda = 0.01\r\n\r\n    # For simplicity we assume 1 as unit (yearly payments of mortgage)\r\n    T_end = 30\r\n    M = Bullet(r,N0,T_end,Lambda)\r\n    \r\n    for i in range(0,T_end+1):\r\n        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]))\r\n    \r\n    plt.figure(1)\r\n    plt.plot(M[:,0],M[:,1],'-r')\r\n    plt.grid()\r\n    plt.xlabel('time')\r\n    plt.ylabel('notional')\r\n    \r\n    return 0.0\r\n\r\nmainCode()\r\n    \r\n    "
  },
  {
    "path": "Lecture 08-Mortgages and Prepayments/Materials/Incentives.py",
    "content": "#%%\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\" mortgage rate\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak and Emanuele Cassamassima\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\n\r\ndef Annuity(rate,notional,periods,CPR):\r\n    # it returns a matrix M such that\r\n    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_quote(t)  installment(t)]\r\n    # WARNING! here \"rate\" and \"periods\" are quite general, the choice of getting year/month/day.. steps, depends on the rate\r\n    # that the function receives. So, it is necessary to pass the correct rate to the function\r\n    M = np.zeros((periods + 1,6))\r\n    M[:,0] = np.arange(periods + 1) # we define the times\r\n    M[0,1] = notional\r\n    for t in range(1,periods + 1):\r\n        # we are computing the installment at time t knowing the oustanding at time (t-1)\r\n        remaining_periods = periods - (t - 1)  \r\n        \r\n        # Installment, C(t_i) \r\n        M[t,5] = rate * M[t-1,1]/(1 - 1/(1 + rate)**remaining_periods) \r\n        \r\n        # Interest rate payment, I(t_i) = r * N(t_{i})\r\n        M[t,4] = rate * M[t-1,1] \r\n        \r\n        # Notional payment, Q(t_i) = C(t_i) - I(t_i)\r\n        M[t,3] = M[t,5] - M[t,4] \r\n        \r\n        # Prepayment, P(t_i)= Lambda * (N(t_i) -Q(t_i))\r\n        M[t,2] = CPR * (M[t-1,1] - M[t,3]) \r\n        \r\n        # notional, N(t_{i+1}) = N(t_{i}) - lambda * (Q(t_{i} + P(t_i)))\r\n        M[t,1] = M[t-1,1] - M[t,3] - M[t,2] \r\n    return M\r\n\r\ndef mainCode():\r\n\r\n\r\n    IncentiveFunction = lambda x : 0.04 + 0.1/(1 + np.exp(115 * (0.02-x))) \r\n    \r\n    oldRate = 0.05\r\n    newRate = np.linspace(-0.05,0.15,25)\r\n    \r\n    epsilon = oldRate-newRate\r\n    incentive = IncentiveFunction(epsilon)\r\n    \r\n    plt.figure(1)\r\n    plt.plot(newRate,incentive)\r\n    plt.xlabel('S(t)')\r\n    plt.ylabel('Incentive')\r\n    plt.grid()\r\n    \r\n    plt.figure(2)\r\n    plt.plot(epsilon,incentive)\r\n    plt.xlabel('epsilon= K - S(t)')\r\n    plt.ylabel('Incentive')\r\n    plt.grid()\r\n\r\n    return 0.0\r\n\r\nmainCode()\r\n    \r\n    "
  },
  {
    "path": "Lecture 08-Mortgages and Prepayments/Materials/StochasticAmortizingSwap.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nStochastic amortization given the incentive function and irrational/rational behavior profile\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak and Emanuele Cassamassima\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.optimize as optimize\r\nimport scipy.integrate as integrate\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    return theta\r\n\r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    n = np.size(rT1) \r\n        \r\n    if T1<T2:\r\n        B_r = HW_B(lambd,eta,T1,T2)\r\n        A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n        return np.exp(A_r + B_r *rT1)\r\n    else:\r\n        return np.ones([n])\r\n\r\ndef SwapRateHW(t,Ti,Tm,n,r_t,P0T,lambd,eta):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n\r\n    if n == 1:\r\n        ti_grid =np.array([Ti,Tm])\r\n    else:\r\n        ti_grid = np.linspace(Ti,Tm,n)\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    # overwrite Ti if t>Ti\r\n    prevTi = ti_grid[np.where(ti_grid<t)]\r\n    if np.size(prevTi) > 0: #prevTi != []:\r\n        Ti = prevTi[-1]\r\n    \r\n    # Now we need to handle the case when some payments are already done\r\n    ti_grid = ti_grid[np.where(ti_grid>t)]          \r\n\r\n    temp= np.zeros(np.size(r_t));\r\n    \r\n    P_t_TiLambda = lambda Ti : HW_ZCB(lambd,eta,P0T,t,Ti,r_t)\r\n    \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if ti>Ti:\r\n            temp = temp + tau * P_t_TiLambda(ti)\r\n            \r\n    P_t_Ti = P_t_TiLambda(Ti)\r\n    P_t_Tm = P_t_TiLambda(Tm)\r\n\r\n    swapRate = (P_t_Ti - P_t_Tm) / temp\r\n    \r\n    return swapRate\r\n\r\ndef Bullet(rate,notional,periods,CPR):\r\n    # it returns a matrix M such that\r\n    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_(t)  installment(t)]\r\n    # WARNING! here \"rate\" and \"periods\" are quite general, the choice of getting year/month/day.. steps, depends on the rate\r\n    # that the function receives. So, it is necessary to pass the correct rate to the function\r\n    M = np.zeros((periods + 1,6))\r\n    M[:,0] = np.arange(periods + 1) # we define the times\r\n    M[0,1] = notional\r\n    for t in range(1,periods):\r\n        M[t,4] = rate*M[t-1,1]      # interest quote\r\n        M[t,3] = 0                  # notional quote, 0 for bullet mortgage\r\n        scheduled_oustanding = M[t-1,1] - M[t,3]\r\n        M[t,2] = scheduled_oustanding * CPR[t]    # prepayment\r\n        M[t,1] = scheduled_oustanding - M[t,2] # notional(t) = notional(t-1) - (notional quote + prepayment)\r\n        M[t,5] = M[t,4] + M[t,2] + M[t,3]\r\n        \r\n    M[periods,4] = rate*M[periods-1,1] # interest quote\r\n    M[periods,3] = M[periods-1,1]      # notional quote\r\n    M[periods,5] = M[periods,4] + M[periods,2] + M[periods,3]\r\n    return M    \r\n\r\ndef Annuity(rate,notional,periods,CPR):\r\n    # it returns a matrix M such that\r\n    # M = [t  notional(t)  prepayment(t)  notional_quote(t)  interest_quote(t)  installment(t)]\r\n    # WARNING! here \"rate\" and \"periods\" are quite general, the choice of getting year/month/day.. steps, depends on the rate\r\n    # that the function receives. So, it is necessary to pass the correct rate to the function\r\n    M = np.zeros((periods + 1,6))\r\n    M[:,0] = np.arange(periods + 1) # we define the times\r\n    M[0,1] = notional\r\n    for t in range(1,periods + 1):\r\n        # we are computing the installment at time t knowing the oustanding at time (t-1)\r\n        remaining_periods = periods - (t - 1)  \r\n        \r\n        # Installment, C(t_i) \r\n        M[t,5] = rate * M[t-1,1]/(1 - 1/(1 + rate)**remaining_periods) \r\n        \r\n        # Interest rate payment, I(t_i) = r * N(t_{i})\r\n        M[t,4] = rate * M[t-1,1] \r\n        \r\n        # Notional payment, Q(t_i) = C(t_i) - I(t_i)\r\n        M[t,3] = M[t,5] - M[t,4] \r\n        \r\n        # Prepayment, P(t_i)= Lambda * (N(t_i) -Q(t_i))\r\n        M[t,2] = CPR[t] * (M[t-1,1] - M[t,3]) \r\n        \r\n        # notional, N(t_{i+1}) = N(t_{i}) - lambda * (Q(t_{i} + P(t_i)))\r\n        M[t,1] = M[t-1,1] - M[t,3] - M[t,2] \r\n    return M\r\n\r\ndef mainCode():\r\n\r\n    Irrational = lambda x : 0.04 + 0.1/(1 + np.exp(200 * (-x))) \r\n    Rational   = lambda x : 0.04*(x>0.0)\r\n\r\n    \r\n    IncentiveFunction = Irrational\r\n    \r\n    K = 0.05\r\n    newRate = np.linspace(-0.1,0.1,150)\r\n    \r\n    epsilon = K - newRate\r\n    incentive = IncentiveFunction(epsilon)\r\n    \r\n    plt.figure(1)\r\n    plt.plot(newRate,incentive)\r\n    plt.xlabel('S(t)')\r\n    plt.ylabel('Incentive')\r\n    plt.grid()\r\n    \r\n    plt.figure(2)\r\n    plt.plot(epsilon,incentive)\r\n    plt.xlabel('epsilon= K - S(t)')\r\n    plt.ylabel('Incentive')\r\n    plt.grid()\r\n\r\n    # Stochastic interest rates\r\n    NoOfPaths = 2000\r\n    NoOfSteps = 30\r\n    lambd     = 0.05\r\n    eta       = 0.01\r\n    \r\n    # End date of the underlying swap / mortgage\r\n    Tend = 30 \r\n\r\n    # Market ZCB\r\n    P0T = lambda T: np.exp(-0.05*T)\r\n    paths =  GeneratePathsHWEuler(NoOfPaths, NoOfSteps,Tend, P0T, lambd, eta)\r\n    R = paths[\"R\"]\r\n    tiGrid = paths[\"time\"]\r\n\r\n    # Compute swap rates, at this point we assume that the incentive is driven by the CMS rate\r\n    S = np.zeros([NoOfPaths,NoOfSteps+1])\r\n    for (i,ti) in enumerate(tiGrid):\r\n        S[:,i] = SwapRateHW(ti,ti,Tend+ti,30,R[:,i],P0T,lambd,eta)\r\n    \r\n    # Incentive for the new swap rate\r\n    epsilon = K - S[:,-1]\r\n    incentive = IncentiveFunction(epsilon)\r\n    plt.figure(3)\r\n    plt.plot(epsilon,incentive,'.r')\r\n    plt.xlabel('epsilon= K - S(t)')\r\n    plt.ylabel('Incentive')\r\n    plt.grid()\r\n    plt.title('Incentive for prepayment given stochastis S(t)')\r\n\r\n    plt.figure(4)\r\n    plt.hist(S[:,-1],bins=50)\r\n    plt.grid()\r\n    plt.title('Swap distribution at Tend')\r\n\r\n    # Building up of stochastic notional N(ti) for every ti\r\n    MortgageProfile =  Annuity\r\n    notional = 1000000\r\n    N = np.zeros([NoOfPaths,NoOfSteps+1])\r\n    \r\n    for i in range(0,NoOfPaths):\r\n        epsilon =  K - S[i,:]\r\n        Lambda = IncentiveFunction(epsilon)\r\n        NotionalProfile = MortgageProfile(K,notional,NoOfSteps,Lambda)\r\n        N[i,:] = NotionalProfile[:,1]\r\n\r\n    plt.figure(6)\r\n    plt.grid()\r\n    plt.xlabel('time')\r\n    plt.ylabel('notional')\r\n    \r\n    n = 100\r\n    for k in range(0,n):\r\n        plt.plot(tiGrid,N[k,:],'-b')\r\n    \r\n    AnnuityProfile_NoPrepayment = MortgageProfile(K,notional,NoOfSteps,np.zeros(NoOfSteps+1))\r\n    plt.plot(tiGrid,AnnuityProfile_NoPrepayment[:,1],'--r')\r\n    plt.title('Notional profile')\r\n        \r\n    return 0.0\r\n\r\nmainCode()\r\n    \r\n    "
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/BSHW_Comparison.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on Thu Jan 04 2019\r\nThe BSHW model and implied volatilities obtained with the COS method and comparisons \r\nagainst a factorized 1D case where the implied volatilities are known analytically\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\nimport scipy.stats as st\r\nimport enum \r\nimport scipy.optimize as optimize\r\n\r\n# set i= imaginary number\r\ni   = np.complex(0.0,1.0)\r\n\r\n# time-step needed for differentiation\r\ndt = 0.0001\r\n \r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0   \r\n\r\ndef CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):\r\n    # cf   - characteristic function as a functon, in the book denoted as \\varphi\r\n    # CP   - C for call and P for put\r\n    # S0   - Initial stock price\r\n    # tau  - time to maturity\r\n    # K    - list of strikes\r\n    # N    - Number of expansion terms\r\n    # L    - size of truncation domain (typ.:L=8 or L=10)  \r\n    # P0T  - zero-coupon bond for maturity T.\r\n        \r\n    # reshape K to a column vector\r\n    if K is not np.array:\r\n        K = np.array(K).reshape([len(K),1])\r\n    \r\n    #assigning i=sqrt(-1)\r\n    i = np.complex(0.0,1.0) \r\n    x0 = np.log(S0 / K)   \r\n    \r\n    # truncation domain\r\n    a = 0.0 - L * np.sqrt(tau)\r\n    b = 0.0 + L * np.sqrt(tau)\r\n    \r\n    # sumation from k = 0 to k=N-1\r\n    k = np.linspace(0,N-1,N).reshape([N,1])  \r\n    u = k * np.pi / (b - a);  \r\n\r\n    # Determine coefficients for Put Prices  \r\n    H_k = CallPutCoefficients(OptionType.PUT,a,b,k)   \r\n    mat = np.exp(i * np.outer((x0 - a) , u))\r\n    temp = cf(u) * H_k \r\n    temp[0] = 0.5 * temp[0]    \r\n    value = K * np.real(mat.dot(temp))     \r\n    \r\n    # we use call-put parity for call options\r\n    if CP == OptionType.CALL:\r\n        value = value + S0 - K * P0T\r\n        \r\n    return value\r\n\r\n# Determine coefficients for Put Prices \r\ndef CallPutCoefficients(CP,a,b,k):\r\n    if CP==OptionType.CALL:                  \r\n        c = 0.0\r\n        d = b\r\n        coef = Chi_Psi(a,b,c,d,k)\r\n        Chi_k = coef[\"chi\"]\r\n        Psi_k = coef[\"psi\"]\r\n        if a < b and b < 0.0:\r\n            H_k = np.zeros([len(k),1])\r\n        else:\r\n            H_k      = 2.0 / (b - a) * (Chi_k - Psi_k)  \r\n    elif CP==OptionType.PUT:\r\n        c = a\r\n        d = 0.0\r\n        coef = Chi_Psi(a,b,c,d,k)\r\n        Chi_k = coef[\"chi\"]\r\n        Psi_k = coef[\"psi\"]\r\n        H_k      = 2.0 / (b - a) * (- Chi_k + Psi_k)               \r\n    \r\n    return H_k    \r\n\r\ndef Chi_Psi(a,b,c,d,k):\r\n    psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a))\r\n    psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi)\r\n    psi[0] = d - c\r\n    \r\n    chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) \r\n    expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d)  - np.cos(k * np.pi \r\n                  * (c - a) / (b - a)) * np.exp(c)\r\n    expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * \r\n                        (d - a) / (b - a))   - k * np.pi / (b - a) * np.sin(k \r\n                        * np.pi * (c - a) / (b - a)) * np.exp(c)\r\n    chi = chi * (expr1 + expr2)\r\n    \r\n    value = {\"chi\":chi,\"psi\":psi }\r\n    return value\r\n    \r\n# Black-Scholes Call option price\r\ndef BS_Call_Option_Price(CP,S_0,K,sigma,tau,r):\r\n    if K is list:\r\n        K = np.array(K).reshape([len(K),1])\r\n    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) \r\n    * tau) / float(sigma * np.sqrt(tau))\r\n    d2    = d1 - sigma * np.sqrt(tau)\r\n    if CP == OptionType.CALL:\r\n        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)\r\n    elif CP == OptionType.PUT:\r\n        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0\r\n    return value\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):\r\n    func = lambda sigma: np.power(BS_Call_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, 0.2, tol=1e-9)\r\n    #impliedVol = optimize.brent(func, brack= (0.05, 2))\r\n    return impliedVol\r\n\r\ndef ChFBSHW(u, T, P0T, lambd, eta, rho, sigma):\r\n    i = np.complex(0.0,1.0)\r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    \r\n    theta = lambda t: 1.0/lambd * (f0T(t+dt)-f0T(t-dt))/(2.0*dt) + f0T(t) \\\r\n        + eta*eta/(2.0*lambd*lambd)*(1.0-np.exp(-2.0*lambd*t))      \r\n    C = lambda u,tau: 1.0/lambd*(i*u-1.0)*(1.0-np.exp(-lambd*tau))\r\n    \r\n    # define a grid for the numerical integration of function theta\r\n    zGrid = np.linspace(0.0,T,2500)\r\n    term1 = lambda u: 0.5*sigma*sigma *i*u*(i*u-1.0)*T\r\n    term2 = lambda u: i*u*rho*sigma*eta/lambd*(i*u-1.0)*(T+1.0/lambd \\\r\n                    *(np.exp(-lambd*T)-1.0))\r\n    term3 = lambda u: eta*eta/(4.0*np.power(lambd,3.0))*np.power(i+u,2.0)*\\\r\n            (3.0+np.exp(-2.0*lambd*T)-4.0*np.exp(-lambd*T)-2.0*lambd*T)\r\n    term4 = lambda u:  lambd*integrate.trapz(theta(T-zGrid)*C(u,zGrid), zGrid)\r\n    A= lambda u: term1(u) + term2(u) + term3(u) + term4(u)\r\n    \r\n    # Note that we don't include B(u)*x0 term as it is included in the COS method\r\n    cf = lambda u : np.exp(A(u) + C(u,T)*r0 )\r\n    \r\n    # Iterate over all u and collect the ChF, iteration is necessary due to the integration over in term4\r\n    cfV = []\r\n    for ui in u:\r\n        cfV.append(cf(ui))\r\n    \r\n    return cfV\r\n\r\ndef BSHWVolatility(T,eta,sigma,rho,lambd):\r\n    Br= lambda t,T: 1/lambd * (np.exp(-lambd*(T-t))-1.0)\r\n    sigmaF = lambda t: np.sqrt(sigma * sigma + eta * eta * Br(t,T) * Br(t,T)\\\r\n                               - 2.0 * rho * sigma * eta * Br(t,T)) \r\n    zGrid = np.linspace(0.0,T,2500)\r\n    sigmaC = np.sqrt(1/T*integrate.trapz(sigmaF(zGrid)*sigmaF(zGrid), zGrid))\r\n    return sigmaC\r\n\r\ndef BSHWOptionPrice(CP,S0,K,P0T,T,eta,sigma,rho,lambd):\r\n    \r\n    frwdS0 = S0 / P0T\r\n    vol = BSHWVolatility(T,eta,sigma,rho,lambd)\r\n    # As we deal with the forward prices we evaluate Black's 76 prices\r\n    r = 0.0\r\n    BlackPrice = BS_Call_Option_Price(CP,frwdS0,K,vol,T,r)\r\n    return  P0T * BlackPrice\r\n\r\ndef mainCalculation():\r\n    CP  = OptionType.CALL\r\n        \r\n    K = np.linspace(40.0,220.0,100)\r\n    K = np.array(K).reshape([len(K),1])\r\n    \r\n    # HW model settings\r\n    lambd = 0.1\r\n    eta   = 0.05\r\n    sigma = 0.2\r\n    rho   = 0.3\r\n    S0    = 100\r\n    \r\n    T = 5.0\r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.05*T) \r\n    \r\n    N = 500\r\n    L = 8\r\n    \r\n    # Characteristic function of the BSHW model + the COS method\r\n    cf = lambda u: ChFBSHW(u, T, P0T, lambd, eta, rho, sigma)\r\n    valCOS = CallPutOptionPriceCOSMthd_StochIR(cf, CP, S0, T, K, N, L,P0T(T))\r\n    exactBSHW = BSHWOptionPrice(CP,S0,K,P0T(T),T,eta,sigma,rho,lambd)\r\n    \r\n    IV = np.zeros([len(K),1])\r\n    for idx in range(0,len(K)):\r\n        frwdStock = S0 / P0T(T)\r\n        valCOSFrwd = valCOS[idx] / P0T(T)\r\n        IV[idx] = ImpliedVolatilityBlack76(CP,valCOSFrwd, K[idx], T, frwdStock)\r\n    \r\n    IVExact = BSHWVolatility(T,eta,sigma,rho,lambd)\r\n    \r\n    print(IVExact)\r\n    # Plot option prices\r\n    plt.figure(1)\r\n    plt.plot(K,valCOS)\r\n    plt.plot(K,exactBSHW,'--r')\r\n    plt.grid()\r\n    plt.xlabel(\"strike\")\r\n    plt.ylabel(\"option price\")\r\n    plt.legend([\"BSHW, COS method\",\"BSHW, exact solution\"])\r\n    \r\n    # Plot implied volatilities\r\n    plt.figure(2)\r\n    plt.plot(K,IV*100.0)\r\n    plt.plot(K,np.ones([len(K),1])*IVExact*100.0,'--r')\r\n    plt.grid()\r\n    plt.xlabel(\"strike\")\r\n    plt.ylabel(\"Implied Volatility [%]\")\r\n    plt.legend([\"BSHW, COS method\",\"BSHW, exact solution\"])\r\n    plt.axis([np.min(K),np.max(K),0,100])\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/BSHW_ImpliedVolatility.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nThe BSHW model and implied volatilities term structure computerion\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\nimport scipy.stats as st\r\nimport enum \r\nimport scipy.optimize as optimize\r\n\r\n# set i= imaginary number\r\ni   = np.complex(0.0,1.0)\r\n\r\n# time-step needed for differentiation\r\ndt = 0.0001\r\n \r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\n# Black-Scholes Call option price\r\ndef BS_Call_Option_Price(CP,S_0,K,sigma,tau,r):\r\n    if K is list:\r\n        K = np.array(K).reshape([len(K),1])\r\n    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) \r\n    * tau) / float(sigma * np.sqrt(tau))\r\n    d2    = d1 - sigma * np.sqrt(tau)\r\n    if CP == OptionType.CALL:\r\n        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)\r\n    elif CP == OptionType.PUT:\r\n        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0\r\n    return value\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76(CP,frwdMarketPrice,K,T,frwdStock):\r\n    func = lambda sigma: np.power(BS_Call_Option_Price(CP,frwdStock,K,sigma,T,0.0) - frwdMarketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, 0.2, tol=1e-9)\r\n    #impliedVol = optimize.brent(func, brack= (0.05, 2))\r\n    return impliedVol\r\n\r\n\r\ndef BSHWVolatility(T,eta,sigma,rho,lambd):\r\n    Br= lambda t,T: 1/lambd * (np.exp(-lambd*(T-t))-1.0)\r\n    sigmaF = lambda t: np.sqrt(sigma * sigma + eta * eta * Br(t,T) * Br(t,T)\\\r\n                               - 2.0 * rho * sigma * eta * Br(t,T)) \r\n    zGrid = np.linspace(0.0,T,2500)\r\n    sigmaC = np.sqrt(1/T*integrate.trapz(sigmaF(zGrid)*sigmaF(zGrid), zGrid))\r\n    return sigmaC\r\n\r\ndef BSHWOptionPrice(CP,S0,K,P0T,T,eta,sigma,rho,lambd):\r\n    \r\n    frwdS0 = S0 / P0T\r\n    vol = BSHWVolatility(T,eta,sigma,rho,lambd)\r\n    # As we deal with the forward prices we evaluate Black's 76 prices\r\n    r = 0.0\r\n    BlackPrice = BS_Call_Option_Price(CP,frwdS0,K,vol,T,r)\r\n    return  P0T * BlackPrice\r\n\r\ndef mainCalculation():\r\n    CP  = OptionType.CALL  \r\n        \r\n    # HW model settings\r\n    lambd = 0.1\r\n    eta   = 0.01\r\n    sigma = 0.2\r\n    rho   = 0.3\r\n    S0    = 100\r\n    \r\n    # Strike equals stock value, thus ATM\r\n    K = [100]\r\n    K = np.array(K).reshape([len(K),1])\r\n      \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.05*T) \r\n       \r\n    # Maturitires at which we compute implied volatility\r\n    TMat = np.linspace(0.1,5.0,20)\r\n        \r\n    # Effect of lambda\r\n    plt.figure(2)\r\n    plt.grid()\r\n    plt.xlabel('maturity, T')\r\n    plt.ylabel('implied volatility')\r\n    lambdV = [0.001,0.1,0.5,1.5]\r\n    legend = []\r\n    for lambdaTemp in lambdV:    \r\n       IV =np.zeros([len(TMat),1])\r\n       for idx in range(0,len(TMat)):\r\n           T = TMat[idx]\r\n           val = BSHWOptionPrice(CP,S0,K,P0T(T),T,eta,sigma,rho,lambdaTemp)\r\n           frwdStock = S0 / P0T(T)\r\n           valFrwd = val / P0T(T)\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K,T,frwdStock)\r\n       plt.plot(TMat,IV*100.0)       \r\n       legend.append('lambda={0}'.format(lambdaTemp))\r\n    plt.legend(legend)    \r\n\r\n    # Effect of eta\r\n    plt.figure(3)\r\n    plt.grid()\r\n    plt.xlabel('maturity, T')\r\n    plt.ylabel('implied volatility')\r\n    etaV = [0.001,0.05,0.1,0.15]\r\n    legend = []\r\n    for etaTemp in etaV:    \r\n       IV =np.zeros([len(TMat),1])\r\n       for idx in range(0,len(TMat)):\r\n           T = TMat[idx]\r\n           val = BSHWOptionPrice(CP,S0,K,P0T(T),T,etaTemp,sigma,rho,lambd)\r\n           frwdStock = S0 / P0T(T)\r\n           valFrwd = val/P0T(T)\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K,T,frwdStock)\r\n       plt.plot(TMat,IV*100.0)       \r\n       legend.append('eta={0}'.format(etaTemp))\r\n    plt.legend(legend)    \r\n    \r\n    # Effect of sigma\r\n    plt.figure(4)\r\n    plt.grid()\r\n    plt.xlabel('maturity, T')\r\n    plt.ylabel('implied volatility')\r\n    sigmaV = [0.1,0.2,0.3,0.4]\r\n    legend = []\r\n    for sigmaTemp in sigmaV:    \r\n       IV =np.zeros([len(TMat),1])\r\n       for idx in range(0,len(TMat)):\r\n           T = TMat[idx]\r\n           val = BSHWOptionPrice(CP,S0,K,P0T(T),T,eta,sigmaTemp,rho,lambd)\r\n           frwdStock = S0 / P0T(T)\r\n           valFrwd = val / P0T(T)\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K,T,frwdStock)\r\n       plt.plot(TMat,IV*100.0)       \r\n       legend.append('sigma={0}'.format(sigmaTemp))\r\n    plt.legend(legend)    \r\n\r\n    # Effect of rho\r\n    plt.figure(5)\r\n    plt.grid()\r\n    plt.xlabel('maturity, T')\r\n    plt.ylabel('implied volatility')\r\n    rhoV = [-0.7, -0.3, 0.3, 0.7]\r\n    legend = []\r\n    for rhoTemp in rhoV:    \r\n       IV =np.zeros([len(TMat),1])\r\n       for idx in range(0,len(TMat)):\r\n           T = TMat[idx]\r\n           val = BSHWOptionPrice(CP,S0,K,P0T(T),T,eta,sigma,rhoTemp,lambd)\r\n           frwdStock = S0 / P0T(T)\r\n           valFrwd = val / P0T(T)\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valFrwd,K,T,frwdStock)\r\n       plt.plot(TMat,IV*100.0)       \r\n       legend.append('rho={0}'.format(rhoTemp))\r\n    plt.legend(legend) \r\n\r\nmainCalculation()"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/H1_HW_COS_vs_MC.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nThe Heston-Hull-White model and pricing European Options with Monte Carlo and the COS method\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.special as sp\r\nimport scipy.integrate as integrate\r\nimport enum \r\n\r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\ndef CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):\r\n    # cf   - characteristic function as a functon, in the book denoted as \\varphi\r\n    # CP   - C for call and P for put\r\n    # S0   - Initial stock price\r\n    # tau  - time to maturity\r\n    # K    - list of strikes\r\n    # N    - Number of expansion terms\r\n    # L    - size of truncation domain (typ.:L=8 or L=10)  \r\n    # P0T  - zero-coupon bond for maturity T.\r\n        \r\n    # reshape K to a column vector\r\n    if K is not np.array:\r\n        K = np.array(K).reshape([len(K),1])\r\n    \r\n    #assigning i=sqrt(-1)\r\n    i = np.complex(0.0,1.0) \r\n    x0 = np.log(S0 / K)   \r\n    \r\n    # truncation domain\r\n    a = 0.0 - L * np.sqrt(tau)\r\n    b = 0.0 + L * np.sqrt(tau)\r\n    \r\n    # sumation from k = 0 to k=N-1\r\n    k = np.linspace(0,N-1,N).reshape([N,1])  \r\n    u = k * np.pi / (b - a)  \r\n\r\n    # Determine coefficients for Put Prices  \r\n    H_k = CallPutCoefficients(OptionType.PUT,a,b,k)   \r\n    mat = np.exp(i * np.outer((x0 - a) , u))\r\n    temp = cf(u) * H_k \r\n    temp[0] = 0.5 * temp[0]    \r\n    value = K * np.real(mat.dot(temp))     \r\n    \r\n    # we use call-put parity for call options\r\n    if CP == OptionType.CALL:\r\n        value = value + S0 - K * P0T\r\n        \r\n    return value\r\n\r\n# Determine coefficients for Put Prices \r\ndef CallPutCoefficients(CP,a,b,k):\r\n    if CP==OptionType.CALL:                  \r\n        c = 0.0\r\n        d = b\r\n        coef = Chi_Psi(a,b,c,d,k)\r\n        Chi_k = coef[\"chi\"]\r\n        Psi_k = coef[\"psi\"]\r\n        if a < b and b < 0.0:\r\n            H_k = np.zeros([len(k),1])\r\n        else:\r\n            H_k      = 2.0 / (b - a) * (Chi_k - Psi_k)  \r\n    elif CP==OptionType.PUT:\r\n        c = a\r\n        d = 0.0\r\n        coef = Chi_Psi(a,b,c,d,k)\r\n        Chi_k = coef[\"chi\"]\r\n        Psi_k = coef[\"psi\"]\r\n        H_k      = 2.0 / (b - a) * (- Chi_k + Psi_k)               \r\n    \r\n    return H_k    \r\n\r\ndef Chi_Psi(a,b,c,d,k):\r\n    psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a))\r\n    psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi)\r\n    psi[0] = d - c\r\n    \r\n    chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) \r\n    expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d)  - np.cos(k * np.pi \r\n                  * (c - a) / (b - a)) * np.exp(c)\r\n    expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * \r\n                        (d - a) / (b - a))   - k * np.pi / (b - a) * np.sin(k \r\n                        * np.pi * (c - a) / (b - a)) * np.exp(c)\r\n    chi = chi * (expr1 + expr2)\r\n    \r\n    value = {\"chi\":chi,\"psi\":psi }\r\n    return value\r\n\r\ndef EUOptionPriceFromMCPathsGeneralizedStochIR(CP,S,K,T,M):\r\n    # S is a vector of Monte Carlo samples at T\r\n    result = np.zeros([len(K),1])\r\n    if CP == OptionType.CALL:\r\n        for (idx,k) in enumerate(K):\r\n            result[idx] = np.mean(1.0/M*np.maximum(S-k,0.0))\r\n    elif CP == OptionType.PUT:\r\n        for (idx,k) in enumerate(K):\r\n            result[idx] = np.mean(1.0/M*np.maximum(k-S,0.0))\r\n    return result\r\n\r\n\r\ndef CIR_Sample(NoOfPaths,kappa,gamma,vbar,s,t,v_s):\r\n    delta = 4.0 *kappa*vbar/gamma/gamma\r\n    c= 1.0/(4.0*kappa)*gamma*gamma*(1.0-np.exp(-kappa*(t-s)))\r\n    kappaBar = 4.0*kappa*v_s*np.exp(-kappa*(t-s))/(gamma*gamma*(1.0-np.exp(-kappa*(t-s))))\r\n    sample = c* np.random.noncentral_chisquare(delta,kappaBar,NoOfPaths)\r\n    return  sample\r\n\r\ndef GeneratePathsHestonHW_AES(NoOfPaths,NoOfSteps,P0T,T,S_0,kappa,gamma,rhoxr,rhoxv,vbar,v0,lambd,eta):    \r\n # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))\r\n    \r\n    Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    Z2 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    Z3 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W1 = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    W2 = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    W3 = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    V = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    X = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M_t = np.ones([NoOfPaths,NoOfSteps+1])\r\n    R[:,0]=r0\r\n    V[:,0]=v0\r\n    X[:,0]=np.log(S_0)\r\n    \r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i])\r\n            Z2[:,i] = (Z2[:,i] - np.mean(Z2[:,i])) / np.std(Z2[:,i])\r\n            Z3[:,i] = (Z3[:,i] - np.mean(Z3[:,i])) / np.std(Z3[:,i])\r\n        \r\n        W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i]\r\n        W2[:,i+1] = W2[:,i] + np.power(dt, 0.5)*Z2[:,i]\r\n        W3[:,i+1] = W3[:,i] + np.power(dt, 0.5)*Z3[:,i]\r\n        \r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W1[:,i+1]-W1[:,i])\r\n        M_t[:,i+1] = M_t[:,i] * np.exp(0.5*(R[:,i+1] + R[:,i])*dt)\r\n        \r\n        # Exact samles for the variance process\r\n        V[:,i+1] = CIR_Sample(NoOfPaths,kappa,gamma,vbar,0,dt,V[:,i])\r\n         \r\n        k0 = -rhoxv /gamma * kappa*vbar*dt\r\n        k2 = rhoxv/gamma\r\n        k1 = kappa*k2 -0.5\r\n        k3 = np.sqrt(1.0-rhoxr*rhoxr - rhoxv*rhoxv)\r\n        \r\n        X[:,i+1] = X[:,i] + k0 + (k1*dt - k2)*V[:,i] + R[:,i]*dt + k2*V[:,i+1]+\\\r\n                    + np.sqrt(V[:,i]*dt)*(rhoxr*Z1[:,i] + k3 * Z3[:,i])\r\n        \r\n        # Moment matching component, i.e.: ensure that E(S(T)/M(T))= S0\r\n        a = S_0 / np.mean(np.exp(X[:,i+1])/M_t[:,i+1])\r\n        X[:,i+1] = X[:,i+1] + np.log(a)\r\n        time[i+1] = time[i] +dt\r\n        \r\n    #Compute exponent\r\n    S = np.exp(X)\r\n    paths = {\"time\":time,\"S\":S,\"R\":R,\"M_t\":M_t}\r\n    return paths\r\n\r\ndef GeneratePathsHestonHWEuler(NoOfPaths,NoOfSteps,P0T,T,S_0,kappa,gamma,rhoxr,rhoxv,vbar,v0,lambd,eta):    \r\n # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))\r\n    \r\n    Z1 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    Z2 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    Z3 = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W1 = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    W2 = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    W3 = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    V = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    X = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M_t = np.ones([NoOfPaths,NoOfSteps+1])\r\n    R[:,0]=r0\r\n    V[:,0]=v0\r\n    X[:,0]=np.log(S_0)\r\n    \r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z1[:,i] = (Z1[:,i] - np.mean(Z1[:,i])) / np.std(Z1[:,i])\r\n            Z2[:,i] = (Z2[:,i] - np.mean(Z2[:,i])) / np.std(Z2[:,i])\r\n            Z3[:,i] = (Z3[:,i] - np.mean(Z3[:,i])) / np.std(Z3[:,i])\r\n        \r\n        W1[:,i+1] = W1[:,i] + np.power(dt, 0.5)*Z1[:,i]\r\n        W2[:,i+1] = W2[:,i] + np.power(dt, 0.5)*Z2[:,i]\r\n        W3[:,i+1] = W3[:,i] + np.power(dt, 0.5)*Z3[:,i]\r\n        \r\n        # Truncated boundary condition\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W1[:,i+1]-W1[:,i])\r\n        M_t[:,i+1] = M_t[:,i] * np.exp(0.5*(R[:,i+1] + R[:,i])*dt)\r\n        V[:,i+1] = V[:,i] + kappa*(vbar - V[:,i]) * dt + gamma* np.sqrt(V[:,i]) * (W2[:,i+1]-W2[:,i])\r\n        V[:,i+1] = np.maximum(V[:,i+1],0.0)\r\n          \r\n        term1 = rhoxr * (W1[:,i+1]-W1[:,i]) + rhoxv * (W2[:,i+1]-W2[:,i]) \\\r\n                + np.sqrt(1.0-rhoxr*rhoxr-rhoxv*rhoxv)* (W3[:,i+1]-W3[:,i])\r\n                \r\n        X[:,i+1] = X[:,i] + (R[:,i] - 0.5*V[:,i])*dt + np.sqrt(V[:,i])*term1\r\n        time[i+1] = time[i] +dt            \r\n        \r\n        # Moment matching component, i.e.: ensure that E(S(T)/M(T))= S0\r\n        a = S_0 / np.mean(np.exp(X[:,i+1])/M_t[:,i+1])\r\n        X[:,i+1] = X[:,i+1] + np.log(a)\r\n        \r\n    #Compute exponent\r\n    S = np.exp(X)\r\n    paths = {\"time\":time,\"S\":S,\"R\":R,\"M_t\":M_t}\r\n    return paths\r\n\r\n# Exact expectation E(sqrt(V(t)))\r\ndef meanSqrtV_3(kappa,v0,vbar,gamma):\r\n    delta = 4.0 *kappa*vbar/gamma/gamma\r\n    c= lambda t: 1.0/(4.0*kappa)*gamma*gamma*(1.0-np.exp(-kappa*(t)))\r\n    kappaBar = lambda t: 4.0*kappa*v0*np.exp(-kappa*t)/(gamma*gamma*(1.0-np.exp(-kappa*t)))\r\n    temp1 = lambda t: np.sqrt(2.0*c(t))* sp.gamma((1.0+delta)/2.0)/sp.gamma(delta/2.0)*sp.hyp1f1(-0.5,delta/2.0,-kappaBar(t)/2.0)\r\n    return temp1\r\n\r\ndef C_H1HW(u,tau,lambd):\r\n    i = np.complex(0.0,1.0)\r\n    C = (i*u - 1.0)/lambd * (1-np.exp(-lambd*tau))\r\n    return C\r\n\r\ndef D_H1HW(u,tau,kappa,gamma,rhoxv):\r\n    i = np.complex(0.0,1.0)\r\n    \r\n    D1 = np.sqrt(np.power(kappa-gamma*rhoxv*i*u,2)+(u*u+i*u)*gamma*gamma)\r\n    g  = (kappa-gamma*rhoxv*i*u-D1)/(kappa-gamma*rhoxv*i*u+D1)\r\n    C  = (1.0-np.exp(-D1*tau))/(gamma*gamma*(1.0-g*np.exp(-D1*tau)))\\\r\n        *(kappa-gamma*rhoxv*i*u-D1)\r\n    return C\r\n    \r\ndef A_H1HW(u,tau,P0T,lambd,eta,kappa,gamma,vbar,v0,rhoxv,rhoxr):\r\n    i  = np.complex(0.0,1.0)\r\n    D1 = np.sqrt(np.power(kappa-gamma*rhoxv*i*u,2)+(u*u+i*u)*gamma*gamma)\r\n    g  = (kappa-gamma*rhoxv*i*u-D1)/(kappa-gamma*rhoxv*i*u+D1)\r\n    \r\n    # function theta(t)\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)\r\n    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))  \r\n\r\n    # Integration in the function I_1\r\n    N  = 500\r\n    z  = np.linspace(0,tau-1e-10,N)\r\n    f1 = (1.0-np.exp(-lambd*z))*theta(tau-z)\r\n    value1 = integrate.trapz(f1,z)\r\n    \r\n    # Note that I_1_adj also allows for theta to be time-dependent\r\n    # therefore it is not exactly the same as given in the book\r\n    I_1_adj = (i*u-1.0) * value1\r\n    I_2     = tau/(gamma**2.0) *(kappa-gamma*rhoxv*i*u-D1) - 2.0/(gamma**2.0)*np.log((1.0-g*np.exp(-D1*tau))/(1.0-g))\r\n    I_3     = 1.0/(2.0*np.power(lambd,3.0))* np.power(i+u,2.0)*(3.0+np.exp(-2.0*lambd*tau)-4.0*np.exp(-lambd*tau)-2.0*lambd*tau)\r\n    \r\n    meanSqrtV = meanSqrtV_3(kappa,v0,vbar,gamma)\r\n    f2        = meanSqrtV(tau-z)*(1.0-np.exp(-lambd*z))\r\n    value2    = integrate.trapz(f2,z)\r\n    I_4       = -1.0/lambd * (i*u+u**2.0)*value2\r\n    \r\n    return I_1_adj + kappa*vbar*I_2 + 0.5*eta**2.0*I_3+eta*rhoxr*I_4\r\n\r\ndef ChFH1HWModel(P0T,lambd,eta,tau,kappa,gamma,vbar,v0,rhoxv, rhoxr):\r\n    # Determine initial interest rate r(0)\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)\r\n    r0 =f0T(0.00001)\r\n    C = lambda u: C_H1HW(u,tau,lambd)\r\n    D = lambda u: D_H1HW(u,tau,kappa,gamma,rhoxv)\r\n    A = lambda u: A_H1HW(u,tau,P0T,lambd,eta,kappa,gamma,vbar,v0,rhoxv,rhoxr)\r\n    cf = lambda u: np.exp(A(u) + C(u)*r0 + D(u)*v0 )\r\n    return cf\r\n\r\ndef mainCalculation():\r\n    CP  = OptionType.CALL  \r\n    \r\n    NoOfPaths = 10000\r\n    NoOfSteps = 500\r\n    \r\n    # HW model settings\r\n    lambd = 1.12\r\n    eta   = 0.01\r\n    S0    = 100.0\r\n    T     = 15.0       \r\n    r     = 0.1\r\n   \r\n    # Strike range\r\n    K = np.linspace(.01,1.8*S0*np.exp(r*T),20)\r\n    K = np.array(K).reshape([len(K),1])\r\n      \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-r*T) \r\n    \r\n    # Settings for the COS method\r\n    N = 2000\r\n    L = 15  \r\n    \r\n    gamma = 0.3\r\n    vbar  = 0.05\r\n    v0    = 0.02\r\n    rhoxr = 0.5\r\n    rhoxv =-0.8\r\n    kappa = 0.5\r\n    \r\n    np.random.seed(1)\r\n    paths = GeneratePathsHestonHWEuler(NoOfPaths,NoOfSteps,P0T,T,S0,kappa,gamma,rhoxr,rhoxv,vbar,v0,lambd,eta)\r\n    S = paths[\"S\"]\r\n    M_t = paths[\"M_t\"]\r\n    \r\n    print(np.mean(S[:,-1]/M_t[:,-1]))\r\n    valueOptMC= EUOptionPriceFromMCPathsGeneralizedStochIR(CP,S[:,-1],K,T,M_t[:,-1])\r\n    \r\n    np.random.seed(1)\r\n    pathsExact = GeneratePathsHestonHW_AES(NoOfPaths,NoOfSteps,P0T,T,S0,kappa,gamma,rhoxr,rhoxv,vbar,v0,lambd,eta)\r\n    S_ex = pathsExact[\"S\"]\r\n    M_t_ex = pathsExact[\"M_t\"]\r\n    valueOptMC_ex= EUOptionPriceFromMCPathsGeneralizedStochIR(CP,S_ex[:,-1],K,T,M_t_ex[:,-1])\r\n    \r\n    print(np.mean(S_ex[:,-1]/M_t_ex[:,-1]))\r\n    \r\n    plt.figure(1)\r\n    plt.plot(K,valueOptMC)\r\n    plt.plot(K,valueOptMC_ex,'.k')\r\n    plt.ylim([0.0,110.0])\r\n        \r\n    # The COS method\r\n    cf2 = ChFH1HWModel(P0T,lambd,eta,T,kappa,gamma,vbar,v0,rhoxv, rhoxr)\r\n    u=np.array([1.0,2.0,3.0])\r\n    print(cf2(u))\r\n    valCOS = CallPutOptionPriceCOSMthd_StochIR(cf2, CP, S0, T, K, N, L,P0T(T))\r\n    plt.plot(K,valCOS,'--r')\r\n    plt.legend(['Euler','AES','COS'])\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('EU Option Value, K')\r\n    print(\"Value from the COS method:\")\r\n    print(valCOS)\r\n \r\nmainCalculation()"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/SZHW_ImpliedVolatilities.py",
    "content": "#%%\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 from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\nimport scipy.stats as st\r\nimport enum \r\nimport scipy.optimize as optimize\r\n\r\n# set i= imaginary number\r\ni   = np.complex(0.0,1.0)\r\n\r\n# time-step needed for differentiation\r\ndt = 0.0001\r\n \r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n    \r\n\r\ndef CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):\r\n    # cf   - characteristic function as a functon, in the book denoted as \\varphi\r\n    # CP   - C for call and P for put\r\n    # S0   - Initial stock price\r\n    # tau  - time to maturity\r\n    # K    - list of strikes\r\n    # N    - Number of expansion terms\r\n    # L    - size of truncation domain (typ.:L=8 or L=10)  \r\n    # P0T  - zero-coupon bond for maturity T.\r\n        \r\n    # reshape K to a column vector\r\n    if K is not np.array:\r\n        K = np.array(K).reshape([len(K),1])\r\n    \r\n    #assigning i=sqrt(-1)\r\n    i = np.complex(0.0,1.0) \r\n    x0 = np.log(S0 / K)   \r\n    \r\n    # truncation domain\r\n    a = 0.0 - L * np.sqrt(tau)\r\n    b = 0.0 + L * np.sqrt(tau)\r\n    \r\n    # sumation from k = 0 to k=N-1\r\n    k = np.linspace(0,N-1,N).reshape([N,1])  \r\n    u = k * np.pi / (b - a)  \r\n\r\n    # Determine coefficients for Put Prices  \r\n    H_k = CallPutCoefficients(OptionType.PUT,a,b,k)   \r\n    mat = np.exp(i * np.outer((x0 - a) , u))\r\n    temp = cf(u) * H_k \r\n    temp[0] = 0.5 * temp[0]    \r\n    value = K * np.real(mat.dot(temp))     \r\n    \r\n    # we use call-put parity for call options\r\n    if CP == OptionType.CALL:\r\n        value = value + S0 - K * P0T\r\n        \r\n    return value\r\n\r\n# Determine coefficients for Put Prices \r\ndef CallPutCoefficients(CP,a,b,k):\r\n    if CP==OptionType.CALL:                  \r\n        c = 0.0\r\n        d = b\r\n        coef = Chi_Psi(a,b,c,d,k)\r\n        Chi_k = coef[\"chi\"]\r\n        Psi_k = coef[\"psi\"]\r\n        if a < b and b < 0.0:\r\n            H_k = np.zeros([len(k),1])\r\n        else:\r\n            H_k      = 2.0 / (b - a) * (Chi_k - Psi_k)  \r\n    elif CP==OptionType.PUT:\r\n        c = a\r\n        d = 0.0\r\n        coef = Chi_Psi(a,b,c,d,k)\r\n        Chi_k = coef[\"chi\"]\r\n        Psi_k = coef[\"psi\"]\r\n        H_k      = 2.0 / (b - a) * (- Chi_k + Psi_k)               \r\n    \r\n    return H_k    \r\n\r\ndef Chi_Psi(a,b,c,d,k):\r\n    psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a))\r\n    psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi)\r\n    psi[0] = d - c\r\n    \r\n    chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) \r\n    expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d)  - np.cos(k * np.pi \r\n                  * (c - a) / (b - a)) * np.exp(c)\r\n    expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * \r\n                        (d - a) / (b - a))   - k * np.pi / (b - a) * np.sin(k \r\n                        * np.pi * (c - a) / (b - a)) * np.exp(c)\r\n    chi = chi * (expr1 + expr2)\r\n    \r\n    value = {\"chi\":chi,\"psi\":psi }\r\n    return value\r\n    \r\n# Black-Scholes Call option price\r\ndef BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):\r\n    if K is list:\r\n        K = np.array(K).reshape([len(K),1])\r\n    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) \r\n    * tau) / (sigma * np.sqrt(tau))\r\n    d2    = d1 - sigma * np.sqrt(tau)\r\n    if CP == OptionType.CALL:\r\n        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)\r\n    elif CP == OptionType.PUT:\r\n        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0\r\n    return value\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):\r\n    # To determine initial volatility we interpolate define a grid for sigma\r\n    # and interpolate on the inverse\r\n    sigmaGrid = np.linspace(0.0,5.0,5000)\r\n    optPriceGrid = BS_Call_Put_Option_Price(CP,S_0,K,sigmaGrid,T,0.0)\r\n    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)\r\n    print(\"Strike = {0}\".format(K))\r\n    print(\"Initial volatility = {0}\".format(sigmaInitial))\r\n    \r\n    # Use determined input for the local-search (final tuning)\r\n    func = lambda sigma: np.power(BS_Call_Put_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-11)\r\n    print(\"Final volatility = {0}\".format(impliedVol))\r\n    if impliedVol > 2.0:\r\n        impliedVol = 0.0\r\n    return impliedVol\r\n\r\ndef C(u,tau,lambd):\r\n    i     = complex(0,1)\r\n    return 1.0/lambd*(i*u-1.0)*(1.0-np.exp(-lambd*tau))\r\n\r\ndef D(u,tau,kappa,Rxsigma,gamma):\r\n    i=np.complex(0.0,1.0)\r\n    a_0=-1.0/2.0*u*(i+u)\r\n    a_1=2.0*(gamma*Rxsigma*i*u-kappa)\r\n    a_2=2.0*gamma*gamma\r\n    d=np.sqrt(a_1*a_1-4.0*a_0*a_2)\r\n    g=(-a_1-d)/(-a_1+d)    \r\n    return (-a_1-d)/(2.0*a_2*(1.0-g*np.exp(-d*tau)))*(1.0-np.exp(-d*tau))\r\n    \r\ndef E(u,tau,lambd,gamma,Rxsigma,Rrsigma,Rxr,eta,kappa,sigmabar):\r\n    i=np.complex(0.0,1.0)\r\n    a_0=-1.0/2.0*u*(i+u)\r\n    a_1=2.0*(gamma*Rxsigma*i*u-kappa)\r\n    a_2=2*gamma*gamma\r\n    d  =np.sqrt(a_1*a_1-4.0*a_0*a_2)\r\n    g =(-a_1-d)/(-a_1+d)    \r\n    \r\n    c_1=gamma*Rxsigma*i*u-kappa-1.0/2.0*(a_1+d)\r\n    f_1=1.0/c_1*(1.0-np.exp(-c_1*tau))+1.0/(c_1+d)*(np.exp(-(c_1+d)*tau)-1.0)\r\n    f_2=1.0/c_1*(1.0-np.exp(-c_1*tau))+1.0/(c_1+lambd)*(np.exp(-(c_1+lambd)*tau)-1.0)\r\n    f_3=(np.exp(-(c_1+d)*tau)-1.0)/(c_1+d)+(1.0-np.exp(-(c_1+d+lambd)*tau))/(c_1+d+lambd)\r\n    f_4=1.0/c_1-1.0/(c_1+d)-1.0/(c_1+lambd)+1.0/(c_1+d+lambd)\r\n    f_5=np.exp(-(c_1+d+lambd)*tau)*(np.exp(lambd*tau)*(1.0/(c_1+d)-np.exp(d*tau)/c_1)+np.exp(d*tau)/(c_1+lambd)-1.0/(c_1+d+lambd)) \r\n\r\n    I_1=kappa*sigmabar/a_2*(-a_1-d)*f_1\r\n    I_2=eta*Rxr*i*u*(i*u-1.0)/lambd*(f_2+g*f_3)\r\n    I_3=-Rrsigma*eta*gamma/(lambd*a_2)*(a_1+d)*(i*u-1)*(f_4+f_5)\r\n    return np.exp(c_1*tau)*1.0/(1.0-g*np.exp(-d*tau))*(I_1+I_2+I_3)\r\n\r\ndef A(u,tau,eta,lambd,Rxsigma,Rrsigma,Rxr,gamma,kappa,sigmabar):\r\n    i=np.complex(0.0,1.0)\r\n    a_0=-1.0/2.0*u*(i+u)\r\n    a_1=2.0*(gamma*Rxsigma*i*u-kappa)\r\n    a_2=2.0*gamma*gamma\r\n    d  =np.sqrt(a_1*a_1-4.0*a_0*a_2)\r\n    g =(-a_1-d)/(-a_1+d) \r\n    f_6=eta*eta/(4.0*np.power(lambd,3.0))*np.power(i+u,2.0)*(3.0+np.exp(-2.0*lambd*tau)-4.0*np.exp(-lambd*tau)-2.0*lambd*tau)\r\n    A_1=1.0/4.0*((-a_1-d)*tau-2.0*np.log((1-g*np.exp(-d*tau))/(1.0-g)))+f_6\r\n  \r\n    # Integration in the function A(u,tau)\r\n    value=np.zeros([len(u),1],dtype=np.complex_)   \r\n    N = 500\r\n    arg=np.linspace(0,tau,N)\r\n\r\n    for k in range(0,len(u)):\r\n       E_val=E(u[k],arg,lambd,gamma,Rxsigma,Rrsigma,Rxr,eta,kappa,sigmabar)\r\n       C_val=C(u[k],arg,lambd)\r\n       f=(kappa*sigmabar+1.0/2.0*gamma*gamma*E_val+gamma*eta*Rrsigma*C_val)*E_val\r\n       value1 =integrate.trapz(np.real(f),arg)\r\n       value2 =integrate.trapz(np.imag(f),arg)\r\n       value[k]=(value1 + value2*i)#np.complex(value1,value2)\r\n    \r\n    return value + A_1\r\n\r\ndef ChFSZHW(u,P0T,sigma0,tau,lambd,gamma,    Rxsigma,Rrsigma,Rxr,eta,kappa,sigmabar):\r\n    v_D = D(u,tau,kappa,Rxsigma,gamma)\r\n    v_E = E(u,tau,lambd,gamma,Rxsigma,Rrsigma,Rxr,eta,kappa,sigmabar)\r\n    v_A = A(u,tau,eta,lambd,Rxsigma,Rrsigma,Rxr,gamma,kappa,sigmabar)\r\n    \r\n    v_0 = sigma0*sigma0\r\n    \r\n    hlp = eta*eta/(2.0*lambd*lambd)*(tau+2.0/lambd*(np.exp(-lambd*tau)-1.0)-1.0/(2.0*lambd)*(np.exp(-2.0*lambd*tau)-1.0))\r\n      \r\n    correction = (i*u-1.0)*(np.log(1/P0T(tau))+hlp)\r\n          \r\n    cf = np.exp(v_0*v_D + sigma0*v_E + v_A + correction)\r\n    return cf.tolist()\r\n\r\ndef ChFBSHW(u, T, P0T, lambd, eta, rho, sigma):\r\n    i = np.complex(0.0,1.0)\r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    \r\n    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))      \r\n    C = lambda u,tau: 1.0/lambd*(i*u-1.0)*(1.0-np.exp(-lambd*tau))\r\n    \r\n    # define a grid for the numerical integration of function theta\r\n    zGrid = np.linspace(0.0,T,2500)\r\n    term1 = lambda u: 0.5*sigma*sigma *i*u*(i*u-1.0)*T\r\n    term2 = lambda u: i*u*rho*sigma*eta/lambd*(i*u-1.0)*(T+1.0/lambd *(np.exp(-lambd*T)-1.0))\r\n    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)\r\n    term4 = lambda u:  lambd*integrate.trapz(theta(T-zGrid)*C(u,zGrid), zGrid)\r\n    A= lambda u: term1(u) + term2(u) + term3(u) + term4(u)\r\n    \r\n    # Note that we don't include B(u)*x0 term as it is included in the COS method\r\n    cf = lambda u : np.exp(A(u) + C(u,T)*r0 )\r\n    \r\n    # Iterate over all u and collect the ChF, iteration is necessary due to the integration over in term4\r\n    cfV = []\r\n    for ui in u:\r\n        cfV.append(cf(ui))\r\n    \r\n    return cfV\r\n\r\ndef mainCalculation():\r\n    CP  = OptionType.CALL  \r\n        \r\n    # HW model settings\r\n    lambd = 0.425\r\n    eta   = 0.1\r\n    S0    = 100\r\n    T     = 5.0\r\n    \r\n    # The SZHW model\r\n    sigma0  = 0.1 \r\n    gamma   = 0.11 \r\n    Rrsigma = 0.32\r\n    Rxsigma = -0.42\r\n    Rxr     = 0.3\r\n    kappa   = 0.4\r\n    sigmabar= 0.05\r\n    \r\n    # Strike range\r\n    K = np.linspace(40,200.0,20)\r\n    K = np.array(K).reshape([len(K),1])\r\n      \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.025*T) \r\n\r\n    # Forward stock\r\n    frwdStock = S0 / P0T(T)\r\n    \r\n    # Settings for the COS method\r\n    N = 2000\r\n    L = 10   \r\n \r\n    # effect of gamma\r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('implied volatility')\r\n    gammaV = [0.1, 0.2, 0.3, 0.4]\r\n    legend = []\r\n    for gammaTemp in gammaV:    \r\n        # Evaluate the SZHW model\r\n       cf = lambda u: ChFSZHW(u,P0T,sigma0,T,lambd,gammaTemp,Rxsigma,Rrsigma,Rxr,eta,kappa,sigmabar)\r\n       #cf = lambda u: ChFBSHW(u, T, P0T, lambd, eta,  -0.7, 0.1)\r\n\r\n       # The COS method\r\n       valCOS = CallPutOptionPriceCOSMthd_StochIR(cf, CP, S0, T, K, N, L,P0T(T))\r\n       valCOSFrwd = valCOS/P0T(T)\r\n       # Implied volatilities\r\n       IV =np.zeros([len(K),1])\r\n       for idx in range(0,len(K)):\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valCOSFrwd[idx],K[idx],T,frwdStock)\r\n       plt.plot(K,IV*100.0)\r\n       legend.append('gamma={0}'.format(gammaTemp))\r\n    plt.legend(legend)\r\n   \r\n    \r\n    # effect of kappa\r\n    plt.figure(2)\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('implied volatility')\r\n    kappaV = [0.05, 0.2, 0.3, 0.4]\r\n    legend = []\r\n    for kappaTemp in kappaV:    \r\n        # Evaluate the SZHW model\r\n       cf = lambda u: ChFSZHW(u,P0T,sigma0,T,lambd,gamma,Rxsigma,Rrsigma,Rxr,eta,kappaTemp,sigmabar)\r\n       \r\n       # The COS method\r\n       valCOS = CallPutOptionPriceCOSMthd_StochIR(cf, CP, S0, T, K, N, L,P0T(T))\r\n       valCOSFrwd = valCOS/P0T(T)\r\n       # Implied volatilities\r\n       IV =np.zeros([len(K),1])\r\n       for idx in range(0,len(K)):\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valCOSFrwd[idx],K[idx],T,frwdStock)\r\n       plt.plot(K,IV*100.0)\r\n       legend.append('kappa={0}'.format(kappaTemp))\r\n    plt.legend(legend)    \r\n    \r\n   \r\n    # effect of rhoxsigma\r\n    plt.figure(3)\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('implied volatility')\r\n    RxsigmaV = [-0.75, -0.25, 0.25, 0.75]\r\n    legend = []\r\n    for RxsigmaTemp in RxsigmaV:    \r\n        # Evaluate the SZHW model\r\n       cf = lambda u: ChFSZHW(u,P0T,sigma0,T,lambd,gamma,RxsigmaTemp,Rrsigma,Rxr,eta,kappa,sigmabar)\r\n       \r\n       # The COS method\r\n       valCOS = CallPutOptionPriceCOSMthd_StochIR(cf, CP, S0, T, K, N, L,P0T(T))\r\n       valCOSFrwd = valCOS/P0T(T)\r\n       # Implied volatilities\r\n       IV =np.zeros([len(K),1])\r\n       for idx in range(0,len(K)):\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valCOSFrwd[idx],K[idx],T,frwdStock)\r\n       plt.plot(K,IV*100.0)\r\n       legend.append('Rxsigma={0}'.format(RxsigmaTemp))\r\n    plt.legend(legend)    \r\n    \r\n    #effect of sigmabar\r\n    plt.figure(4)\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('implied volatility')\r\n    sigmabarV = [0.1, 0.2, 0.3, 0.4]\r\n    legend = []\r\n    for sigmabarTemp in sigmabarV:    \r\n        # Evaluate the SZHW model\r\n       cf = lambda u: ChFSZHW(u,P0T,sigma0,T,lambd,gamma,Rxsigma,Rrsigma,Rxr,eta,kappa,sigmabarTemp)\r\n       \r\n       # The COS method\r\n       valCOS = CallPutOptionPriceCOSMthd_StochIR(cf, CP, S0, T, K, N, L,P0T(T))\r\n       valCOSFrwd = valCOS/P0T(T)\r\n       # Implied volatilities\r\n       IV =np.zeros([len(K),1])\r\n       for idx in range(0,len(K)):\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valCOSFrwd[idx],K[idx],T,frwdStock)\r\n       plt.plot(K,IV*100.0)\r\n       legend.append('sigmabar={0}'.format(sigmabarTemp))\r\n    plt.legend(legend)  \r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 09-Hybrid Models and Stochastic Interest Rates/Materials/SZHW_MonteCarlo_DiversificationProduct.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on July 05  2021\r\nThe SZHW model and pricing of diversification product\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\nimport enum \r\n\r\n# set i= imaginary number\r\ni   = np.complex(0.0,1.0)\r\n\r\n# time-step needed for differentiation\r\ndt = 0.0001\r\n \r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    return theta\r\n\r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    return np.exp(A_r + B_r *rT1)\r\n\r\ndef EUOptionPriceFromMCPathsGeneralizedStochIR(CP,S,K,T,M):\r\n    # S is a vector of Monte Carlo samples at T\r\n    result = np.zeros([len(K),1])\r\n    if CP == OptionType.CALL:\r\n        for (idx,k) in enumerate(K):\r\n            result[idx] = np.mean(1.0/M*np.maximum(S-k,0.0))\r\n    elif CP == OptionType.PUT:\r\n        for (idx,k) in enumerate(K):\r\n            result[idx] = np.mean(1.0/M*np.maximum(k-S,0.0))\r\n    return result\r\n\r\ndef GeneratePathsSZHWEuler(NoOfPaths,NoOfSteps,P0T,T,S0,sigma0,sigmabar,kappa,gamma,lambd,eta,Rxsigma,Rxr,Rsigmar):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    # Empty containers for Brownian motion\r\n    Wx = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    Wsigma = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    Wr = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    \r\n    Sigma = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    X     = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R     = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    M_t   = np.ones([NoOfPaths,NoOfSteps+1])\r\n    R[:,0]     = r0 \r\n    Sigma[:,0] = sigma0\r\n    X[:,0]     = np.log(S0)\r\n    \r\n    dt = T / float(NoOfSteps)    \r\n    cov = np.array([[1.0, Rxsigma,Rxr],[Rxsigma,1.0,Rsigmar], [Rxr,Rsigmar,1.0]])\r\n      \r\n    time = np.zeros([NoOfSteps+1])    \r\n\r\n    for i in range(0,NoOfSteps):\r\n        Z = np.random.multivariate_normal([.0,.0,.0],cov,NoOfPaths)\r\n        if NoOfPaths > 1:\r\n            Z[:,0] = (Z[:,0] - np.mean(Z[:,0])) / np.std(Z[:,0])\r\n            Z[:,1] = (Z[:,1] - np.mean(Z[:,1])) / np.std(Z[:,1])\r\n            Z[:,2] = (Z[:,2] - np.mean(Z[:,2])) / np.std(Z[:,2])\r\n            \r\n        Wx[:,i+1]     = Wx[:,i]     + np.power(dt, 0.5)*Z[:,0]\r\n        Wsigma[:,i+1] = Wsigma[:,i] + np.power(dt, 0.5)*Z[:,1]\r\n        Wr[:,i+1]     = Wr[:,i]     + np.power(dt, 0.5)*Z[:,2]\r\n\r\n        # Euler discretization\r\n        R[:,i+1]     = R[:,i] + lambd*(theta(time[i]) - R[:,i])*dt + eta * (Wr[:,i+1]-Wr[:,i])\r\n        M_t[:,i+1]   = M_t[:,i] * np.exp(0.5*(R[:,i+1] + R[:,i])*dt)\r\n        Sigma[:,i+1] = Sigma[:,i] + kappa*(sigmabar - Sigma[:,i])*dt + gamma* (Wsigma[:,i+1]-Wsigma[:,i])                        \r\n        X[:,i+1]     = X[:,i] + (R[:,i] - 0.5*Sigma[:,i]**2.0)*dt + Sigma[:,i] * (Wx[:,i+1]-Wx[:,i])                        \r\n        time[i+1] = time[i] +dt\r\n        \r\n        # Moment matching component, i.e.: ensure that E(S(T)/M(T))= S0\r\n        a = S0 / np.mean(np.exp(X[:,i+1])/M_t[:,i+1])\r\n        X[:,i+1] = X[:,i+1] + np.log(a)\r\n        \r\n    paths = {\"time\":time,\"S\":np.exp(X),\"M_t\":M_t,\"R\":R}\r\n    return paths\r\n\r\ndef DiversifcationPayoff(P0T,S_T,S0,r_T,M_T,T,T1,lambd,eta,omegaV):\r\n    P_T_T1= HW_ZCB(lambd,eta,P0T,T,T1,r_T)\r\n    P_0_T1= P0T(T1)\r\n    \r\n    value =np.zeros(omegaV.size)\r\n    for (idx,omega) in enumerate(omegaV):\r\n        payoff = omega * S_T/S0 + (1.0-omega) * P_T_T1/P_0_T1\r\n        value[idx] = np.mean(1/M_T*np.maximum(payoff,0.0))\r\n    return value\r\n\r\ndef mainCalculation():\r\n    # HW model settings\r\n    lambd = 1.12\r\n    eta   = 0.02\r\n    S0    = 100.0\r\n    \r\n    # Fixed mean reversion parameter\r\n    kappa   = 0.5\r\n    # Diversification product\r\n    T  = 9.0\r\n    T1 = 10.0\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.033*T) \r\n     \r\n    # Range of the waiting factor\r\n    omegaV=  np.linspace(-3.0,3.0,50)\r\n    # Monte Carlo setting\r\n    NoOfPaths =5000\r\n    NoOfSteps = int(100*T)\r\n    \r\n    # The SZHW model parameters\r\n    # The parameters can be obtained by running the calibration of the SZHW model and with\r\n    # varying the correlation rhoxr.\r\n  \r\n    parameters=[{\"Rxr\":0.0,\"sigmabar\":0.167,\"gamma\":0.2,\"Rxsigma\":-0.850,\"Rrsigma\":-0.008,\"kappa\":0.5,\"sigma0\":0.035},\r\n                {\"Rxr\":-0.7,\"sigmabar\":0.137,\"gamma\":0.236,\"Rxsigma\":-0.381,\"Rrsigma\":-0.339,\"kappa\":0.5,\"sigma0\":0.084},\r\n                {\"Rxr\":0.7,\"sigmabar\":0.102,\"gamma\":0.211,\"Rxsigma\":-0.850,\"Rrsigma\":-0.340,\"kappa\":0.5,\"sigma0\":0.01}]\r\n    \r\n    legend = []\r\n    for (idx,par) in enumerate(parameters):\r\n        sigma0  = par[\"sigma0\"]\r\n        gamma   = par[\"gamma\"]\r\n        Rrsigma = par[\"Rrsigma\"]\r\n        Rxsigma = par[\"Rxsigma\"]\r\n        Rxr     = par[\"Rxr\"]\r\n        sigmabar= par[\"sigmabar\"]\r\n    \r\n        # Generate MC paths\r\n        np.random.seed(1)\r\n        paths = GeneratePathsSZHWEuler(NoOfPaths,NoOfSteps,P0T,T,S0,sigma0,sigmabar,kappa,gamma,lambd,eta,Rxsigma,Rxr,Rrsigma)\r\n        S = paths[\"S\"]\r\n        M = paths[\"M_t\"]\r\n        R = paths[\"R\"]\r\n    \r\n        S_T= S[:,-1]\r\n        R_T= R[:,-1]\r\n        M_T= M[:,-1]\r\n        value_0 = DiversifcationPayoff(P0T,S_T,S0,R_T,M_T,T,T1,lambd,eta,omegaV)\r\n    \r\n        # reference with rho=0.0\r\n        if Rxr==0.0:\r\n            refR0 = value_0\r\n            \r\n        plt.figure(1)\r\n        plt.plot(omegaV,value_0)\r\n        legend.append('par={0}'.format(idx))\r\n        \r\n        plt.figure(2)\r\n        plt.plot(omegaV,value_0/refR0)\r\n    \r\n    plt.figure(1)\r\n    plt.grid()      \r\n    plt.legend(legend)\r\n    plt.figure(2)\r\n    plt.grid()      \r\n    plt.legend(legend)\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 10-Foreign Exchange (FX) and Inflation/Materials/H1_HW_COS_vs_MC_FX.py",
    "content": "#%%\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\nthe COS method and comparisons with the Monte Carlo simulation.\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.stats as st\r\nimport scipy.special as sp\r\nimport scipy.integrate as integrate\r\nimport scipy.optimize as optimize\r\nimport enum \r\n\r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\n# Black-Scholes Call option price\r\ndef BS_Call_Put_Option_Price(CP,S_0,K,sigma,tau,r):\r\n    if K is list:\r\n        K = np.array(K).reshape([len(K),1])\r\n    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) \r\n    * tau) / (sigma * np.sqrt(tau))\r\n    d2    = d1 - sigma * np.sqrt(tau)\r\n    if CP == OptionType.CALL:\r\n        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)\r\n    elif CP == OptionType.PUT:\r\n        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0\r\n    return value\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):\r\n    # To determine initial volatility we interpolate define a grid for sigma\r\n    # and interpolate on the inverse\r\n    sigmaGrid = np.linspace(0.0,5.0,5000)\r\n    optPriceGrid = BS_Call_Put_Option_Price(CP,S_0,K,sigmaGrid,T,0.0)\r\n    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)\r\n    print(\"Strike = {0}\".format(K))\r\n    print(\"Initial volatility = {0}\".format(sigmaInitial))\r\n    \r\n    # Use determined input for the local-search (final tuning)\r\n    func = lambda sigma: np.power(BS_Call_Put_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-15)\r\n    print(\"Final volatility = {0}\".format(impliedVol))\r\n    if impliedVol > 2.0:\r\n        impliedVol = 0.0\r\n    return impliedVol\r\n\r\ndef CallPutOptionPriceCOSMthd_StochIR(cf,CP,S0,tau,K,N,L,P0T):\r\n    # cf   - characteristic function as a functon, in the book denoted as \\varphi\r\n    # CP   - C for call and P for put\r\n    # S0   - Initial stock price\r\n    # tau  - time to maturity\r\n    # K    - list of strikes\r\n    # N    - Number of expansion terms\r\n    # L    - size of truncation domain (typ.:L=8 or L=10)  \r\n    # P0T  - zero-coupon bond for maturity T.\r\n        \r\n    # reshape K to a column vector\r\n    if K is not np.array:\r\n        K = np.array(K).reshape([len(K),1])\r\n    \r\n    #assigning i=sqrt(-1)\r\n    i = np.complex(0.0,1.0) \r\n    x0 = np.log(S0 / K)   \r\n    \r\n    # truncation domain\r\n    a = 0.0 - L * np.sqrt(tau)\r\n    b = 0.0 + L * np.sqrt(tau)\r\n    \r\n    # sumation from k = 0 to k=N-1\r\n    k = np.linspace(0,N-1,N).reshape([N,1])  \r\n    u = k * np.pi / (b - a)  \r\n\r\n    # Determine coefficients for Put Prices  \r\n    H_k = CallPutCoefficients(OptionType.PUT,a,b,k)   \r\n    mat = np.exp(i * np.outer((x0 - a) , u))\r\n    temp = cf(u) * H_k \r\n    temp[0] = 0.5 * temp[0]    \r\n    value = K * np.real(mat.dot(temp))     \r\n    \r\n    # we use call-put parity for call options\r\n    if CP == OptionType.CALL:\r\n        value = value + S0 - K * P0T\r\n        \r\n    return value\r\n\r\n# Determine coefficients for Put Prices \r\ndef CallPutCoefficients(CP,a,b,k):\r\n    if CP==OptionType.CALL:                  \r\n        c = 0.0\r\n        d = b\r\n        coef = Chi_Psi(a,b,c,d,k)\r\n        Chi_k = coef[\"chi\"]\r\n        Psi_k = coef[\"psi\"]\r\n        if a < b and b < 0.0:\r\n            H_k = np.zeros([len(k),1])\r\n        else:\r\n            H_k      = 2.0 / (b - a) * (Chi_k - Psi_k)  \r\n    elif CP==OptionType.PUT:\r\n        c = a\r\n        d = 0.0\r\n        coef = Chi_Psi(a,b,c,d,k)\r\n        Chi_k = coef[\"chi\"]\r\n        Psi_k = coef[\"psi\"]\r\n        H_k      = 2.0 / (b - a) * (- Chi_k + Psi_k)               \r\n    \r\n    return H_k    \r\n\r\ndef Chi_Psi(a,b,c,d,k):\r\n    psi = np.sin(k * np.pi * (d - a) / (b - a)) - np.sin(k * np.pi * (c - a)/(b - a))\r\n    psi[1:] = psi[1:] * (b - a) / (k[1:] * np.pi)\r\n    psi[0] = d - c\r\n    \r\n    chi = 1.0 / (1.0 + np.power((k * np.pi / (b - a)) , 2.0)) \r\n    expr1 = np.cos(k * np.pi * (d - a)/(b - a)) * np.exp(d)  - np.cos(k * np.pi \r\n                  * (c - a) / (b - a)) * np.exp(c)\r\n    expr2 = k * np.pi / (b - a) * np.sin(k * np.pi * \r\n                        (d - a) / (b - a))   - k * np.pi / (b - a) * np.sin(k \r\n                        * np.pi * (c - a) / (b - a)) * np.exp(c)\r\n    chi = chi * (expr1 + expr2)\r\n    \r\n    value = {\"chi\":chi,\"psi\":psi }\r\n    return value\r\n\r\ndef EUOptionPriceFromMCPathsGeneralizedFXFrwd(CP,S,K):\r\n    # S is a vector of Monte Carlo samples at T\r\n    result = np.zeros([len(K),1])\r\n    if CP == OptionType.CALL:\r\n        for (idx,k) in enumerate(K):\r\n            result[idx] = np.mean(np.maximum(S-k,0.0))\r\n    elif CP == OptionType.PUT:\r\n        for (idx,k) in enumerate(K):\r\n            result[idx] = np.mean(np.maximum(k-S,0.0))\r\n    return result\r\n\r\ndef GeneratePathsHHWFXHWEuler(NoOfPaths,NoOfSteps,T,frwdFX,v0,vbar,kappa,gamma,lambdd,lambdf,etad,etaf,rhoxv,rhoxrd,rhoxrf,rhovrd,rhovrf,rhordrf):    \r\n    Wx = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    Wv = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    Wrd = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    Wrf = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    \r\n    V = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    FX = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    V[:,0] = v0\r\n    FX[:,0] = frwdFX\r\n    \r\n    dt = T / float(NoOfSteps)\r\n    Bd = lambda t,T: 1.0/lambdd*(np.exp(-lambdd*(T-t))-1.0)\r\n    Bf = lambda t,T: 1.0/lambdf*(np.exp(-lambdf*(T-t))-1.0)\r\n    \r\n    cov = np.array([[1.0, rhoxv,rhoxrd,rhoxrf],[rhoxv,1.0,rhovrd,rhovrf],\\\r\n                        [rhoxrd,rhovrd,1.0,rhordrf],[rhoxrf,rhovrf,rhordrf,1.0]])\r\n      \r\n    time = np.zeros([NoOfSteps+1])    \r\n\r\n    for i in range(0,NoOfSteps):\r\n        Z = np.random.multivariate_normal([.0,.0,.0,.0],cov,NoOfPaths)\r\n        if NoOfPaths > 1:\r\n            Z[:,0] = (Z[:,0] - np.mean(Z[:,0])) / np.std(Z[:,0])\r\n            Z[:,1] = (Z[:,1] - np.mean(Z[:,1])) / np.std(Z[:,1])\r\n            Z[:,2] = (Z[:,2] - np.mean(Z[:,2])) / np.std(Z[:,2])\r\n            Z[:,3] = (Z[:,3] - np.mean(Z[:,3])) / np.std(Z[:,3])\r\n            \r\n        Wx[:,i+1] = Wx[:,i] + np.power(dt, 0.5)*Z[:,0]\r\n        Wv[:,i+1] = Wv[:,i] + np.power(dt, 0.5)*Z[:,1]\r\n        Wrd[:,i+1] = Wrd[:,i] + np.power(dt, 0.5)*Z[:,2]\r\n        Wrf[:,i+1] = Wrf[:,i] + np.power(dt, 0.5)*Z[:,3]\r\n\r\n        # Variance process- Euler discretization\r\n        V[:,i+1] = V[:,i] + kappa*(vbar - V[:,i])*dt \\\r\n                          + gamma*rhovrd*etad*Bd(time[i],T)*np.sqrt(V[:,i]) * dt \\\r\n                          + gamma* np.sqrt(V[:,i]) * (Wv[:,i+1]-Wv[:,i])\r\n        V[:,i+1] = np.maximum(V[:,i+1],0.0)\r\n        \r\n        # FX process under the forward measure\r\n        FX[:,i+1] = FX[:,i] *(1.0 + np.sqrt(V[:,i])*(Wx[:,i+1]-Wx[:,i]) \\\r\n                   -etad*Bd(time[i],T)*(Wrd[:,i+1]-Wrd[:,i])\\\r\n                   +etaf*Bf(time[i],T)*(Wrf[:,i+1]-Wrf[:,i]))\r\n        time[i+1] = time[i] +dt\r\n        \r\n    paths = {\"time\":time,\"FX\":FX}\r\n    return paths\r\n\r\n# Exact expectation E(sqrt(V(t)))\r\ndef meanSqrtV_3(kappa,v0,vbar,gamma):\r\n    delta = 4.0 *kappa*vbar/gamma/gamma\r\n    c= lambda t: 1.0/(4.0*kappa)*gamma*gamma*(1.0-np.exp(-kappa*(t)))\r\n    kappaBar = lambda t: 4.0*kappa*v0*np.exp(-kappa*t)/(gamma*gamma*(1.0-np.exp(-kappa*t)))\r\n    temp1 = lambda t: np.sqrt(2.0*c(t))* sp.gamma((1.0+delta)/2.0)/sp.gamma(delta/2.0)*sp.hyp1f1(-0.5,delta/2.0,-kappaBar(t)/2.0)\r\n    return temp1\r\n\r\ndef C_H1HW_FX(u,tau,kappa,gamma,rhoxv):\r\n    i = np.complex(0.0,1.0)\r\n    \r\n    D1 = np.sqrt(np.power(kappa-gamma*rhoxv*i*u,2.0)+(u*u+i*u)*gamma*gamma)\r\n    g  = (kappa-gamma*rhoxv*i*u-D1)/(kappa-gamma*rhoxv*i*u+D1)\r\n    C  = (1.0-np.exp(-D1*tau))/(gamma*gamma*(1.0-g*np.exp(-D1*tau)))\\\r\n        *(kappa-gamma*rhoxv*i*u-D1)\r\n    return C\r\n\r\ndef ChFH1HW_FX(u,tau,gamma,Rxv,Rxrd,Rxrf,Rrdrf,Rvrd,Rvrf,lambdd,etad,lambdf,etaf,kappa,vBar,v0):\r\n    i  = np.complex(0.0,1.0)\r\n    C = lambda u,tau: C_H1HW_FX(u,tau,kappa,gamma,Rxv)\r\n    Bd = lambda t,T: 1.0/lambdd*(np.exp(-lambdd*(T-t))-1.0)\r\n    Bf = lambda t,T: 1.0/lambdf*(np.exp(-lambdf*(T-t))-1.0)\r\n    G = meanSqrtV_3(kappa,v0,vBar,gamma)\r\n    \r\n    zeta = lambda t: (Rxrd*etad*Bd(t,tau) - Rxrf*etaf*Bf(t,tau))*G(t) + \\\r\n                    Rrdrf*etad*etaf*Bd(t,tau)*Bf(t,tau) - 0.5*(etad**2.0*Bd(t,tau)**2.0+etaf**2.0*Bf(t,tau)**2.0)\r\n    \r\n    # Integration in the function A(u,tau)\r\n    int1=np.zeros([len(u),1],dtype=np.complex_)   \r\n    N = 500\r\n    z=np.linspace(0.0+1e-10,tau-1e-10,N)\r\n    \r\n    temp1 =lambda z1: kappa*vBar + Rvrd*gamma*etad*G(tau-z1)*Bd(tau-z1,tau)\r\n    temp2 =lambda z1, u: -Rvrd*gamma*etad*G(tau-z1)*Bd(tau-z1,tau)*i*u\r\n    temp3 =lambda z1, u:  Rvrf*gamma*etaf*G(tau-z1)*Bf(tau-z1,tau)*i*u\r\n    f = lambda z1,u: (temp1(z1)+temp2(z1,u)+temp3(z1,u))*C(u,z1)\r\n    \r\n    value1 =integrate.trapz(np.real(f(z,u)),z).reshape(u.size,1)\r\n    value2 =integrate.trapz(np.imag(f(z,u)),z).reshape(u.size,1)\r\n    int1=(value1 + value2*i)\r\n    \r\n    \"\"\"\r\n    for k in range(0,len(u)):\r\n        temp1 = kappa*vBar + Rvrd*gamma*etad*G(tau-z)*Bd(tau-z,tau)\r\n        temp2 = -Rvrd*gamma*etad*G(tau-z)*Bd(tau-z,tau)*i*u[k]\r\n        temp3 = Rvrf*gamma*etaf*G(tau-z)*Bf(tau-z,tau)*i*u[k]\r\n        f = (temp1+temp2+temp3)*C(u[k],z)\r\n        value1 =integrate.trapz(np.real(f),z)\r\n        value2 =integrate.trapz(np.imag(f),z)\r\n        int1[k]=(value1 + value2*i)\r\n    \"\"\"   \r\n    int2 = (u**2.0 + i*u)*integrate.trapz(zeta(tau-z),z)\r\n    A = int1 + int2\r\n    \r\n    cf = np.exp(A + v0*C(u,tau))\r\n    return cf\r\n\r\ndef GenerateStrikes(frwd,Ti):\r\n    c_n = np.array([-1.5, -1.0, -0.5,0.0, 0.5, 1.0, 1.5])\r\n    return frwd * np.exp(0.1 * c_n * np.sqrt(Ti))\r\n\r\ndef mainCalculation():\r\n    CP  = OptionType.CALL  \r\n    T       = 5.0\r\n    \r\n    NoOfPaths = 1000\r\n    NoOfSteps = (int)(T*50)\r\n    \r\n    # Settings for the COS method\r\n    N = 500\r\n    L = 8  \r\n    \r\n    # Market Settings\r\n    P0Td = lambda t: np.exp(-0.02*t)\r\n    P0Tf = lambda t: np.exp(-0.05*t)\r\n    y0      = 1.35\r\n    frwdFX  = y0*P0Tf(T)/P0Td(T)\r\n    kappa   = 0.5\r\n    gamma   = 0.3\r\n    vbar    = 0.1\r\n    v0      = 0.1\r\n    \r\n    # HW model settings\r\n    lambdd  = 0.01\r\n    lambdf  = 0.05\r\n    etad    = 0.007\r\n    etaf    = 0.012\r\n    \r\n    # correlations\r\n    Rxv   = -0.4\r\n    Rxrd  = -0.15\r\n    Rxrf  = -0.15\r\n    Rvrd  = 0.3\r\n    Rvrf  = 0.3\r\n    Rrdrf = 0.25\r\n    \r\n    # Strikes\r\n    K = GenerateStrikes(frwdFX,T)\r\n    K = np.array(K).reshape([len(K),1])\r\n        \r\n    # number of repeated simulations for different Monte Carlo seed\r\n    SeedV = range(0,20) \r\n    optMCM = np.zeros([len(SeedV),len(K)])\r\n    for (idx,seed) in enumerate(SeedV):\r\n        print('Seed number = {0} out of {1}'.format(idx,len(SeedV)))\r\n        np.random.seed(seed)\r\n        paths = GeneratePathsHHWFXHWEuler(NoOfPaths,NoOfSteps,T,frwdFX,v0,vbar,kappa,gamma,lambdd,lambdf,etad,etaf,Rxv,Rxrd,Rxrf,Rvrd,Rvrf,Rrdrf)\r\n        frwdfxT = paths[\"FX\"]\r\n        optMC = P0Td(T)* EUOptionPriceFromMCPathsGeneralizedFXFrwd(CP,frwdfxT[:,-1],K)    \r\n        optMCM[idx,:]= np.squeeze(optMC)\r\n    \r\n    # Average of the runs + standard deviation\r\n    optionMC_E = np.zeros([len(K)])\r\n    optionMC_StDev = np.zeros([len(K)])\r\n    for (idx,k) in enumerate(K):\r\n        optionMC_E[idx] = np.mean(optMCM[:,idx])\r\n        optionMC_StDev[idx]  = np.std(optMCM[:,idx])\r\n\r\n    # Value from the COS method\r\n    cf = lambda u: ChFH1HW_FX(u,T,gamma,Rxv,Rxrd,Rxrf,Rrdrf,Rvrd,Rvrf,lambdd,etad,lambdf,etaf,kappa,vbar,v0)    \r\n    valCOS_H1HW = P0Td(T)*CallPutOptionPriceCOSMthd_StochIR(cf, CP, frwdFX, T, K, N, L,1.0)\r\n    \r\n    # checking martingale property\r\n    EyT = P0Td(T)/P0Tf(T)*EUOptionPriceFromMCPathsGeneralizedFXFrwd(CP,frwdfxT[:,-1],[0.0])\r\n    print(\"Martingale check: P_d(T)/P_f(T)*E[FX(T)] ={0:.4f} and y0 ={1}\".format(EyT[0][0],y0))\r\n    print(\"Maturity chosen to T={0}\".format(T))\r\n    for (idx,k) in enumerate(K):\r\n        print(\"Option price for strike K={0:.4f} is equal to: COS method = {1:.4f} and MC = {2:.4f} with stdDev = {3:.4f}\".format(k[0],valCOS_H1HW[idx][0],optionMC_E[idx],optionMC_StDev[idx]))\r\n    \r\n    plt.figure(1)\r\n    plt.plot(K,optionMC_E)\r\n    plt.plot(K,valCOS_H1HW,'--r')\r\n    plt.grid()\r\n    plt.legend(['Monte Carlo Option Price','COS method'])\r\n    plt.title(\"Fx Option prices\")\r\n    \r\n    # implied volatilities\r\n    IVCos =np.zeros([len(K),1])\r\n    IVMC =np.zeros([len(K),1])\r\n    for (idx,k) in enumerate(K):\r\n        priceCOS = valCOS_H1HW[idx]/P0Td(T) \r\n        IVCos[idx] = ImpliedVolatilityBlack76(CP,priceCOS ,k,T,frwdFX)*100.0\r\n        priceMC = optionMC_E[idx]/P0Td(T)\r\n        IVMC[idx] = ImpliedVolatilityBlack76(CP,priceMC ,k,T,frwdFX)*100.0\r\n    \r\n    plt.figure(2)\r\n    plt.plot(K,IVMC)\r\n    plt.plot(K,IVCos,'--r')\r\n    \r\n    plt.grid()\r\n    plt.legend(['IV-COS','IV-MC'])\r\n    plt.title(\"Fx Implied volatilities\")\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 11-Market Model and Convexity Adjustments/Materials/ConvexityCorrection.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on August 25 2021\r\nConvexity correction exercise\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport enum \r\nimport matplotlib.pyplot as plt\r\nimport scipy.integrate as integrate\r\n\r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    B_r = HW_B(lambd,eta,T1,T2)\r\n    A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n    return np.exp(A_r + B_r *rT1)\r\n\r\ndef HWMean_r(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,2500)\r\n    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))\r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    return r_mean\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    return r0\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 20000\r\n    NoOfSteps = 1000\r\n        \r\n    lambd     = 0.02\r\n    eta       = 0.02\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.1*T)#np.exp(-0.03*T*T-0.1*T)\r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 25\r\n    T_end = 50\r\n    Tgrid= np.linspace(0,T_end,N)\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy= np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Exact[i] = P0T(Ti)\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy,'--r')\r\n    plt.legend([\"Analytcal ZCB\",\"Monte Carlo ZCB\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n\r\n    # Here we define a libor rate and measure the convexity effect\r\n    T1 = 4.0\r\n    T2 = 8.0\r\n    \r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T1 ,P0T, lambd, eta)\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n    dt = timeGrid[1]-timeGrid[0]\r\n    M_t = np.zeros([NoOfPaths,NoOfSteps])\r\n    for i in range(0,NoOfPaths):\r\n        M_t[i,:] = np.exp(np.cumsum(r[i,:-1])*dt)\r\n    P_T1_T2 = HW_ZCB(lambd,eta,P0T,T1,T2,r[:,-1])\r\n    L_T1_T2 = 1.0/(T2-T1)*(1.0/P_T1_T2-1)\r\n    MC_Result = np.mean(1/M_t[:,-1]*L_T1_T2)\r\n    print('Price of E(L(T1,T1,T2)/M(T1)) = {0}'.format(MC_Result))\r\n    \r\n    L_T0_T1_T2 = 1.0/(T2-T1)*(P0T(T1)/P0T(T2)-1.0)\r\n    # Define convexity correction\r\n    cc = lambda sigma: P0T(T2)*(L_T0_T1_T2 + (T2-T1)*L_T0_T1_T2**2.0*np.exp(sigma**2*T1))-L_T0_T1_T2\r\n    \r\n    # take random sigma and check the effect on price\r\n    sigma = 0.2\r\n    print('Price of E(L(T1,T1,T2)/M(T1)) = {0} (no cc)'.format(L_T0_T1_T2))\r\n    print('Price of E(L(T1,T1,T2)/M(T1)) = {0} (with cc, sigma={1})'.format(L_T0_T1_T2+cc(sigma),sigma))\r\n    \r\n    # Plot some results\r\n    plt.figure(2)\r\n    sigma_range = np.linspace(0.0,0.6,100)\r\n    plt.plot(sigma_range,cc(sigma_range))\r\n    plt.grid()\r\n    plt.xlabel('sigma')\r\n    plt.ylabel('cc')\r\n    \r\n    plt.figure(3)\r\n    plt.plot(sigma_range,MC_Result*np.ones([len(sigma_range),1]))\r\n    plt.plot(sigma_range,L_T0_T1_T2+cc(sigma_range),'--r')\r\n    plt.grid()\r\n    plt.xlabel('sigma')\r\n    plt.ylabel('value of derivative')\r\n    plt.legend(['market price','price with cc'])\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 11-Market Model and Convexity Adjustments/Materials/DD_ImpliedVolatility.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on August 25 2021\r\nDisplaced Diffusion and implied volatilities\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport scipy.stats as st\r\nimport scipy.optimize as optimize\r\nimport enum \r\n\r\n# set i= imaginary number\r\ni   = np.complex(0.0,1.0)\r\n\r\n# time-step needed for differentiation\r\ndt = 0.0001\r\n \r\n# This class defines puts and calls\r\nclass OptionType(enum.Enum):\r\n    CALL = 1.0\r\n    PUT = -1.0\r\n    \r\n# Black-Scholes Call option price\r\ndef BS_Call_Option_Price(CP,S_0,K,sigma,tau,r):\r\n    if K is list:\r\n        K = np.array(K).reshape([len(K),1])\r\n    d1    = (np.log(S_0 / K) + (r + 0.5 * np.power(sigma,2.0)) * tau) / (sigma * np.sqrt(tau))\r\n    d2    = d1 - sigma * np.sqrt(tau)\r\n    if CP == OptionType.CALL:\r\n        value = st.norm.cdf(d1) * S_0 - st.norm.cdf(d2) * K * np.exp(-r * tau)\r\n    elif CP == OptionType.PUT:\r\n        value = st.norm.cdf(-d2) * K * np.exp(-r * tau) - st.norm.cdf(-d1)*S_0\r\n    return value\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76_xxx(CP,marketPrice,K,T,S_0):\r\n    func = lambda sigma: np.power(BS_Call_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, 0.2, tol=1e-9)\r\n    #impliedVol = optimize.brent(func, brack= (0.05, 2))\r\n    return impliedVol\r\n\r\n# Implied volatility method\r\ndef ImpliedVolatilityBlack76(CP,marketPrice,K,T,S_0):\r\n    # To determine initial volatility we interpolate define a grid for sigma\r\n    # and interpolate on the inverse\r\n    sigmaGrid = np.linspace(0.0,2.0,5000)\r\n    optPriceGrid = BS_Call_Option_Price(CP,S_0,K,sigmaGrid,T,0.0)\r\n    sigmaInitial = np.interp(marketPrice,optPriceGrid,sigmaGrid)\r\n    print(\"Initial volatility = {0}\".format(sigmaInitial))\r\n    \r\n    # Use determined input for the local-search (final tuning)\r\n    func = lambda sigma: np.power(BS_Call_Option_Price(CP,S_0,K,sigma,T,0.0) - marketPrice, 1.0)\r\n    impliedVol = optimize.newton(func, sigmaInitial, tol=1e-5)\r\n    print(\"Final volatility = {0}\".format(impliedVol))\r\n    return impliedVol\r\n\r\ndef DisplacedDiffusionModel_CallPrice(K,P0T,beta,sigma,frwd,T):\r\n     d1    = (np.log(frwd / (beta*K+(1.0-beta)*frwd)) + 0.5 * np.power(sigma*beta,2.0) * T) / (sigma * beta* np.sqrt(T))\r\n     d2    = d1 - sigma * beta * np.sqrt(T)\r\n     return P0T(T) * (frwd/beta * st.norm.cdf(d1) - (K + (1.0-beta)/beta*frwd) * st.norm.cdf(d2))\r\n\r\ndef mainCalculation():\r\n    CP  = OptionType.CALL\r\n        \r\n    K = np.linspace(0.3,2.8,22)\r\n    K = np.array(K).reshape([len(K),1])\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.05*T) \r\n    \r\n    # DD model parameters\r\n    beta  = 0.5\r\n    sigma = 0.15\r\n    \r\n    # Forward rate\r\n    frwd = 1.0\r\n    \r\n    # Maturity\r\n    T = 2.0\r\n           \r\n    # Effect of sigma\r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('implied volatility')\r\n    sigmaV = [0.1,0.2,0.3,0.4]\r\n    legend = []\r\n    for sigmaTemp in sigmaV:    \r\n       callPrice = DisplacedDiffusionModel_CallPrice(K,P0T,beta,sigmaTemp,frwd,T)\r\n       # Implied volatilities\r\n       IV =np.zeros([len(K),1])\r\n       for idx in range(0,len(K)):\r\n           valCOSFrwd = callPrice[idx]/P0T(T)\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valCOSFrwd,K[idx],T,frwd)\r\n       plt.plot(K,IV*100.0)       \r\n       legend.append('sigma={0}'.format(sigmaTemp))\r\n       plt.ylim([0.0,60])\r\n    plt.legend(legend)    \r\n    \r\n    # Effect of beta\r\n    plt.figure(2)\r\n    plt.grid()\r\n    plt.xlabel('strike, K')\r\n    plt.ylabel('implied volatility')\r\n    betaV = [-0.5, 0.0001, 0.5, 1.0]\r\n    legend = []\r\n    for betaTemp in betaV:    \r\n       callPrice = DisplacedDiffusionModel_CallPrice(K,P0T,betaTemp,sigma,frwd,T)\r\n       # Implied volatilities\r\n       IV =np.zeros([len(K),1])\r\n       for idx in range(0,len(K)):\r\n           valCOSFrwd = callPrice[idx]/P0T(T)\r\n           IV[idx] = ImpliedVolatilityBlack76(CP,valCOSFrwd,K[idx],T,frwd)\r\n       plt.plot(K,IV*100.0)       \r\n       legend.append('beta={0}'.format(betaTemp))\r\n    plt.legend(legend)    \r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 12-Valuation Adjustments- xVA (CVA,BCVA and FVA)/Materials/Exposures_HW_Netting.py",
    "content": "#%%\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 is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport enum \r\nimport matplotlib.pyplot as plt\r\nimport scipy.stats as st\r\nimport scipy.integrate as integrate\r\n\r\n# This class defines puts and calls\r\nclass OptionTypeSwap(enum.Enum):\r\n    RECEIVER = 1.0\r\n    PAYER = -1.0\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    n = np.size(rT1) \r\n        \r\n    if T1<T2:\r\n        B_r = HW_B(lambd,eta,T1,T2)\r\n        A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n        return np.exp(A_r + B_r *rT1)\r\n    else:\r\n        return np.ones([n])\r\n\r\n\r\ndef HWMean_r(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,2500)\r\n    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))\r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    return r_mean\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    return r0\r\n\r\ndef HW_Mu_FrwdMeasure(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,500)\r\n    \r\n    theta_hat =lambda t,T:  theta(t) + eta*eta / lambd *1.0/lambd * (np.exp(-lambd*(T-t))-1.0)\r\n    \r\n    temp =lambda z: theta_hat(z,T) * np.exp(-lambd*(T-z))\r\n    \r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    \r\n    return r_mean\r\n\r\ndef HWVar_r(lambd,eta,T):\r\n    return eta*eta/(2.0*lambd) *( 1.0-np.exp(-2.0*lambd *T))\r\n\r\ndef HWDensity(P0T,lambd,eta,T):\r\n    r_mean = HWMean_r(P0T,lambd,eta,T)\r\n    r_var = HWVar_r(lambd,eta,T)\r\n    return lambda x: st.norm.pdf(x,r_mean,np.sqrt(r_var))\r\n\r\ndef HW_SwapPrice(CP,notional,K,t,Ti,Tm,n,r_t,P0T,lambd,eta):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n\r\n    if n == 1:\r\n        ti_grid =np.array([Ti,Tm])\r\n    else:\r\n        ti_grid = np.linspace(Ti,Tm,n)\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    # overwrite Ti if t>Ti\r\n    prevTi = ti_grid[np.where(ti_grid<t)]\r\n    if np.size(prevTi) > 0: #prevTi != []:\r\n        Ti = prevTi[-1]\r\n    \r\n    # Now we need to handle the case when some payments are already done\r\n    ti_grid = ti_grid[np.where(ti_grid>t)]          \r\n\r\n    temp= np.zeros(np.size(r_t));\r\n    \r\n    P_t_TiLambda = lambda Ti : HW_ZCB(lambd,eta,P0T,t,Ti,r_t)\r\n    \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if ti>Ti:\r\n            temp = temp + tau * P_t_TiLambda(ti)\r\n            \r\n    P_t_Ti = P_t_TiLambda(Ti)\r\n    P_t_Tm = P_t_TiLambda(Tm)\r\n    \r\n    if CP==OptionTypeSwap.PAYER:\r\n        swap = (P_t_Ti - P_t_Tm) - K * temp\r\n    elif CP==OptionTypeSwap.RECEIVER:\r\n        swap = K * temp- (P_t_Ti - P_t_Tm)\r\n    \r\n    return swap*notional\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 2000\r\n    NoOfSteps = 1000\r\n    lambd     = 0.5\r\n    eta       = 0.03\r\n    notional  = 10000.0 \r\n    notional2 = 10000.0\r\n    alpha     = 0.99\r\n    alpha2     = 0.95\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.01*T)\r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 25\r\n    T_end = 50\r\n    Tgrid= np.linspace(0,T_end,N)\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy= np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Exact[i] = P0T(Ti)\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy,'--r')\r\n    plt.legend([\"Analytcal ZCB\",\"Monte Carlo ZCB\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n    \r\n    \r\n    # Here we simulate the exposure profiles for a swap, using the HW model    \r\n    # Swap settings\r\n    K = 0.01  # strike\r\n    Ti = 1.0  # begining of the swap\r\n    Tm = 10.0 # end date of the swap \r\n    n = 10    # number of payments between Ti and Tm\r\n    \r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,Tm+1.0 ,P0T, lambd, eta)\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n    dt = timeGrid[1]-timeGrid[0]\r\n    \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    \r\n    M_t = np.zeros([NoOfPaths,NoOfSteps])\r\n            \r\n    for i in range(0,NoOfPaths):\r\n        M_t[i,:] = np.exp(np.cumsum(r[i,0:-1])*dt)\r\n        \r\n    # Portfolio without netting    \r\n    Value= np.zeros([NoOfPaths,NoOfSteps+1])\r\n    E  = np.zeros([NoOfPaths,NoOfSteps+1])\r\n    EE = np.zeros([NoOfSteps+1])\r\n    PFE = np.zeros([NoOfSteps+1])\r\n    PFE2 = np.zeros([NoOfSteps+1])\r\n    for (idx, ti) in enumerate(timeGrid[0:-2]):\r\n        V = HW_SwapPrice(OptionTypeSwap.PAYER,notional,K,timeGrid[idx],Ti,Tm,n,r[:,idx],P0T,lambd,eta)\r\n        Value[:,idx] = V\r\n        E[:,idx] = np.maximum(V,0.0)\r\n        EE[idx] = np.mean(E[:,idx]/M_t[:,idx])\r\n        PFE[idx] = np.quantile(E[:,idx],alpha)\r\n        PFE2[idx] = np.quantile(E[:,idx],alpha2)\r\n    \r\n    # Portfolio with netting    \r\n    ValuePort = np.zeros([NoOfPaths,NoOfSteps+1])\r\n    EPort  = np.zeros([NoOfPaths,NoOfSteps+1])\r\n    EEPort = np.zeros([NoOfSteps+1])\r\n    PFEPort = np.zeros([NoOfSteps+1])\r\n    for (idx, ti) in enumerate(timeGrid[0:-2]):\r\n        Swap1 = HW_SwapPrice(OptionTypeSwap.PAYER,notional,K,timeGrid[idx],Ti,Tm,n,r[:,idx],P0T,lambd,eta)\r\n        Swap2 = HW_SwapPrice(OptionTypeSwap.RECEIVER,notional2,0.0,timeGrid[idx],Tm-2.0*(Tm-Ti)/n,Tm,1,r[:,idx],P0T,lambd,eta)\r\n        \r\n        VPort = Swap1 + Swap2\r\n        ValuePort[:,idx] = VPort\r\n        EPort[:,idx] = np.maximum(VPort,0.0)\r\n        EEPort[idx] = np.mean(EPort[:,idx]/M_t[:,idx])\r\n        PFEPort[idx] = np.quantile(EPort[:,idx],alpha)\r\n    \r\n    plt.figure(2)\r\n    plt.plot(timeGrid,Value[0:100,:].transpose(),'b')\r\n    plt.grid()\r\n    plt.xlabel('time')\r\n    plt.ylabel('exposure, Value(t)')\r\n    plt.title('Value of a swap')\r\n\r\n    plt.figure(3)\r\n    plt.plot(timeGrid,E[0:100,:].transpose(),'r')\r\n    plt.grid()\r\n    plt.xlabel('time')\r\n    plt.ylabel('exposure, E(t)')\r\n    plt.title('Positive Exposure E(t)')\r\n    \r\n    plt.figure(4)\r\n    plt.plot(timeGrid,EE,'r')\r\n    plt.grid()\r\n    plt.xlabel('time')\r\n    plt.ylabel('exposure, EE(t)')\r\n    plt.title('Discounted Expected (positive) exposure, EE')\r\n    plt.legend(['EE','PFE'])\r\n    \r\n    plt.figure(5)\r\n    plt.plot(timeGrid,EE,'r')\r\n    plt.plot(timeGrid,PFE,'k')\r\n    plt.plot(timeGrid,PFE2,'--b')\r\n    plt.grid()\r\n    plt.xlabel('time')\r\n    plt.ylabel(['EE, PEE(t)'])\r\n    plt.title('Discounted Expected (positive) exposure, EE')\r\n    \r\n    plt.figure(6)\r\n    plt.plot(timeGrid,EEPort,'r')\r\n    plt.plot(timeGrid,PFEPort,'k')\r\n    plt.grid()\r\n    plt.title('Portfolio with two swaps')\r\n    plt.legend(['EE-port','PFE-port'])\r\n    \r\n    plt.figure(7)\r\n    plt.plot(timeGrid,EE,'r')\r\n    plt.plot(timeGrid,EEPort,'--r')\r\n    plt.grid()\r\n    plt.title('Comparison of EEs ')\r\n    plt.legend(['EE, swap','EE, portfolio'])\r\n    \r\n    plt.figure(8)\r\n    plt.plot(timeGrid,PFE,'k')\r\n    plt.plot(timeGrid,PFEPort,'--k')\r\n    plt.grid()\r\n    plt.title('Comparison of PFEs ')\r\n    plt.legend(['PFE, swap','PFE, portfolio'])\r\n    \r\nmainCalculation()"
  },
  {
    "path": "Lecture 13-Value-at-Risk and Expected Shortfall/Materials/HistoricalVaR_Calculation.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on August 27 2021\r\n\r\nHistorical Value-at-Risk Calculation based on real market data \r\nhttps://www.treasury.gov/resource-center/data-chart-center/interest-rates/Pages/TextView.aspx?data=yieldYear&year=2021\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\nimport numpy as np\r\nimport enum \r\nimport matplotlib.pyplot as plt\r\nfrom copy import deepcopy\r\nimport pandas as pd\r\n\r\n# This class defines puts and calls\r\nclass OptionTypeSwap(enum.Enum):\r\n    RECEIVER = 1.0\r\n    PAYER = -1.0\r\n    \r\ndef IRSwap(CP,notional,K,t,Ti,Tm,n,P0T):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n    ti_grid = np.linspace(Ti,Tm,int(n))\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    # overwrite Ti if t>Ti\r\n    prevTi = ti_grid[np.where(ti_grid<t)]\r\n    if np.size(prevTi) > 0: #prevTi != []:\r\n        Ti = prevTi[-1]\r\n    \r\n    # Now we need to handle the case when some payments are already done\r\n    ti_grid = ti_grid[np.where(ti_grid>t)]           \r\n\r\n    temp= 0.0\r\n        \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if ti>Ti:\r\n            temp = temp + tau * P0T(ti)\r\n            \r\n    P_t_Ti = P0T(Ti)\r\n    P_t_Tm = P0T(Tm)\r\n    \r\n    if CP==OptionTypeSwap.PAYER:\r\n        swap = (P_t_Ti - P_t_Tm) - K * temp\r\n    elif CP==OptionTypeSwap.RECEIVER:\r\n        swap = K * temp - (P_t_Ti - P_t_Tm)\r\n    \r\n    return swap * notional\r\n\r\ndef P0TModel(t,ti,ri,method):\r\n    rInterp = method(ti,ri)\r\n    r = rInterp(t)\r\n    return np.exp(-r*t)\r\n\r\ndef YieldCurve(instruments, maturities, r0, method, tol):\r\n    r0 = deepcopy(r0)\r\n    ri = MultivariateNewtonRaphson(r0, maturities, instruments, method, tol=tol)\r\n    return ri\r\n\r\ndef MultivariateNewtonRaphson(ri, ti, instruments, method, tol):\r\n    err = 10e10\r\n    idx = 0\r\n    while err > tol:\r\n        idx = idx +1\r\n        values = EvaluateInstruments(ti,ri,instruments,method)\r\n        J = Jacobian(ti,ri, instruments, method)\r\n        J_inv = np.linalg.inv(J)\r\n        err = - np.dot(J_inv, values)\r\n        ri[0:] = ri[0:] + err \r\n        err = np.linalg.norm(err)\r\n        #print('index in the loop is',idx,' Error is ', err)\r\n    return ri\r\n\r\ndef Jacobian(ti, ri, instruments, method):\r\n    eps = 1e-05\r\n    swap_num = len(ti)\r\n    J = np.zeros([swap_num, swap_num])\r\n    val = EvaluateInstruments(ti,ri,instruments,method)\r\n    ri_up = deepcopy(ri)\r\n    \r\n    for j in range(0, len(ri)):\r\n        ri_up[j] = ri[j] + eps  \r\n        val_up = EvaluateInstruments(ti,ri_up,instruments,method)\r\n        ri_up[j] = ri[j]\r\n        dv = (val_up - val) / eps\r\n        J[:, j] = dv[:]\r\n    return J\r\n\r\ndef EvaluateInstruments(ti,ri,instruments,method):\r\n    P0Ttemp = lambda t: P0TModel(t,ti,ri,method)\r\n    val = np.zeros(len(instruments))\r\n    for i in range(0,len(instruments)):\r\n        val[i] = instruments[i](P0Ttemp)\r\n    return val\r\n\r\ndef linear_interpolation(ti,ri):\r\n    interpolator = lambda t: np.interp(t, ti, ri)\r\n    return interpolator\r\n\r\ndef BuildYieldCurve(K,mat):\r\n    # Convergence tolerance\r\n    tol = 1.0e-15\r\n    # Initial guess for the spine points\r\n    r0 = np.array([0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01])   \r\n    # Interpolation method\r\n    method = linear_interpolation\r\n\r\n    swap1 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[0],0.0,0.0,mat[0],4*mat[0],P0T)\r\n    swap2 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[1],0.0,0.0,mat[1],4*mat[1],P0T)\r\n    swap3 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[2],0.0,0.0,mat[2],4*mat[2],P0T)\r\n    swap4 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[3],0.0,0.0,mat[3],4*mat[3],P0T)\r\n    swap5 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[4],0.0,0.0,mat[4],4*mat[4],P0T)\r\n    swap6 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[5],0.0,0.0,mat[5],4*mat[5],P0T)\r\n    swap7 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[6],0.0,0.0,mat[6],4*mat[6],P0T)\r\n    swap8 = lambda P0T: IRSwap(OptionTypeSwap.PAYER,1,K[7],0.0,0.0,mat[7],4*mat[7],P0T)\r\n    instruments = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8]\r\n    \r\n    # determine optimal spine points\r\n    ri = YieldCurve(instruments, mat, r0, method, tol)\r\n    #print('\\n Spine points are',ri,'\\n')\r\n    \r\n    # Build a ZCB-curve/yield curve from the spine points\r\n    P0T = lambda t: P0TModel(t,mat,ri,method)\r\n    return P0T, instruments\r\n\r\ndef Portfolio(P0T):\r\n            #IRSwap(CP,           notional,          K,   t,   Ti,  Tm,   n,      P0T):\r\n    value = IRSwap(OptionTypeSwap.RECEIVER,1000000,0.02,0.0, 0.0,  20,  20,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.PAYER, 500000,   0.01,0.0, 0.0,  10,  20,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.RECEIVER,25000,0.02,0.0, 0.0,  30,  60,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.PAYER,74000,0.005,0.0, 0.0,  5,  10,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.RECEIVER,254000,0.032,0.0, 0.0,  15,  10,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.RECEIVER,854000,0.01,0.0, 0.0,  7,  20,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.PAYER,350000,0.028,0.0, 0.0,  10,  20,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.PAYER,1000000,-0.01,0.0, 0.0,  5,  20,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.RECEIVER,1000000,0.01,0.0, 0.0,  14,  20,  P0T) +\\\r\n            IRSwap(OptionTypeSwap.PAYER,1000000,0.03,0.0, 0.0,  2,  4,  P0T) \r\n\r\n    return value\r\n\r\ndef mainCode():\r\n    \r\n    # Curves from the market\r\n    marketdataXLS = pd.read_excel('MrktData.xlsx')\r\n    \r\n    # Divide the quotes by 100 as they are expressed in %\r\n    marketData = np.array(marketdataXLS) / 100.0\r\n    \r\n    # Building up 1D scenarios\r\n    shape= np.shape(marketData)     \r\n    NoOfScen = shape[0]\r\n    NoOfInsts = shape[1]\r\n    \r\n    Scenarios = np.zeros([NoOfScen-1,NoOfInsts])\r\n    for i in range(0,NoOfScen-1):\r\n        for j in range(0,NoOfInsts):\r\n            Scenarios[i,j] = marketData[i+1,j]-marketData[i,j]\r\n    \r\n    # Construct instruments for TODAY's curve\r\n    Swaps_mrkt   = np.array([0.08,\t0.2,\t0.4,\t0.77,\t1.07,\t1.29,\t1.82,\t1.9]) / 100\r\n    mat = np.array([1.0,    2.0,    3.0,    5.0,    7.0,    10.0,   20.0,   30.0])\r\n    \r\n    # Given market quotes for swaps and scenarios we generate now \"shocked\" yield curves\r\n    Swaps_mrkt_shocked = np.zeros([NoOfScen-1,NoOfInsts])\r\n    for i in range(0,NoOfScen-1):\r\n        for j in range(0,NoOfInsts):\r\n            Swaps_mrkt_shocked[i,j] = Swaps_mrkt[j] + Scenarios[i,j]\r\n    \r\n    # For every shocked market scenario we build a yield curve\r\n    YC_for_VaR = []#np.zeros([NoOfScen-1])\r\n    for i in range(0,NoOfScen-1):\r\n        P0T,instruments = BuildYieldCurve(Swaps_mrkt_shocked[i,:],mat)\r\n        YC_for_VaR.append(P0T)\r\n        print('Scenario number',i,' out of  ', NoOfScen-1)\r\n    \r\n    # For every shocked yield curve we re-value the portfolio of interest rate derivatives\r\n    PortfolioPV = np.zeros([NoOfScen-1])\r\n    for i in range(0,NoOfScen-1):\r\n        PortfolioPV[i] = Portfolio(YC_for_VaR[i])\r\n    \r\n    # Current Yield curve\r\n    YC_today,insts = BuildYieldCurve(Swaps_mrkt,mat)\r\n    print('Current Portfolio PV is ', Portfolio(YC_today))\r\n    \r\n    # Histograms and Var Calculatiosn\r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.hist(PortfolioPV,20)\r\n    \r\n    # VaR calculation\r\n    alpha = 0.05\r\n    HVaR_estimate = np.quantile(PortfolioPV,alpha)\r\n    print('(H)VaR for alpha = ', alpha, ' is equal to=', HVaR_estimate)\r\n    \r\n    # Expected shortfal\r\n    condLosses = PortfolioPV[PortfolioPV < HVaR_estimate]\r\n    print('P&L which < VaR_alpha =',condLosses)\r\n    ES = np.mean(condLosses)\r\n    \r\n    print('Expected shortfal = ', ES)\r\n    \r\n    plt.plot(HVaR_estimate,0,'or')\r\n    plt.plot(ES,0,'ok')\r\n    plt.legend(['VaR','ES','P&L'])\r\n    \r\n    return 0.0\r\n\r\nmainCode()\r\n    \r\n    "
  },
  {
    "path": "Lecture 13-Value-at-Risk and Expected Shortfall/Materials/MonteCarloVaR.py",
    "content": "#%%\r\n\"\"\"\r\nCreated on August 25  2021\r\nValue-At-Risk computation based on Monte Carlo simulation of the Hull-White model\r\n\r\nThis code is purely educational and comes from \"Financial Engineering\" course by L.A. Grzelak\r\nThe course is based on the book “Mathematical Modeling and Computation\r\nin Finance: With Exercises and Python and MATLAB Computer Codes”,\r\nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing Europe Ltd, 2019.\r\n@author: Lech A. Grzelak\r\n\"\"\"\r\n\r\nimport numpy as np\r\nimport enum \r\nimport matplotlib.pyplot as plt\r\nimport scipy.stats as st\r\nimport scipy.integrate as integrate\r\nimport scipy.optimize as optimize\r\n\r\n# This class defines puts and calls\r\nclass OptionTypeSwap(enum.Enum):\r\n    RECEIVER = 1.0\r\n    PAYER = -1.0\r\n\r\ndef GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T,P0T, lambd, eta):    \r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    \r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    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))      \r\n    \r\n    #theta = lambda t: 0.1 +t -t\r\n    #print(\"changed theta\")\r\n    \r\n    Z = np.random.normal(0.0,1.0,[NoOfPaths,NoOfSteps])\r\n    W = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R = np.zeros([NoOfPaths, NoOfSteps+1])\r\n    R[:,0]=r0\r\n    time = np.zeros([NoOfSteps+1])\r\n        \r\n    dt = T / float(NoOfSteps)\r\n    for i in range(0,NoOfSteps):\r\n        # making sure that samples from normal have mean 0 and variance 1\r\n        if NoOfPaths > 1:\r\n            Z[:,i] = (Z[:,i] - np.mean(Z[:,i])) / np.std(Z[:,i])\r\n        W[:,i+1] = W[:,i] + np.power(dt, 0.5)*Z[:,i]\r\n        R[:,i+1] = R[:,i] + lambd*(theta(time[i]) - R[:,i]) * dt + eta* (W[:,i+1]-W[:,i])\r\n        time[i+1] = time[i] +dt\r\n        \r\n    # Outputs\r\n    paths = {\"time\":time,\"R\":R}\r\n    return paths\r\n\r\ndef HW_theta(lambd,eta,P0T):\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    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))\r\n    #print(\"CHANGED THETA\")\r\n    return theta#lambda t: 0.1+t-t\r\n    \r\ndef HW_A(lambd,eta,P0T,T1,T2):\r\n    tau = T2-T1\r\n    zGrid = np.linspace(0.0,tau,250)\r\n    B_r = lambda tau: 1.0/lambd * (np.exp(-lambd *tau)-1.0)\r\n    theta = HW_theta(lambd,eta,P0T)    \r\n    temp1 = lambd * integrate.trapz(theta(T2-zGrid)*B_r(zGrid),zGrid)\r\n    \r\n    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)\r\n    \r\n    return temp1 + temp2\r\n\r\ndef HW_B(lambd,eta,T1,T2):\r\n    return 1.0/lambd *(np.exp(-lambd*(T2-T1))-1.0)\r\n\r\ndef HW_ZCB(lambd,eta,P0T,T1,T2,rT1):\r\n    n = np.size(rT1) \r\n        \r\n    if T1<T2:\r\n        B_r = HW_B(lambd,eta,T1,T2)\r\n        A_r = HW_A(lambd,eta,P0T,T1,T2)\r\n        return np.exp(A_r + B_r *rT1)\r\n    else:\r\n        return np.ones([n])\r\n\r\n\r\ndef HWMean_r(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2.0*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,2500)\r\n    temp =lambda z: theta(z) * np.exp(-lambd*(T-z))\r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    return r_mean\r\n\r\ndef HW_r_0(P0T,lambd,eta):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    return r0\r\n\r\ndef HW_Mu_FrwdMeasure(P0T,lambd,eta,T):\r\n    # time-step needed for differentiation\r\n    dt = 0.0001    \r\n    f0T = lambda t: - (np.log(P0T(t+dt))-np.log(P0T(t-dt)))/(2*dt)\r\n    # Initial interest rate is a forward rate at time t->0\r\n    r0 = f0T(0.00001)\r\n    theta = HW_theta(lambd,eta,P0T)\r\n    zGrid = np.linspace(0.0,T,500)\r\n    \r\n    theta_hat =lambda t,T:  theta(t) + eta*eta / lambd *1.0/lambd * (np.exp(-lambd*(T-t))-1.0)\r\n    \r\n    temp =lambda z: theta_hat(z,T) * np.exp(-lambd*(T-z))\r\n    \r\n    r_mean = r0*np.exp(-lambd*T) + lambd * integrate.trapz(temp(zGrid),zGrid)\r\n    \r\n    return r_mean\r\n\r\ndef HWVar_r(lambd,eta,T):\r\n    return eta*eta/(2.0*lambd) *( 1.0-np.exp(-2.0*lambd *T))\r\n\r\ndef HWDensity(P0T,lambd,eta,T):\r\n    r_mean = HWMean_r(P0T,lambd,eta,T)\r\n    r_var = HWVar_r(lambd,eta,T)\r\n    return lambda x: st.norm.pdf(x,r_mean,np.sqrt(r_var))\r\n\r\ndef HW_SwapPrice(CP,notional,K,t,Ti,Tm,n,r_t,P0T,lambd,eta):\r\n    # CP- payer of receiver\r\n    # n- notional\r\n    # K- strike\r\n    # t- today's date\r\n    # Ti- beginning of the swap\r\n    # Tm- end of Swap\r\n    # n- number of dates payments between Ti and Tm\r\n    # r_t -interest rate at time t\r\n\r\n    if n == 1:\r\n        ti_grid =np.array([Ti,Tm])\r\n    else:\r\n        ti_grid = np.linspace(Ti,Tm,n)\r\n    tau = ti_grid[1]- ti_grid[0]\r\n    \r\n    # overwrite Ti if t>Ti\r\n    prevTi = ti_grid[np.where(ti_grid<t)]\r\n    if np.size(prevTi) > 0: #prevTi != []:\r\n        Ti = prevTi[-1]\r\n    \r\n    # Now we need to handle the case when some payments are already done\r\n    ti_grid = ti_grid[np.where(ti_grid>t)]          \r\n\r\n    temp= np.zeros(np.size(r_t));\r\n    \r\n    P_t_TiLambda = lambda Ti : HW_ZCB(lambd,eta,P0T,t,Ti,r_t)\r\n    \r\n    for (idx,ti) in enumerate(ti_grid):\r\n        if ti>Ti:\r\n            temp = temp + tau * P_t_TiLambda(ti)\r\n            \r\n    P_t_Ti = P_t_TiLambda(Ti)\r\n    P_t_Tm = P_t_TiLambda(Tm)\r\n    \r\n    if CP==OptionTypeSwap.PAYER:\r\n        swap = (P_t_Ti - P_t_Tm) - K * temp\r\n    elif CP==OptionTypeSwap.RECEIVER:\r\n        swap = K * temp- (P_t_Ti - P_t_Tm)\r\n    \r\n    return swap*notional\r\n\r\ndef Portfolio(P0T,r_t,lambd,eta):\r\n    \r\n            #IRSwap(CP,           notional,          K,   t,   Ti,  Tm,   n,      P0T):\r\n    value = HW_SwapPrice(OptionTypeSwap.RECEIVER,1000000,0.02,0.0, 0.0,  20,  20,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.PAYER, 500000,   0.01,0.0, 0.0,  10,  20,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.RECEIVER,25000,0.02,0.0, 0.0,  30,  60,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.PAYER,74000,0.005,0.0, 0.0,  5,  10,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.RECEIVER,254000,0.032,0.0, 0.0,  15,  10,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.RECEIVER,854000,0.01,0.0, 0.0,  7,  20,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.PAYER,900000,0.045,0.0, 0.0,  10,  20,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.PAYER,400000,0.02,0.0, 0.0,  10,  20,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.RECEIVER,1000000,0.01,0.0, 0.0,  14,  20,r_t,  P0T,lambd,eta) +\\\r\n            HW_SwapPrice(OptionTypeSwap.PAYER,115000,0.06,0.0, 0.0,  9,  10,r_t,  P0T,lambd,eta) \r\n            #HW_SwapPrice(CP,notional,K,t,Ti,Tm,n,r_t,P0T,lambd,eta)\r\n    return value\r\n\r\ndef mainCalculation():\r\n    NoOfPaths = 2000\r\n    NoOfSteps = 100\r\n    lambd     = 0.5\r\n    eta       = 0.03\r\n    \r\n    # We define a ZCB curve (obtained from the market)\r\n    P0T = lambda T: np.exp(-0.001*T)\r\n    r0 = HW_r_0(P0T,lambd,eta)\r\n    \r\n    # In this experiment we compare ZCB from the Market and Analytical expression\r\n    N = 25\r\n    T_end = 50\r\n    Tgrid= np.linspace(0,T_end,N)\r\n    \r\n    Exact = np.zeros([N,1])\r\n    Proxy= np.zeros ([N,1])\r\n    for i,Ti in enumerate(Tgrid):\r\n        Proxy[i] = HW_ZCB(lambd,eta,P0T,0.0,Ti,r0)\r\n        Exact[i] = P0T(Ti)\r\n        \r\n    plt.figure(1)\r\n    plt.grid()\r\n    plt.plot(Tgrid,Exact,'-k')\r\n    plt.plot(Tgrid,Proxy,'--r')\r\n    plt.legend([\"Analytcal ZCB\",\"Monte Carlo ZCB\"])\r\n    plt.title('P(0,T) from Monte Carlo vs. Analytical expression')\r\n    \r\n    \r\n    # Here we simulate the exposure profiles for a swap, using the HW model    \r\n    T_end = 20    \r\n    paths= GeneratePathsHWEuler(NoOfPaths,NoOfSteps,T_end ,P0T, lambd, eta)\r\n    r = paths[\"R\"]\r\n    timeGrid = paths[\"time\"]\r\n    dt = timeGrid[1]-timeGrid[0]\r\n    \r\n    # Here we compare the price of an option on a ZCB from Monte Carlo and Analytical expression    \r\n    M_t = np.zeros([NoOfPaths,NoOfSteps])\r\n            \r\n    for i in range(0,NoOfPaths):\r\n        M_t[i,:] = np.exp(np.cumsum(r[i,0:-1])*dt)\r\n        \r\n    # Compute exposure profile\r\n    r0 = r[0,0]\r\n    \r\n    stepSize = 10\r\n    V_M = np.zeros([NoOfPaths,NoOfSteps-stepSize])\r\n    \r\n    for i in range(0,NoOfSteps-stepSize):\r\n        dr = r[:,i + stepSize] - r[:,i]\r\n        V_t0 = Portfolio(P0T, r[:,0] + dr,lambd,eta)\r\n        V_M[:,i] = V_t0\r\n    \r\n    plt.figure(2)\r\n    V_t0_vec = np.matrix.flatten(V_M)\r\n    plt.hist(V_t0_vec,100)\r\n    plt.grid()\r\n    \r\n    print('Value V(t_0)= ',Portfolio(P0T,r[0,0],lambd,eta))\r\n        \r\n    # VaR calculation\r\n    alpha = 0.05\r\n    HVaR_estimate = np.quantile(V_t0_vec,alpha)\r\n    print('(H)VaR for alpha = ', alpha, ' is equal to=', HVaR_estimate)\r\n    \r\n    # Expected shortfal\r\n    condLosses = V_t0_vec[V_t0_vec < HVaR_estimate]\r\n    print('P&L which < VaR_alpha =',condLosses)\r\n    ES = np.mean(condLosses)\r\n    \r\n    print('Expected shortfal = ', ES)\r\n    \r\n    plt.plot(HVaR_estimate,0,'or')\r\n    plt.plot(ES,0,'ok')\r\n    plt.legend(['VaR','ES','P&L'])\r\n    \r\n    \r\nmainCalculation()"
  },
  {
    "path": "README.md",
    "content": "# Financial Engineering Course\nHere you will find materials for the course of \"Financial Engineering: Interest rates & xVA\"\n\nYouTube lectures you can find at:\nhttps://www.youtube.com/ComputationsInFinance\n\nThe course is based on the book: \n\"Mathematical Modeling and Computation in Finance: With Exercises and Python and MATLAB Computer Codes\", \nby C.W. Oosterlee and L.A. Grzelak, World Scientific Publishing, 2019.\n\nThe website of the book is:\nhttps://QuantFinanceBook.com\n\nPersonal website of the author (with more courses) is:\nhttps://LechGrzelak.com\n\nContent of the course:\n\nLecture 1- Introduction & Details Regarding the Course\\\nLecture 2- Understanding of Filtrations and Measures\\\nLecture 3- The HJM Framework\\\nLecture 4- Yield Curve Dynamics under Short Rate\\\nLecture 5- Interest Rate Products\\\nLecture 6- Construction of Yield Curve and Multi-Curves\\\nLecture 7- Pricing of Swaptions and Negative Interest Rates\\\nLecture 8- Mortgages and Prepayments\\\nLecture 9- Hybrid Models and Stochastic Interest Rates\\\nLecture 10- Foreign Exchange (FX) and Inflation\\\nLecture 11- Market Models and Convexity Adjustments\\\nLecture 12- Valuation Adjustments- xVA (CVA, BCVA and FVA)\\\nLecture 13- Value-at-Risk and Expected Shortfall\\\nLecture 14- The Summary of the Course\\\n"
  }
]