def getGaussianSobol(numPoints, dimension): ''' Sobol Gaussian quasi random points generator based on graycode order. The generated points follow a normal distribution. ''' points = getUniformSobol(numPoints, dimension) for i in range(numPoints): for j in range(dimension): points[i, j] = norminvcdf(points[i, j]) return points
def LMMSimulateFwdsMF(numForwards, numFactors, numPaths, numeraireIndex, fwd0, lambdas, taus, useSobol, seed): ''' Multi-Factor Arbitrage-free simulation of forward Libor curves in the spot measure following Hull Page 768. Given an initial forward curve, volatility factor term structure. The 3D matrix of forward rates by path, time and forward point is returned. ''' np.random.seed(seed) if len(lambdas) != numFactors: raise FinError("Lambda does not have the right number of factors") if len(lambdas[0]) != numForwards: raise FinError("Lambda does not have the right number of forwards") # Even number of paths for antithetics numPaths = 2 * int(numPaths / 2) halfNumPaths = int(numPaths / 2) fwd = np.empty((numPaths, numForwards, numForwards)) fwdB = np.zeros(numForwards) numTimes = numForwards if useSobol == 1: numDimensions = numTimes * numFactors rands = getUniformSobol(halfNumPaths, numDimensions) gMatrix = np.empty((numPaths, numTimes, numFactors)) for iPath in range(0, halfNumPaths): for j in range(0, numTimes): for q in range(0, numFactors): col = j * numFactors + q u = rands[iPath, col] g = norminvcdf(u) gMatrix[iPath, j, q] = g gMatrix[iPath + halfNumPaths, j, q] = -g elif useSobol == 0: gMatrix = np.empty((numPaths, numTimes, numFactors)) for iPath in range(0, halfNumPaths): for j in range(0, numTimes): for q in range(0, numFactors): g = np.random.normal() gMatrix[iPath, j, q] = g gMatrix[iPath + halfNumPaths, j, q] = -g else: raise FinError("Use Sobol must be 0 or 1.") for iPath in range(0, numPaths): # Initial value of forward curve at time 0 for iFwd in range(0, numForwards): fwd[iPath, 0, iFwd] = fwd0[iFwd] for j in range(0, numForwards - 1): # TIME LOOP dtj = taus[j] sqrtdtj = np.sqrt(dtj) for k in range(j, numForwards): # FORWARDS LOOP muA = 0.0 for i in range(j + 1, k + 1): fi = fwd[iPath, j, i] ti = taus[i] zz = 0.0 for q in range(0, numFactors): zij = lambdas[q][i - j] zkj = lambdas[q][k - j] zz += zij * zkj muA += fi * ti * zz / (1.0 + fi * ti) itoTerm = 0.0 for q in range(0, numFactors): itoTerm += lambdas[q][k - j] * lambdas[q][k - j] randomTerm = 0.0 for q in range(0, numFactors): wq = gMatrix[iPath, j, q] randomTerm += lambdas[q][k - j] * wq randomTerm *= sqrtdtj x = np.exp(muA * dtj - 0.5 * itoTerm * dtj + randomTerm) fwdB[k] = fwd[iPath, j, k] * x muB = 0.0 for i in range(j + 1, k + 1): fi = fwdB[k] ti = taus[i] zz = 0.0 for q in range(0, numFactors): zij = lambdas[q][i - j] zkj = lambdas[q][k - j] zz += zij * zkj muB += fi * ti * zz / (1.0 + fi * ti) muC = 0.5 * (muA + muB) x = np.exp(muC * dtj - 0.5 * itoTerm * dtj + randomTerm) fwd[iPath, j + 1, k] = fwd[iPath, j, k] * x return fwd
def LMMSimulateFwds1F(numForwards, numPaths, numeraireIndex, fwd0, gammas, taus, useSobol, seed): ''' One factor Arbitrage-free simulation of forward Libor curves in the spot measure following Hull Page 768. Given an initial forward curve, volatility term structure. The 3D matrix of forward rates by path, time and forward point is returned. This function is kept mainly for its simplicity and speed. NB: The Gamma volatility has an initial entry of zero. This differs from Hull's indexing by one and so is why I do not subtract 1 from the index as Hull does in his equation 32.14. The Number of Forwards is the number of points on the initial curve to the trade maturity date. But be careful: a cap that matures in 10 years with quarterly caplets has 40 forwards BUT the last forward to reset occurs at 9.75 years. You should not simulate beyond this time. If you give the model 10 years as in the Hull examples, you need to simulate 41 (or in this case 11) forwards as the final cap or ratchet has its reset in 10 years. ''' if len(gammas) != numForwards: raise FinError("Gamma vector does not have right number of forwards") if len(fwd0) != numForwards: raise FinError("The length of fwd0 is not equal to numForwards") if len(taus) != numForwards: raise FinError("The length of Taus is not equal to numForwards") np.random.seed(seed) # Even number of paths for antithetics numPaths = 2 * int(numPaths / 2) halfNumPaths = int(numPaths / 2) fwd = np.empty((numPaths, numForwards, numForwards)) fwdB = np.zeros(numForwards) numTimes = numForwards if useSobol == 1: numDimensions = numTimes rands = getUniformSobol(halfNumPaths, numDimensions) gMatrix = np.empty((numPaths, numTimes)) for iPath in range(0, halfNumPaths): for j in range(0, numTimes): u = rands[iPath, j] g = norminvcdf(u) gMatrix[iPath, j] = g gMatrix[iPath + halfNumPaths, j] = -g elif useSobol == 0: gMatrix = np.empty((numPaths, numTimes)) for iPath in range(0, halfNumPaths): for j in range(0, numTimes): g = np.random.normal() gMatrix[iPath, j] = g gMatrix[iPath + halfNumPaths, j] = -g else: raise FinError("Use Sobol must be 0 or 1") for iPath in prange(0, numPaths): # Initial value of forward curve at time 0 for iFwd in range(0, numForwards): fwd[iPath, 0, iFwd] = fwd0[iFwd] for j in range(0, numForwards - 1): # TIME LOOP dtj = taus[j] sqrtdtj = np.sqrt(dtj) w = gMatrix[iPath, j] for k in range(j, numForwards): # FORWARDS LOOP zkj = gammas[k - j] muA = 0.0 for i in range(j + 1, k + 1): fi = fwd[iPath, j, i] zij = gammas[i - j] ti = taus[i] muA += zkj * fi * ti * zij / (1.0 + fi * ti) # predictor corrector x = np.exp(muA * dtj - 0.5 * (zkj**2) * dtj + zkj * w * sqrtdtj) fwdB[k] = fwd[iPath, j, k] * x muB = 0.0 for i in range(j + 1, k + 1): fi = fwdB[k] zij = gammas[i - j] ti = taus[i] muB += zkj * fi * ti * zij / (1.0 + fi * ti) muC = 0.5 * (muA + muB) x = np.exp(muC * dtj - 0.5 * (zkj**2) * dtj + zkj * w * sqrtdtj) fwd[iPath, j + 1, k] = fwd[iPath, j, k] * x return fwd
def test_FinMath(): xValues = np.linspace(-6.0, 6.0, 13) start = time.time() testCases.header("FUNCTION", "X", "Y") for x in xValues: y = normcdf(x, 1) testCases.print("NORMCDF1", x, y) end = time.time() duration = end - start testCases.header("LABEL", "TIME") testCases.print("Fast N(x) takes ", duration) ########################################################################## testCases.header("FUNCTION", "X", "Y") start = time.time() for x in xValues: y = normcdf(x, 2) testCases.print("NORMCDF2", x, y) end = time.time() duration = end - start testCases.header("LABEL", "TIME") testCases.print("Slow N(x) takes ", duration) ########################################################################## testCases.header("FUNCTION", "X", "Y") start = time.time() for x in xValues: y = normcdf(x, 3) testCases.print("NORMCDF3", x, y) end = time.time() duration = end - start testCases.header("LABEL", "TIME") testCases.print("Trap N(x) takes ", duration) ########################################################################## xValues = np.linspace(-6.0, 6.0, 20) testCases.header("X", "Y1", "Y2", "Y3", "DIFF1", "DIFF2") for x in xValues: y1 = normcdf(x, 1) y2 = normcdf(x, 2) y3 = normcdf(x, 3) diff1 = y3 - y1 diff2 = y3 - y2 testCases.print(x, y1, y2, y3, diff1, diff2) ########################################################################## xValues = np.linspace(-6.0, 6.0, 20) testCases.header("X", "Y1", "Y2", "INV_Y1", "INV_Y2", "DIFF1", "DIFF2") for x_in in xValues: y1 = normcdf(x_in, 1) y2 = normcdf(x_in, 2) x_out1 = norminvcdf(y1) x_out2 = norminvcdf(y2) diff1 = x_out1 - x_in diff2 = x_out2 - x_in testCases.print(x, y1, y2, x_out1, x_out2, diff1, diff2)