def LMMFlexiCapPricer(maxCaplets, K, numPeriods, numPaths, fwd0, fwds, taus): ''' Price a flexicap using the simulated Libor rates.''' maxPaths = len(fwds) maxForwards = len(fwds[0][0]) if numPeriods > maxForwards: raise FinError("NumPeriods > numForwards") if numPaths > maxPaths: raise FinError("NumPaths > MaxPaths") discFactor = np.zeros(maxForwards) numeraire = np.zeros(maxForwards) flexiCaplets = np.zeros(maxForwards) flexiCapletValues = np.zeros(maxForwards) # Set up initial term structure discFactor[0] = 1.0 / (1.0 + fwd0[0] * taus[0]) for ix in range(1, maxForwards): discFactor[ix] = discFactor[ix - 1] / (1.0 + fwd0[ix] * taus[ix]) for iPath in range(0, numPaths): periodRoll = 1.0 libor = fwds[iPath, 0, 0] flexiCaplets[0] = 0.0 numCapletsLeft = maxCaplets for j in range(1, numPeriods): # TIME LOOP libor = fwds[iPath, j, j] if j == 1: if libor > K and numCapletsLeft > 0: flexiCaplets[j] = max(libor - K, 0.0) * taus[j] numCapletsLeft -= 1 numeraire[0] = 1.0 / discFactor[0] else: if libor > K and numCapletsLeft > 0: flexiCaplets[j] = max(libor - K, 0.0) * taus[j] numCapletsLeft -= 1 periodRoll = (1.0 + libor * taus[j]) numeraire[j] = numeraire[j - 1] * periodRoll for iFwd in range(0, numPeriods): flexiCapletValues[iFwd] += flexiCaplets[iFwd] / numeraire[iFwd] for iFwd in range(0, numPeriods): flexiCapletValues[iFwd] /= numPaths flexiCapValue = 0.0 for iFwd in range(0, numPeriods): flexiCapValue += flexiCapletValues[iFwd] return flexiCapValue
def LMMSwaptionPricer(strike, a, b, numPaths, fwd0, fwds, taus, isPayer): ''' Function to price a European swaption using the simulated forward curves. ''' maxPaths = len(fwds) maxForwards = len(fwds[0]) if a > maxForwards: raise FinError("NumPeriods > numForwards") if a >= b: raise FinError("Swap maturity is before expiry date") if numPaths > maxPaths: raise FinError("NumPaths > MaxPaths") discFactor = np.zeros(maxForwards) # pv01 = np.zeros(maxForwards) # Set up initial term structure discFactor[0] = 1.0 / (1.0 + fwd0[0] * taus[0]) for ix in range(1, b): discFactor[ix] = discFactor[ix - 1] / (1.0 + fwd0[ix] * taus[ix]) sumPayRecSwaption = 0.0 for iPath in range(0, numPaths): numeraire = 1.0 for k in range(0, a): numeraire *= (1.0 + taus[k] * fwds[iPath, k, k]) pv01 = 0.0 df = 1.0 # Value the swap as if we were at time a with forward curve known for k in range(a, b): f = fwds[iPath, a, k] tau = taus[k] df = df / (1.0 + tau * f) pv01 = pv01 + tau * df fwdSwapRate = (1.0 - df) / pv01 if isPayer == 1: payRecSwaption = max(fwdSwapRate - strike, 0.0) * pv01 elif isPayer == 0: payRecSwaption = max(strike - fwdSwapRate, 0.0) * pv01 else: raise FinError("Unknown payRecSwaption value - must be 0 or 1") sumPayRecSwaption += payRecSwaption / (abs(numeraire) + 1e-10) payRecPrice = sumPayRecSwaption / numPaths return payRecPrice
def LMMCapFlrPricer(numForwards, numPaths, K, fwd0, fwds, taus, isCap): ''' Function to price a strip of cap or floorlets in accordance with the simulated forward curve dynamics. ''' maxPaths = len(fwds) maxForwards = len(fwds[0]) if numForwards > maxForwards: raise FinError("NumForwards > maxForwards") if numPaths > maxPaths: raise FinError("NumPaths > MaxPaths") discFactor = np.zeros(numForwards) capFlrLets = np.zeros(numForwards - 1) capFlrLetValues = np.zeros(numForwards - 1) numeraire = np.zeros(numForwards) for iPath in range(0, numPaths): periodRoll = 1.0 libor = fwds[iPath, 0, 0] capFlrLets[0] = max(K - libor, 0) * taus[0] # Now loop over the caplets starting with one that fixes immediately # but which may have intrinsic value that cannot be ignored. for j in range(0, numForwards): libor = fwds[iPath, j, j] if j == 1: if isCap == 0: capFlrLets[j] = max(K - libor, 0) * taus[j] else: capFlrLets[j] = max(libor - K, 0) * taus[j] numeraire[0] = 1.0 / discFactor[0] else: if isCap == 1: capFlrLets[j] = max(libor - K, 0) * taus[j] elif isCap == 0: capFlrLets[j] = max(K - libor, 0) * taus[j] else: raise FinError("isCap should be 0 or 1") periodRoll = (1.0 + libor * taus[j]) numeraire[j] = numeraire[j - 1] * periodRoll for iFwd in range(0, numForwards): denom = abs(numeraire[iFwd]) + 1e-12 capFlrLetValues[iFwd] += capFlrLets[iFwd] / denom for iFwd in range(0, numForwards): capFlrLetValues[iFwd] /= numPaths return capFlrLetValues
def LMMStickyCapletPricer(spread, numPeriods, numPaths, fwd0, fwds, taus): ''' Price a sticky cap using the simulated Libor rates. ''' maxPaths = len(fwds) maxForwards = len(fwds[0][0]) if numPeriods > maxForwards: raise FinError("NumPeriods > numForwards") if numPaths > maxPaths: raise FinError("NumPaths > MaxPaths") discFactor = np.zeros(maxForwards) numeraire = np.zeros(maxForwards) stickyCaplets = np.zeros(maxForwards) stickyCapletValues = np.zeros(maxForwards) # Set up initial term structure discFactor[0] = 1.0 / (1.0 + fwd0[0] * taus[0]) for ix in range(1, maxForwards): discFactor[ix] = discFactor[ix - 1] / (1.0 + fwd0[ix] * taus[ix]) for iPath in range(0, numPaths): periodRoll = 1.0 libor = fwds[iPath, 0, 0] stickyCaplets[0] = 0.0 K = libor for j in range(1, numPeriods): # TIME LOOP prevLibor = libor K = min(prevLibor, K) + spread libor = fwds[iPath, j, j] if j == 1: stickyCaplets[j] = max(libor - K, 0.0) * taus[j] numeraire[0] = 1.0 / discFactor[0] else: stickyCaplets[j] = max(libor - K, 0.0) * taus[j] periodRoll = (1.0 + libor * taus[j]) numeraire[j] = numeraire[j - 1] * periodRoll for iFwd in range(0, numPeriods): stickyCapletValues[iFwd] += stickyCaplets[iFwd] / numeraire[iFwd] for iFwd in range(0, numPeriods): stickyCapletValues[iFwd] /= numPaths return stickyCapletValues
def LMMPriceCapsBlack(fwd0, volCaplet, p, K, taus): ''' Price a strip of capfloorlets using Black's model using the time grid of the LMM model. The prices can be compared with the LMM model prices. ''' caplet = np.zeros(p + 1) discFwd = np.zeros(p + 1) if K <= 0.0: raise FinError("Negative strike not allowed.") # Set up initial term structure discFwd[0] = 1.0 / (1.0 + fwd0[0] * taus[0]) for i in range(1, p): discFwd[i] = discFwd[i - 1] / (1.0 + fwd0[i] * taus[i]) # Price ATM caplets texp = 0.0 for i in range(1, p): # 1 to p-1 K = fwd0[i] texp += taus[i] vol = volCaplet[i] F = fwd0[i] d1 = (np.log(F / K) + vol * vol * texp / 2.0) / vol / np.sqrt(texp) d2 = d1 - vol * np.sqrt(texp) caplet[i] = (F * N(d1) - K * N(d2)) * taus[i] * discFwd[i] return caplet
def LMMSimSwaptionVol(a, b, fwd0, fwds, taus): ''' Calculates the swap rate volatility using the forwards generated in the simulation to see how it compares to Rebonatto estimate. ''' numPaths = len(fwds) numForwards = len(fwds[0]) if a > numForwards: raise FinError("NumPeriods > numForwards") if a >= b: raise FinError("Swap maturity is before expiry date") fwdSwapRateMean = 0.0 fwdSwapRateVar = 0.0 for iPath in prange(0, numPaths): numeraire = 1.0 for k in range(0, a): numeraire *= (1.0 + taus[k] * fwds[iPath, k, k]) pv01 = 0.0 df = 1.0 for k in range(a, b): f = fwds[iPath, a, k] tau = taus[k] df = df / (1.0 + tau * f) pv01 = pv01 + tau * df fwdSwapRate = (1.0 - df) / pv01 fwdSwapRateMean += fwdSwapRate fwdSwapRateVar += fwdSwapRate**2 taua = 0.0 for i in range(0, a): taua += taus[i] fwdSwapRateMean /= numPaths fwdSwapRateVar = fwdSwapRateVar / numPaths - fwdSwapRateMean**2 fwdSwapRateVol = np.sqrt(fwdSwapRateVar / taua) fwdSwapRateVol /= fwdSwapRateMean return fwdSwapRateVol
def print(self, *args): ''' Print comma separated output to GOLDEN or COMPARE directory. ''' if self._mode == FinTestCaseMode.DEBUG_TEST_CASES: print(args) return if not self._foldersExist: print("Cannot print as GOLDEN and COMPARE folders don't exist") return if self._headerFields is None: print("ERROR: Need to set header fields before printing results") elif len(self._headerFields) != len(args): n1 = len(self._headerFields) n2 = len(args) raise FinError("ERROR: Number of data columns is " + str(n1) + " but must equal " + str(n2) + " to align with headers.") if self._mode == FinTestCaseMode.SAVE_TEST_CASES: filename = self._goldenFilename else: filename = self._compareFilename f = open(filename, 'a') f.write("RESULTS,") for arg in args: if isinstance(arg, float): f.write("%10.8f" % (arg)) else: f.write(str(arg)) f.write(",") f.write("\n") f.close()
def __init__(self, moduleName, mode): ''' Create the TestCase given the module name and whether we are in GOLDEN or COMPARE mode. ''' rootFolder, moduleFilename = split(moduleName) self._carefulMode = False self._verbose = False if mode in FinTestCaseMode: self._mode = mode else: raise FinError("Unknown TestCase Mode") if mode == FinTestCaseMode.DEBUG_TEST_CASES: # Don't do anything self._verbose = True return self._moduleName = moduleFilename[0:-3] self._foldersExist = True self._rootFolder = rootFolder self._headerFields = None # print("Root folder:",self._rootFolder) # print("Modulename:",self._moduleName) self._goldenFolder = join(rootFolder, "golden") self._differencesFolder = join(rootFolder, "differences") if exists(self._goldenFolder) is False: print("Looking for:", self._goldenFolder) print("GOLDEN Folder DOES NOT EXIST. You must create it. Exiting") self._foldersExist = False return None self._compareFolder = join(rootFolder, "compare") if exists(self._compareFolder) is False: print("Looking for:", self._compareFolder) print("COMPARE Folder DOES NOT EXIST. You must create it. Exiting") self._foldersExist = False return None self._goldenFilename = join(self._goldenFolder, self._moduleName + "_GOLDEN.log") self._compareFilename = join(self._compareFolder, self._moduleName + "_COMPARE.log") self._differencesFilename = join(self._differencesFolder, self._moduleName + "_DIFFS.log") if self._mode == FinTestCaseMode.SAVE_TEST_CASES: print("GOLDEN Test Case Creation for module:", moduleFilename) if exists(self._goldenFilename) and self._carefulMode: overwrite = input("File " + self._goldenFilename + " exists. Overwrite (Y/N) ?") if overwrite == "N": print("Not overwriting. Saving test cases failed.") return elif overwrite == "Y": print("Overwriting existing file...") creationTime = time.strftime("%Y%m%d_%H%M%S") f = open(self._goldenFilename, 'w') f.write("File Created on:" + creationTime) f.write("\n") f.close() else: print("GENERATING NEW OUTPUT FOR MODULE", moduleFilename, "FOR COMPARISON.") if exists(self._compareFilename) and self._carefulMode: overwrite = input("File " + self._compareFilename + " exists. Overwrite (Y/N) ?") if overwrite == "N": print("Not overwriting. Saving test cases failed.") return elif overwrite == "Y": print("Overwriting existing file...") # print("Creating empty file",self._compareFilename) creationTime = time.strftime("%Y%m%d_%H%M%S") f = open(self._compareFilename, 'w') f.write("File Created on:" + creationTime) f.write("\n") f.close()
def LMMSwapPricer(cpn, numPeriods, numPaths, fwd0, fwds, taus): ''' Function to reprice a basic swap using the simulated forward Libors. ''' maxPaths = len(fwds) maxForwards = len(fwds[0]) if numPeriods > maxForwards: raise FinError("NumPeriods > numForwards") if numPaths > maxPaths: raise FinError("NumPaths > MaxPaths") discFactor = np.zeros(maxForwards) numeraire = np.zeros(maxForwards) sumFixed = 0.0 sumFloat = 0.0 fixedFlows = np.zeros(maxForwards) floatFlows = np.zeros(maxForwards) # Set up initial term structure discFactor[0] = 1.0 / (1.0 + fwd0[0] * taus[0]) for ix in range(1, maxForwards): discFactor[ix] = discFactor[ix - 1] / (1.0 + fwd0[ix] * taus[ix]) for iPath in range(0, numPaths): periodRoll = 1.0 libor = fwds[iPath, 0, 0] floatFlows[0] = libor * taus[0] fixedFlows[0] = cpn * taus[0] numeraire[0] = 1.0 / discFactor[0] for j in range(1, numPeriods): # TIME LOOP libor = fwds[iPath, j, j] if j == 1: fixedFlows[j] = cpn * taus[j] floatFlows[j] = libor * taus[j] else: fixedFlows[j] = fixedFlows[j - 1] * periodRoll + cpn * taus[j] floatFlows[j] = floatFlows[j - 1] * periodRoll + libor * taus[j] periodRoll = (1.0 + libor * taus[j]) numeraire[j] = numeraire[j - 1] * periodRoll for iFwd in range(0, numPeriods): sumFloat += floatFlows[iFwd] / numeraire[iFwd] sumFixed += fixedFlows[iFwd] / numeraire[iFwd] sumFloat /= numPaths sumFixed /= numPaths v = sumFixed - sumFloat pv01 = sumFixed / cpn swapRate = sumFloat / pv01 print("FLOAT LEG:", sumFloat) print("FIXED LEG:", sumFixed) print("SWAP RATE:", swapRate) print("NET VALUE:", v) return v
def LMMSwaptionVolApprox(a, b, fwd0, taus, zetas, rho): ''' Implements Rebonato's approximation for the swap rate volatility to be used when pricing a swaption that expires in period a for a swap maturing at the end of period b taking into account the forward volatility term structure (zetas) and the forward-forward correlation matrix rho.. ''' numPeriods = len(fwd0) # if len(taus) != numPeriods: # raise FinError("Tau vector must have length" + str(numPeriods)) # if len(zetas) != numPeriods: # raise FinError("Tau vector must have length" + str(numPeriods)) # if len(rho) != numPeriods: # raise FinError("Rho matrix must have length" + str(numPeriods)) # if len(rho[0]) != numPeriods: # raise FinError("Rho matrix must have height" + str(numPeriods)) if b > numPeriods: raise FinError("Swap maturity beyond numPeriods.") if a == b: raise FinError("Swap maturity on swap expiry date") p = np.zeros(numPeriods) p[0] = 1.0 / (1.0 + fwd0[0] * taus[0]) for ix in range(1, numPeriods): p[ix] = p[ix - 1] / (1.0 + fwd0[ix] * taus[ix]) wts = np.zeros(numPeriods) pv01ab = 0.0 for k in range(a + 1, b): pv01ab += taus[k] * p[k] sab = (p[a] - p[b - 1]) / pv01ab for i in range(a, b): wts[i] = taus[i] * p[i] / pv01ab swaptionVar = 0.0 for i in range(a, b): for j in range(a, b): wti = wts[i] wtj = wts[j] fi = fwd0[i] fj = fwd0[j] intsigmaij = 0.0 for k in range(0, a): intsigmaij += zetas[i] * zetas[j] * taus[k] term = wti * wtj * fi * fj * rho[i][j] * intsigmaij / (sab**2) swaptionVar += term taua = 0.0 for i in range(0, a): taua += taus[i] taub = 0.0 for i in range(0, b): taub += taus[i] swaptionVol = np.sqrt(swaptionVar / taua) return swaptionVol
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