Source code for btQuant.bootstrap

import numpy as np

[docs] def curve(x, y, z=None, method='cubic', nPoints=100): """ Bootstrap curves using interpolation. Parameters: x: maturities/tenors (1D array) y: rates/prices (1D array) z: optional cash flows/coupons (1D array) method: 'linear', 'cubic', 'pchip' nPoints: number of interpolation points Returns: dict with 'x' and 'y' arrays """ x = np.asarray(x) y = np.asarray(y) if method == 'linear': xNew = np.linspace(np.min(x), np.max(x), nPoints) yNew = np.interp(xNew, x, y) elif method == 'cubic': xNew = np.linspace(np.min(x), np.max(x), nPoints) yNew = _cubicSpline(x, y, xNew) elif method == 'pchip': xNew = np.linspace(np.min(x), np.max(x), nPoints) yNew = _pchip(x, y, xNew) else: raise ValueError("method must be 'linear', 'cubic', or 'pchip'") return {'x': xNew, 'y': yNew}
def _cubicSpline(x, y, xNew): """Cubic spline interpolation.""" n = len(x) h = np.diff(x) alpha = np.zeros(n - 1) for i in range(1, n - 1): alpha[i] = (3 / h[i]) * (y[i + 1] - y[i]) - (3 / h[i - 1]) * (y[i] - y[i - 1]) l = np.ones(n) mu = np.zeros(n) z = np.zeros(n) for i in range(1, n - 1): l[i] = 2 * (x[i + 1] - x[i - 1]) - h[i - 1] * mu[i - 1] mu[i] = h[i] / l[i] z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i] c = np.zeros(n) b = np.zeros(n - 1) d = np.zeros(n - 1) for j in range(n - 2, -1, -1): c[j] = z[j] - mu[j] * c[j + 1] b[j] = (y[j + 1] - y[j]) / h[j] - h[j] * (c[j + 1] + 2 * c[j]) / 3 d[j] = (c[j + 1] - c[j]) / (3 * h[j]) yNew = np.zeros(len(xNew)) for i, xi in enumerate(xNew): j = np.searchsorted(x[1:], xi) j = min(j, n - 2) dx = xi - x[j] yNew[i] = y[j] + b[j] * dx + c[j] * dx**2 + d[j] * dx**3 return yNew def _pchip(x, y, xNew): """Piecewise Cubic Hermite Interpolating Polynomial.""" n = len(x) h = np.diff(x) delta = np.diff(y) / h m = np.zeros(n) m[0] = delta[0] m[-1] = delta[-1] for i in range(1, n - 1): if delta[i - 1] * delta[i] > 0: w1 = 2 * h[i] + h[i - 1] w2 = h[i] + 2 * h[i - 1] m[i] = (w1 + w2) / (w1 / delta[i - 1] + w2 / delta[i]) else: m[i] = 0 yNew = np.zeros(len(xNew)) for i, xi in enumerate(xNew): j = np.searchsorted(x[1:], xi) j = min(j, n - 2) t = (xi - x[j]) / h[j] yNew[i] = (y[j] * (1 + 2 * t) * (1 - t)**2 + y[j + 1] * (3 - 2 * t) * t**2 + m[j] * h[j] * t * (1 - t)**2 + m[j + 1] * h[j] * t**2 * (t - 1)) return yNew
[docs] def surface(x, y, z, method='linear', gridSize=(100, 100)): """ Bootstrap 2D surfaces (e.g., volatility surfaces). Parameters: x: first dimension (e.g., maturities) y: second dimension (e.g., strikes) z: values at (x, y) points method: 'linear', 'cubic' gridSize: (nx, ny) tuple Returns: dict with 'X', 'Y', 'Z' arrays """ x = np.asarray(x) y = np.asarray(y) z = np.asarray(z) xGrid = np.linspace(np.min(x), np.max(x), gridSize[0]) yGrid = np.linspace(np.min(y), np.max(y), gridSize[1]) X, Y = np.meshgrid(xGrid, yGrid) if method == 'linear': Z = _bilinearInterp(x, y, z, X.flatten(), Y.flatten()) Z = Z.reshape(gridSize[1], gridSize[0]) elif method == 'cubic': Z = _bicubicInterp(x, y, z, X.flatten(), Y.flatten()) Z = Z.reshape(gridSize[1], gridSize[0]) else: raise ValueError("method must be 'linear' or 'cubic'") return {'X': X, 'Y': Y, 'Z': Z}
def _bilinearInterp(x, y, z, xi, yi): """Bilinear interpolation for scattered data.""" zi = np.zeros(len(xi)) for i in range(len(xi)): dists = np.sqrt((x - xi[i])**2 + (y - yi[i])**2) if np.min(dists) < 1e-10: zi[i] = z[np.argmin(dists)] else: weights = 1.0 / (dists + 1e-10) weights /= np.sum(weights) zi[i] = np.sum(weights * z) return zi def _bicubicInterp(x, y, z, xi, yi): """Bicubic interpolation approximation.""" return _bilinearInterp(x, y, z, xi, yi)
[docs] def zeroRateCurve(maturities, prices, method='linear', nPoints=100): """ Bootstrap zero rate curve from bond prices. Parameters: maturities: bond maturities (years) prices: bond prices method: interpolation method nPoints: number of points Returns: dict with 'maturities' and 'zeroRates' """ maturities = np.asarray(maturities) prices = np.asarray(prices) zeroRates = -np.log(prices) / maturities return curve(maturities, zeroRates, method=method, nPoints=nPoints)
[docs] def forwardCurve(maturities, zeroRates, method='linear', nPoints=100): """ Derive forward rate curve from zero rates. Parameters: maturities: maturities zeroRates: zero rates method: interpolation method nPoints: number of points Returns: dict with 'maturities' and 'forwardRates' """ maturities = np.asarray(maturities) zeroRates = np.asarray(zeroRates) forwardRates = np.zeros(len(maturities) - 1) for i in range(len(maturities) - 1): t1 = maturities[i] t2 = maturities[i + 1] r1 = zeroRates[i] r2 = zeroRates[i + 1] forwardRates[i] = (r2 * t2 - r1 * t1) / (t2 - t1) forwardMaturities = (maturities[:-1] + maturities[1:]) / 2 return curve(forwardMaturities, forwardRates, method=method, nPoints=nPoints)
[docs] def discountCurve(maturities, zeroRates, method='linear', nPoints=100): """ Derive discount factor curve from zero rates. Parameters: maturities: maturities zeroRates: zero rates method: interpolation method nPoints: number of points Returns: dict with 'maturities' and 'discountFactors' """ maturities = np.asarray(maturities) zeroRates = np.asarray(zeroRates) discountFactors = np.exp(-zeroRates * maturities) return curve(maturities, discountFactors, method=method, nPoints=nPoints)
[docs] def yieldCurve(maturities, prices, coupons=None, method='linear', nPoints=100): """ Bootstrap yield curve from bond data. Parameters: maturities: bond maturities prices: bond prices coupons: coupon rates (optional) method: interpolation method nPoints: number of points Returns: dict with 'maturities' and 'yields' """ maturities = np.asarray(maturities) prices = np.asarray(prices) if coupons is None: coupons = np.zeros(len(maturities)) else: coupons = np.asarray(coupons) yields = np.zeros(len(maturities)) for i in range(len(maturities)): ytm = coupons[i] for _ in range(50): pv = sum(coupons[i] / (1 + ytm)**t for t in range(1, int(maturities[i]) + 1)) pv += 1.0 / (1 + ytm)**maturities[i] diff = pv - prices[i] if abs(diff) < 1e-6: break dur = sum(t * coupons[i] / (1 + ytm)**(t + 1) for t in range(1, int(maturities[i]) + 1)) dur += maturities[i] / (1 + ytm)**(maturities[i] + 1) ytm -= diff / dur yields[i] = ytm return curve(maturities, yields, method=method, nPoints=nPoints)
[docs] def volSurface(maturities, strikes, impliedVols, method='linear', gridSize=(50, 50)): """ Bootstrap implied volatility surface. Parameters: maturities: option maturities strikes: strike prices impliedVols: implied volatilities method: interpolation method gridSize: grid dimensions Returns: dict with 'maturities', 'strikes', 'vols' """ return surface(maturities, strikes, impliedVols, method=method, gridSize=gridSize)
[docs] def creditCurve(maturities, cdsSpreads, recoveryRate=0.4, method='linear', nPoints=100): """ Bootstrap credit default swap curve. Parameters: maturities: CDS maturities cdsSpreads: CDS spreads (in basis points) recoveryRate: recovery rate assumption method: interpolation method nPoints: number of points Returns: dict with 'maturities' and 'defaultProbabilities' """ maturities = np.asarray(maturities) cdsSpreads = np.asarray(cdsSpreads) / 10000 hazardRates = cdsSpreads / (1 - recoveryRate) defaultProbs = 1 - np.exp(-hazardRates * maturities) return curve(maturities, defaultProbs, method=method, nPoints=nPoints)
[docs] def fxForwardCurve(spot, domesticRates, foreignRates, maturities, method='linear', nPoints=100): """ Bootstrap FX forward curve. Parameters: spot: spot FX rate domesticRates: domestic interest rates foreignRates: foreign interest rates maturities: forward maturities method: interpolation method nPoints: number of points Returns: dict with 'maturities' and 'forwardRates' """ domesticRates = np.asarray(domesticRates) foreignRates = np.asarray(foreignRates) maturities = np.asarray(maturities) forwardRates = spot * np.exp((domesticRates - foreignRates) * maturities) return curve(maturities, forwardRates, method=method, nPoints=nPoints)
[docs] def inflationCurve(maturities, breakevens, realRates, method='linear', nPoints=100): """ Bootstrap inflation curve from breakeven rates. Parameters: maturities: maturities breakevens: breakeven inflation rates realRates: real interest rates method: interpolation method nPoints: number of points Returns: dict with 'maturities' and 'inflationRates' """ breakevens = np.asarray(breakevens) realRates = np.asarray(realRates) maturities = np.asarray(maturities) inflationRates = breakevens - realRates return curve(maturities, inflationRates, method=method, nPoints=nPoints)