├── QuantLibWrapper ├── __init__.py ├── Regression.py ├── ThetaMethod.py ├── Helpers.py ├── BermudanOption.py ├── MCSimulation.py ├── YieldCurve.py ├── Payoffs.py ├── BermudanSwaption.py ├── PDESolver.py ├── Swap.py ├── SabrModel.py ├── DensityIntegrations.py ├── HullWhiteModel.py ├── AMCSolver.py └── Swaption.py ├── README.md ├── .gitignore ├── testYieldCurve.py ├── testSwapPricing.py ├── testHullWhiteModelPaths.py ├── testSABRModelSmileDynamics.py ├── testSABRModelStaticSmile.py ├── testHullWhiteBermudan.py ├── testHullWhiteModelVolatilities.py ├── testHullWhiteModel.py ├── testBermudanSwaption.py └── LICENSE /QuantLibWrapper/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuanLibPython 2 | Example Python scripts for interest rate modelling and QuantLib usage 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | __pycache__/ 3 | QuantLibWrapper/__pycache__/ 4 | *.csv 5 | *.xls 6 | *.xlsx 7 | *.zip 8 | *.pyc 9 | QuantLibWrapper/*.pyc 10 | tmp/ 11 | -------------------------------------------------------------------------------- /QuantLibWrapper/Regression.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | from scipy.linalg import lstsq 5 | 6 | def MultiIndexSet(n, k): 7 | if n==1: return [ [i] for i in range(k) ] 8 | return [ [i]+s for i in range(k) for s in MultiIndexSet(n-1,k-i)] 9 | 10 | class Regression: 11 | 12 | # Python constructor 13 | def __init__(self, controls, observations, maxPolynomialDegree=2): 14 | self.maxPolynomialDegree = maxPolynomialDegree 15 | self.multiIdxSet = np.array(MultiIndexSet(controls.shape[1],maxPolynomialDegree+1)) 16 | A = np.array([ self.monomials(c) for c in controls ]) 17 | p, res, rnk, s = lstsq(A, observations) # res, rnk, s for debug purposes 18 | self.beta = p 19 | 20 | def monomials(self, control): 21 | x = np.ones(self.multiIdxSet.shape[0]) 22 | for i in range(self.multiIdxSet.shape[0]): 23 | for j in range(self.multiIdxSet.shape[1]): 24 | x[i] *= control[j]**self.multiIdxSet[i][j] 25 | return x 26 | 27 | def value(self, control): 28 | return self.monomials(control).dot(self.beta) 29 | 30 | -------------------------------------------------------------------------------- /testYieldCurve.py: -------------------------------------------------------------------------------- 1 | 2 | import QuantLib as ql 3 | import QuantLibWrapper.YieldCurve as yc 4 | 5 | terms = [ \ 6 | '1y', \ 7 | '2y', \ 8 | '3y', \ 9 | '4y', \ 10 | '5y', \ 11 | '6y', \ 12 | '7y', \ 13 | '8y', \ 14 | '9y', \ 15 | '10y',\ 16 | '12y',\ 17 | '15y',\ 18 | '20y',\ 19 | '25y',\ 20 | '30y' ] 21 | 22 | rates = [ \ 23 | 2.70e-2,\ 24 | 2.75e-2,\ 25 | 2.80e-2,\ 26 | 3.00e-2,\ 27 | 3.36e-2,\ 28 | 3.68e-2,\ 29 | 3.97e-2,\ 30 | 4.24e-2,\ 31 | 4.50e-2,\ 32 | 4.75e-2,\ 33 | 4.75e-2,\ 34 | 4.70e-2,\ 35 | 4.50e-2,\ 36 | 4.30e-2,\ 37 | 4.30e-2 ] 38 | 39 | fwdRateYC = yc.YieldCurve(terms,rates) 40 | 41 | print(fwdRateYC.table()) 42 | 43 | fwdRateYC.plot(1.0/365) 44 | 45 | today = ql.Settings.getEvaluationDate(ql.Settings.instance()) 46 | print('Today: '+str(today)) 47 | calendar = ql.TARGET() 48 | period = ql.Period('500d') 49 | bdc = ql.Following 50 | print(str(calendar)+', '+str(period)+', '+str(bdc)) 51 | maturity = calendar.advance(today,period,bdc) 52 | print('Maturity: '+str(maturity)) 53 | discountFactor = fwdRateYC.discount(maturity) 54 | print('Discount: '+str(discountFactor)) 55 | -------------------------------------------------------------------------------- /QuantLibWrapper/ThetaMethod.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from scipy.sparse import diags, identity 4 | import numpy as np 5 | 6 | 7 | def solveTDS(diagA, y): 8 | # solve linear systen Ax = y for a tridiagonal matrix A 9 | a = diagA.diagonal(-1) 10 | b = diagA.diagonal(0) 11 | c = diagA.diagonal(1) 12 | # in place LU decomposition; no error handling if LU decomposition does not exist 13 | for i in range(1,b.shape[0]): 14 | a[i-1] /= b[i-1] 15 | b[i] -= c[i-1]*a[i-1] 16 | # forward substitution 17 | z = np.zeros(b.shape[0]) 18 | z[0] = y[0] 19 | for i in range(1,z.shape[0]): 20 | z[i] = y[i] - a[i-1]*z[i-1] 21 | # backward substitution, overwrite input y 22 | y[-1] = z[-1]/b[-1] 23 | for i in range(z.shape[0]-1,0,-1): 24 | y[i-1] = (z[i-1] - c[i-1]*y[i])/b[i-1] 25 | return 26 | 27 | 28 | def thetaStep(arrayL, arrayC, arrayU, arrayRHS, stepSize, theta): 29 | # solve v = [I+h*theta*M]^-1 [I-h(1-theta)M] r 30 | # where M = diag[l, c, u] and r = RHS 31 | M = diags([arrayL, arrayC, arrayU], [-1, 0, 1]) 32 | # print(M.toarray()) 33 | I = identity(arrayC.shape[0]) 34 | b = (I - (stepSize*(1.0-theta))*M).dot(arrayRHS) 35 | if theta==0: # Explicit Euler 36 | return b 37 | A = (I + (stepSize*theta)*M) 38 | solveTDS(A,b) 39 | return b 40 | -------------------------------------------------------------------------------- /testSwapPricing.py: -------------------------------------------------------------------------------- 1 | 2 | import QuantLib as ql 3 | import QuantLibWrapper.YieldCurve as yc 4 | import QuantLibWrapper.Swap as sw 5 | 6 | today = ql.Date(3,9,2018) 7 | ql.Settings.setEvaluationDate(ql.Settings.instance(),today) 8 | 9 | terms = [ \ 10 | '1y', \ 11 | '2y', \ 12 | '3y', \ 13 | '4y', \ 14 | '5y', \ 15 | '6y', \ 16 | '7y', \ 17 | '8y', \ 18 | '9y', \ 19 | '10y',\ 20 | '12y',\ 21 | '15y',\ 22 | '20y',\ 23 | '25y',\ 24 | '30y' ] 25 | 26 | rates = [ \ 27 | 2.70e-2,\ 28 | 2.75e-2,\ 29 | 2.80e-2,\ 30 | 3.00e-2,\ 31 | 3.36e-2,\ 32 | 3.68e-2,\ 33 | 3.97e-2,\ 34 | 4.24e-2,\ 35 | 4.50e-2,\ 36 | 4.75e-2,\ 37 | 4.75e-2,\ 38 | 4.70e-2,\ 39 | 4.50e-2,\ 40 | 4.30e-2,\ 41 | 4.30e-2 ] 42 | 43 | rates2 = [ r+0.005 for r in rates ] 44 | 45 | discCurve = yc.YieldCurve(terms,rates) 46 | projCurve = yc.YieldCurve(terms,rates2) 47 | 48 | startDate = ql.Date(30, 10, 2018) 49 | endDate = ql.Date(30, 10, 2038) 50 | 51 | swap = sw.Swap(startDate,endDate,0.05,discCurve,projCurve) 52 | 53 | print('NPV: %11.2f' % (swap.npv())) 54 | print('FairRate: %11.6f' % (swap.fairRate())) 55 | print('Annuity: %11.2f' % (swap.annuity())) 56 | 57 | print(swap.fixedCashFlows()) 58 | print(swap.floatCashFlows()) 59 | -------------------------------------------------------------------------------- /QuantLibWrapper/Helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from scipy.stats import norm 4 | from scipy.optimize import brentq 5 | import numpy as np 6 | 7 | 8 | def BlackOverK(moneyness, stdDev, callOrPut): 9 | d1 = np.log(moneyness) / stdDev + stdDev / 2.0 10 | d2 = d1 - stdDev 11 | return callOrPut * (moneyness*norm.cdf(callOrPut*d1)-norm.cdf(callOrPut*d2)) 12 | 13 | def Black(strike, forward, sigma, T, callOrPut): 14 | nu = sigma*np.sqrt(T) 15 | if nu<1.0e-12: # assume zero 16 | return max(callOrPut*(forward-strike),0.0) # intrinsic value 17 | return strike * BlackOverK(forward/strike,nu,callOrPut) 18 | 19 | def BlackImpliedVol(price, strike, forward, T, callOrPut): 20 | def objective(sigma): 21 | return Black(strike, forward, sigma, T, callOrPut) - price 22 | return brentq(objective,0.01, 1.00, xtol=1.0e-8) 23 | 24 | def BachelierRaw(moneyness, stdDev, callOrPut): 25 | h = callOrPut * moneyness / stdDev 26 | return stdDev * (h*norm.cdf(h) + norm.pdf(h)) 27 | 28 | def BachelierVegaRaw(moneyness, stdDev): 29 | return norm.pdf(moneyness / stdDev) 30 | 31 | def Bachelier(strike, forward, sigma, T, callOrPut): 32 | return BachelierRaw(forward-strike,sigma*np.sqrt(T),callOrPut) 33 | 34 | def BachelierVega(strike, forward, sigma, T): 35 | return BachelierVegaRaw(forward-strike,sigma*np.sqrt(T))*np.sqrt(T) 36 | 37 | def BachelierImpliedVol(price, strike, forward, T, callOrPut): 38 | def objective(sigma): 39 | return Bachelier(strike, forward, sigma, T, callOrPut) - price 40 | return brentq(objective,1e-4, 1e-1, xtol=1.0e-8) 41 | 42 | -------------------------------------------------------------------------------- /QuantLibWrapper/BermudanOption.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | 5 | class BermudanOption: 6 | 7 | # Python constructor 8 | def __init__(self, expiryTimes, underlyings, method): 9 | print('Bermudan option pricing: |',end='', flush=True) 10 | self.expiryTimes = expiryTimes # T_E^1, ..., T_E^k 11 | self.underlyings = underlyings # U_k(x) 12 | self.method = method # the numerical method used for roll-back 13 | for k in range(expiryTimes.shape[0],0,-1): 14 | print(".",end='', flush=True) 15 | if k==expiryTimes.shape[0]: 16 | x = method.xSet(expiryTimes[k-1]) 17 | H = np.zeros(x.shape[0]) 18 | else: 19 | [x, H] = method.rollBack(expiryTimes[k-1],expiryTimes[k],x,U,H) 20 | if len(x.shape)==1: # PDE and density integration 21 | U = np.array([ underlyings[k-1].at([state,0.0]) for state in x ]) 22 | else: # MC simulation 23 | U = np.array([ underlyings[k-1].at(state) for state in x ]) 24 | [x, H] = method.rollBack(0.0,expiryTimes[0],x,U,H) 25 | self.x = x 26 | self.H = H 27 | print('| Done.', flush=True) 28 | 29 | def npv(self): 30 | if self.H.shape[0]==1: 31 | return self.H[0] 32 | return np.interp(0.0, self.x, self.H) 33 | 34 | 35 | # We specify a European payoff (pricer) without [.]^+ operator 36 | class EuropeanPayoff(BermudanOption): 37 | 38 | # Python constructor 39 | def __init__(self, expiryTime, underlying, method): 40 | x = method.xSet(expiryTime) 41 | if len(x.shape)==1: # PDE and density integration 42 | U = np.array([ underlying.at([state,0.0]) for state in x ]) 43 | else: # MC simulation 44 | U = np.array([ underlying.at(state) for state in x ]) 45 | [x, H] = method.rollBack(0.0,expiryTime,x,U,U) 46 | self.x = x 47 | self.H = H -------------------------------------------------------------------------------- /QuantLibWrapper/MCSimulation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | 5 | class MCSimulation: 6 | 7 | # Python constructor 8 | def __init__(self, model, times, nPaths, seed=123): 9 | print('Start MC Simulation:', end='', flush=True) 10 | self.model = model # an object implementing stochastic process interface 11 | self.times = times # simulation times [0, ..., T], np.array 12 | self.nPaths = nPaths # number of paths, long 13 | # random number generator 14 | print(' |dW\'s', end='', flush=True) 15 | self.dW = np.random.RandomState(seed).standard_normal([self.nPaths,len(self.times)-1,model.factors()]) 16 | print('|', end='', flush=True) 17 | # simulate states 18 | self.X = np.zeros([self.nPaths,len(self.times),model.size()]) 19 | for i in range(self.nPaths): 20 | if i % max(int(self.nPaths/10),1) == 0 : print('s', end='', flush=True) 21 | self.X[i][0] = self.model.initialValues() 22 | for j in range(len(self.times)-1): 23 | self.X[i][j+1] = model.evolve(self.times[j],self.X[i][j],times[j+1]-times[j],self.dW[i][j]) 24 | print('| Finished.', end='\n', flush=True) 25 | 26 | def npv(self, payoff): 27 | print('Calculate payoff...', end='', flush=True) 28 | obsIdx = np.where(self.times==payoff.observationTime)[0][0] # assume we simulated these dates 29 | payIdx = np.where(self.times==payoff.payTime)[0][0] # otherwise we get an exception 30 | V0 = np.zeros([self.nPaths]) # simulated discounted payoffs 31 | VT = np.zeros([self.nPaths]) # simulated payoff at observation time 32 | N0 = np.zeros([self.nPaths]) # numeraire at 0; should be 1 33 | NT = np.zeros([self.nPaths]) # simulated numeraire at pay time 34 | for i in range(self.nPaths): 35 | VT[i] = payoff.at(self.X[i][obsIdx]) 36 | N0[i] = payoff.model.numeraire(self.X[i][0]) 37 | NT[i] = payoff.model.numeraire(self.X[i][payIdx]) 38 | V0[i] = N0[i] * VT[i] / NT[i] 39 | print(' Done.', end='\n', flush=True) 40 | return np.mean(V0) 41 | -------------------------------------------------------------------------------- /QuantLibWrapper/YieldCurve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import QuantLib as ql 4 | 5 | import matplotlib.pyplot as plt 6 | import pandas 7 | 8 | class YieldCurve: 9 | 10 | # Python constructor 11 | def __init__(self, terms, rates): 12 | today = ql.Settings.getEvaluationDate(ql.Settings.instance()) 13 | self.terms = terms 14 | self.dates = [ ql.WeekendsOnly().advance(today,ql.Period(term),ql.ModifiedFollowing) for term in [ '0d' ] + terms ] 15 | self.rates = [rates[0]] + rates 16 | # use rates as backward flat interpolated continuous compounded forward rates 17 | self.yts = ql.ForwardCurve(self.dates,self.rates,ql.Actual365Fixed(),ql.NullCalendar()) 18 | 19 | # zero coupon bond 20 | def discount(self,dateOrTime): 21 | return self.yts.discount(dateOrTime,True) 22 | 23 | def forwardRate(self,time): 24 | return self.yts.forwardRate(time,time,ql.Continuous,ql.Annual,True).rate() 25 | 26 | # plot zero rates and forward rate 27 | def plot(self,stepsize=0.1): 28 | times = [ k*stepsize for k in range(int(round(30.0/stepsize,0))+1) ] 29 | continuousForwd = [ self.yts.forwardRate(time,time,ql.Continuous,ql.Annual,True).rate() for time in times ] 30 | continuousZeros = [ self.yts.zeroRate(time,ql.Continuous,ql.Annual,True).rate() for time in times ] 31 | annualZeros = [ self.yts.zeroRate(time,ql.Compounded,ql.Annual,True).rate() for time in times ] 32 | # print(times, continuousForwd, continuousZeros, annualZeros) 33 | plt.plot(times,continuousForwd, label='Cont. forward rate') 34 | plt.plot(times,continuousZeros, label='Cont. zero rate') 35 | plt.plot(times,annualZeros, label='Annually comp. zero rate') 36 | plt.legend() 37 | plt.xlabel('Maturity') 38 | plt.ylabel('Interest rate') 39 | plt.show() 40 | 41 | # return a table with curve data 42 | def table(self): 43 | table = pandas.DataFrame( [ self.terms, self.dates, self.rates ] ).T 44 | table.columns = [ 'Terms', 'Dates', 'Rates' ] 45 | return table 46 | 47 | def referenceDate(self): 48 | return self.referenceDate() 49 | 50 | # end of YieldCurve 51 | 52 | -------------------------------------------------------------------------------- /testHullWhiteModelPaths.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | from scipy.optimize import brentq 4 | 5 | import matplotlib.pyplot as plt 6 | from matplotlib.ticker import LinearLocator, FormatStrFormatter 7 | 8 | import pandas 9 | 10 | import QuantLib as ql 11 | import QuantLibWrapper.YieldCurve as yc 12 | 13 | from QuantLibWrapper.HullWhiteModel import HullWhiteModel 14 | from QuantLibWrapper.MCSimulation import MCSimulation 15 | from QuantLibWrapper import Payoffs 16 | 17 | # yield curves 18 | 19 | flatCurve = yc.YieldCurve(['30y'],[0.03]) 20 | 21 | # We calibrate a Hull-White model to 100bp 'volatility' of x at 10y and 11y 22 | 23 | meanReversion = 0.2 24 | 25 | def obj1(sigma1): 26 | model = HullWhiteModel(flatCurve,meanReversion,np.array([5.0, 10.0]),np.array([sigma1, sigma1])) 27 | return model.varianceX(0,5.0) - (1.0e-2)**2 * 5 28 | sigma1 = brentq(obj1,1.0e-4,1.0e-1) 29 | 30 | def obj2(sigma2): 31 | model = HullWhiteModel(flatCurve,meanReversion,np.array([5.0, 10.0]),np.array([sigma1, sigma2])) 32 | return model.varianceX(0,10.0) - (1.0e-2)**2 * 10 33 | sigma2 = brentq(obj2,1.0e-4,1.0e-1) 34 | 35 | print([sigma1, sigma2]) 36 | model = HullWhiteModel(flatCurve,meanReversion,[1.0, 10.0],[sigma1, sigma2]) 37 | 38 | 39 | times = np.array([k*0.1 for k in range(101)]) 40 | nPaths = 10 41 | mcSim = MCSimulation(model,times,nPaths) 42 | 43 | fig = plt.figure(figsize=(4, 6)) 44 | for path in mcSim.X: 45 | plt.plot(times, [ X[0] for X in path ]) 46 | plt.xlabel('Simulation time t') 47 | plt.ylabel('State variable x(t)') 48 | plt.title('a = %4.2f' % meanReversion) 49 | 50 | fig = plt.figure(figsize=(4, 6)) 51 | for path in mcSim.X: 52 | plt.plot(times, [ np.exp(X[1]) for X in path ]) 53 | plt.xlabel('Simulation time t') 54 | plt.ylabel('Numeraire N(t)') 55 | plt.title('a = %4.2f' % meanReversion) 56 | plt.show() 57 | 58 | 59 | 60 | #exit() 61 | 62 | ##sigma = np.array([ np.std([ mcSim.X[i][j][0] for i in range(mcSim.X.shape[0])])/np.sqrt(times[j]) for j in range(mcSim.X.shape[1]) ]) 63 | #table = pandas.DataFrame([ times, sigma ]).T 64 | #print(table) 65 | #table.to_excel('table.xls') 66 | 67 | def sigmaFwd(a): 68 | T0, T1, sigma = 5.0, 10.0, 1.0e-2 69 | y0 = sigma**2 * T0 70 | y1 = sigma**2 * T1 71 | return np.sqrt((y1 - np.exp(-a*(T1-T0))*y0)/(T1-T0)) 72 | 73 | fig = plt.figure(figsize=(8, 4)) 74 | meanRevs = np.linspace(-0.1,0.1,101) 75 | plt.plot(meanRevs, np.array([ sigmaFwd(a)*1.0e+4 for a in meanRevs])) 76 | plt.xlabel('Mean reversion t') 77 | plt.ylabel('Forward volatility (bp)') 78 | plt.title('T0 = 5y, T1 = 10y, spot sigma = 100bp') 79 | plt.show() 80 | 81 | -------------------------------------------------------------------------------- /testSABRModelSmileDynamics.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import pandas 5 | 6 | from QuantLibWrapper.SabrModel import SabrModel 7 | from QuantLibWrapper.MCSimulation import MCSimulation 8 | 9 | # SabrModel( S(t), T, alpha, beta, nu, rho ) 10 | model1 = SabrModel(0.05,5.0,0.0420, 0.1000,0.5000,0.3 ) 11 | model2 = SabrModel(0.05,5.0,0.0420, 0.7000,0.5000,0.0 ) 12 | model3 = SabrModel(0.05,5.0,0.0420, 0.9000,0.0001,0.0 ) 13 | # ATM calibration 14 | print(model1.calibrateATM(0.01), model2.calibrateATM(0.01), model3.calibrateATM(0.01)) 15 | # Strikes 16 | strikes = [ (i+1)/1000 for i in range(100) ] 17 | # implied volatility 18 | vols1 = [model1.normalVolatility(strike) for strike in strikes] 19 | vols2 = [model2.normalVolatility(strike) for strike in strikes] 20 | vols3 = [model3.normalVolatility(strike) for strike in strikes] 21 | 22 | # smile dynamics 23 | S_ = [ 0.020, 0.035, 0.050, 0.065, 0.080 ] 24 | vols1_ = [] 25 | vols2_ = [] 26 | vols3_ = [] 27 | backBone1_ = [] 28 | backBone2_ = [] 29 | backBone3_ = [] 30 | for S in S_: 31 | model1.forward = S 32 | model2.forward = S 33 | model3.forward = S 34 | vols1_.append([model1.normalVolatility(strike) for strike in strikes]) 35 | vols2_.append([model2.normalVolatility(strike) for strike in strikes]) 36 | vols3_.append([model3.normalVolatility(strike) for strike in strikes]) 37 | backBone1_.append(model1.normalVolatility(S)) 38 | backBone2_.append(model2.normalVolatility(S)) 39 | backBone3_.append(model3.normalVolatility(S)) 40 | 41 | plt.figure() 42 | plt.plot(strikes,vols1, 'b-', label='beta=0.1,nu=0.5,rho=0.3') 43 | plt.plot(strikes,vols2, 'r-', label='beta=0.7,nu=0.5,rho=0.0') 44 | plt.plot(strikes,vols3, 'g-', label='beta=0.9,nu=0.0,rho=0.0') 45 | plt.legend() 46 | plt.xlabel('Strike') 47 | plt.ylabel('Normal Volatility') 48 | plt.xlim((0.0, 0.10)) 49 | plt.ylim((0.005, 0.020)) 50 | 51 | plt.figure() 52 | for k in range(len(S_)): 53 | plt.plot(strikes,vols1_[k], 'b:', label='S='+str(S_[k])) 54 | plt.plot(S_,backBone1_, 'bo-') 55 | plt.legend() 56 | plt.xlabel('Strike') 57 | plt.ylabel('Normal Volatility') 58 | plt.xlim((0.0, 0.10)) 59 | plt.ylim((0.005, 0.020)) 60 | 61 | plt.figure() 62 | for k in range(len(S_)): 63 | plt.plot(strikes,vols2_[k], 'r:', label='S='+str(S_[k])) 64 | plt.plot(S_,backBone2_, 'ro-') 65 | plt.legend() 66 | plt.xlabel('Strike') 67 | plt.ylabel('Normal Volatility') 68 | plt.xlim((0.0, 0.10)) 69 | plt.ylim((0.005, 0.020)) 70 | 71 | plt.figure() 72 | for k in range(len(S_)): 73 | plt.plot(strikes,vols3_[k], 'g:', label='S='+str(S_[k])) 74 | plt.plot(S_,backBone3_, 'go-') 75 | plt.legend() 76 | plt.xlabel('Strike') 77 | plt.ylabel('Normal Volatility') 78 | plt.xlim((0.0, 0.10)) 79 | plt.ylim((0.003, 0.020)) 80 | 81 | plt.show() 82 | 83 | -------------------------------------------------------------------------------- /QuantLibWrapper/Payoffs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | 5 | class Pay: 6 | # Python constructor 7 | def __init__(self, payoff, payTime): 8 | self.observationTime = payoff.observationTime 9 | self.model = payoff.model 10 | # 11 | self.payoff = payoff 12 | self.payTime = payTime 13 | def at(self,x): 14 | return self.payoff.at(x) 15 | 16 | class Zero: 17 | def at(self,x): 18 | return 0.0 19 | 20 | class One: 21 | def at(self,x): 22 | return 1.0 23 | 24 | class Max: 25 | # Python constructor 26 | def __init__(self, first, second): 27 | self.first = first 28 | self.second = second 29 | 30 | def at(self,x): 31 | return max(self.first.at(x),self.second.at(x),) 32 | 33 | 34 | class VanillaOption: 35 | # Python constructor 36 | def __init__(self, underlying, strike, callOrPut): 37 | self.observationTime = underlying.observationTime 38 | self.model = underlying.model 39 | # 40 | self.underlying = underlying 41 | self.strike = strike 42 | self.callOrPut = callOrPut 43 | def at(self, x): 44 | return max(self.callOrPut*(self.underlying.at(x)-self.strike),0.0) 45 | 46 | class CouponBond: 47 | # Python constructor 48 | def __init__(self, model, observationTime, payTimes, cashFlows): 49 | self.observationTime = observationTime 50 | self.model = model 51 | # 52 | self.payTimes = payTimes 53 | self.cashFlows = cashFlows 54 | # function 55 | def at(self, x): 56 | bond = 0 57 | for i in range(len(self.payTimes)): 58 | bond += self.cashFlows[i] * self.model.zeroBondPayoff(x,self.observationTime,self.payTimes[i]) 59 | return bond 60 | 61 | class SwapRate: 62 | # Python constructor 63 | def __init__(self, model, observationTime, startTime, endTime): 64 | self.observationTime = observationTime 65 | self.model = model 66 | # 67 | self.startTime = startTime 68 | self.endTime = endTime 69 | # 70 | tmp = [startTime+k for k in range(int(endTime-startTime)+1)] 71 | if tmp[-1]T0: np.append(tGrid,[T0]) 30 | for k in range(tGrid.shape[0]-1): 31 | # then we roll back individual time steps 32 | V = self.rollBackOneStep(tGrid[k+1],tGrid[k],x1,V) 33 | return [x1, V] 34 | 35 | def rollBackOneStep(self, T0, T1, x, V): 36 | # time and space discretisations 37 | ht = T1 - T0 38 | hx = (x[-1]-x[0])/(x.shape[0]-1) # assume equidistant grid 39 | # theta estimation point 40 | t = self.theta*T0 + (1-self.theta)*T1 41 | f = self.hwModel.forwardRate(0.0,0.0,t) 42 | sigma = self.hwModel.sigma(t) 43 | y = self.hwModel.y(t) 44 | a = self.hwModel.meanReversion 45 | # linear operator v' = M v. Note, x is an array 46 | c = (sigma**2/hx**2 + f) + x 47 | l = -sigma**2/2.0/hx**2 + (1.0/2.0/hx)*(y-a*x) 48 | u = -sigma**2/2.0/hx**2 - (1.0/2.0/hx)*(y-a*x) 49 | # adjust for boundary conditions 50 | # lanbda approximation 51 | if self.lambda0N!=None: # fall-back if provided by user, typically lambda0N=0 52 | lambda0 = self.lambda0N 53 | lambdaN = self.lambda0N 54 | else: 55 | Vx0 = (V[2]-V[0])/2.0/hx 56 | Vxx0 = (V[2]-2*V[1]+V[0])/hx/hx 57 | lambda0 = Vxx0/Vx0 if abs(Vx0)>1.0e-8 else 0.0 58 | VxN = (V[-1]-V[-3])/2.0/hx 59 | VxxN = (V[-1]-2*V[-2]+V[-3])/hx/hx 60 | lambdaN = VxxN/VxN if abs(VxN)>1.0e-8 else 0.0 61 | #print('Vx0 = '+str('%10.6f'%Vx0)+', Vxx0 = '+str('%10.6f'%Vxx0)+', l0 = '+str('%10.6f'%lambda0)+ \ 62 | # ', VxN = '+str('%10.6f'%VxN)+', VxxN = '+str('%10.6f'%VxxN)+', lN = '+str('%10.6f'%lambdaN) ) 63 | c[0] = 2.0*(y-a*x[0] +lambda0*sigma**2/2.0)/(2.0+lambda0*hx)/hx + x[0] + f 64 | c[-1] = -2.0*(y-a*x[-1]+lambdaN*sigma**2/2.0)/(2.0+lambdaN*hx)/hx + x[-1] + f 65 | u[0] = -2.0*(y-a*x[0] +lambda0*sigma**2/2.0)/(2.0+lambda0*hx)/hx 66 | l[-1] = 2.0*(y-a*x[-1]+lambdaN*sigma**2/2.0)/(2.0+lambdaN*hx)/hx 67 | # solve one step via theta method 68 | # M = diags([l[1:], c, u[:-1] ],[-1, 0, 1]) 69 | V = thetaStep(l[1:], c, u[:-1],V,ht,self.theta) 70 | return V 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /testSABRModelStaticSmile.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import pandas 5 | 6 | from QuantLibWrapper.SabrModel import SabrModel 7 | from QuantLibWrapper.MCSimulation import MCSimulation 8 | 9 | # SabrModel( S(t), T, alpha, beta, nu, rho ) 10 | model1 = SabrModel(0.05,1.0,0.0100,0.0001,0.0001,0.0,shift=0.1) 11 | model2 = SabrModel(0.05,1.0,0.0450,0.5000,0.0001,0.0,shift=0.1) 12 | model3 = SabrModel(0.05,1.0,0.0405,0.5000,0.5000,0.0,shift=0.1) 13 | model4 = SabrModel(0.05,1.0,0.0420,0.5000,0.5000,0.7,shift=0.1) 14 | # ATM calibration 15 | print(model1.calibrateATM(0.01), model2.calibrateATM(0.01), model3.calibrateATM(0.01), model4.calibrateATM(0.01)) 16 | # Strikes 17 | strikes = [ (i+1)/1000 for i in range(100) ] 18 | # implied volatility 19 | vols1 = [model1.normalVolatility(strike) for strike in strikes] 20 | vols2 = [model2.normalVolatility(strike) for strike in strikes] 21 | vols3 = [model3.normalVolatility(strike) for strike in strikes] 22 | vols4 = [model4.normalVolatility(strike) for strike in strikes] 23 | # implied density 24 | dens1 = [model1.density(strike) for strike in strikes] 25 | dens2 = [model2.density(strike) for strike in strikes] 26 | dens3 = [model3.density(strike) for strike in strikes] 27 | dens4 = [model4.density(strike) for strike in strikes] 28 | 29 | plt.figure() 30 | plt.plot(strikes,vols1, 'b-', label='Normal') 31 | plt.plot(strikes,vols2, 'r-', label='CEV') 32 | plt.plot(strikes,vols3, 'g-', label='CEV+SV') 33 | plt.plot(strikes,vols4, 'y-', label='CEV+SV+Corr') 34 | plt.legend() 35 | plt.xlabel('Strike') 36 | plt.ylabel('Normal Volatility') 37 | 38 | plt.figure() 39 | plt.plot(strikes,dens1, 'b-', label='Normal') 40 | plt.plot(strikes,dens2, 'r-', label='CEV') 41 | plt.plot(strikes,dens3, 'g-', label='CEV+SV') 42 | plt.plot(strikes,dens4, 'y-', label='CEV+SV+Corr') 43 | plt.legend() 44 | plt.xlabel('Rate') 45 | plt.ylabel('Density') 46 | 47 | plt.show() 48 | 49 | table = pandas.DataFrame( [ strikes, vols1, vols2, vols3, vols4 ] ).T 50 | table.columns = [ 'Strikes', 'Normal', 'CEV', 'CEV+SV', 'CEV+SV+Corr' ] 51 | # print(table) 52 | table.to_csv('SABRVolsAnalytic.csv') 53 | 54 | # MC simulation 55 | print('Start MC simulation...') 56 | times = np.array([k*0.01 for k in range(501)]) 57 | nPaths = 10000 58 | mcSim1 = MCSimulation(model1,times,nPaths) 59 | print('.') 60 | mcSim2 = MCSimulation(model2,times,nPaths) 61 | print('.') 62 | mcSim3 = MCSimulation(model3,times,nPaths) 63 | print('.') 64 | mcSim4 = MCSimulation(model4,times,nPaths) 65 | print('Done.') 66 | # payoffs and normal vols 67 | print('Start MC payoff calculation...') 68 | mcStrikes = np.array([ (i+1)/100 for i in range(10) ]) 69 | mcVols1 = model1.monteCarloImpliedNormalVol(mcSim1,mcStrikes) 70 | print('.') 71 | mcVols2 = model2.monteCarloImpliedNormalVol(mcSim2,mcStrikes) 72 | print('.') 73 | mcVols3 = model3.monteCarloImpliedNormalVol(mcSim3,mcStrikes) 74 | print('.') 75 | mcVols4 = model4.monteCarloImpliedNormalVol(mcSim4,mcStrikes) 76 | print('Done.') 77 | 78 | # output 79 | plt.figure() 80 | # 81 | plt.plot(strikes,vols1, 'b-', label='Normal') 82 | plt.plot(mcStrikes,mcVols1, 'b*') 83 | # 84 | plt.plot(strikes,vols2, 'r-', label='CEV') 85 | plt.plot(mcStrikes,mcVols2, 'r*') 86 | # 87 | plt.plot(strikes,vols3, 'g-', label='CEV+SV') 88 | plt.plot(mcStrikes,mcVols3, 'g*') 89 | # 90 | plt.plot(strikes,vols4, 'y-', label='CEV+SV+Corr') 91 | plt.plot(mcStrikes,mcVols4, 'y*') 92 | # 93 | plt.legend() 94 | plt.xlabel('Strike') 95 | plt.ylabel('Normal Volatility') 96 | plt.xlim((0.0, 0.101)) 97 | plt.ylim((0.005, 0.025)) 98 | plt.show() 99 | 100 | table = pandas.DataFrame( [ mcStrikes, mcVols1, mcVols2, mcVols3, mcVols4 ] ).T 101 | table.columns = [ 'Strikes', 'Normal', 'CEV', 'CEV+SV', 'CEV+SV+Corr' ] 102 | # print(table) 103 | table.to_csv('SABRVolsMonteCarlo.csv') 104 | -------------------------------------------------------------------------------- /testHullWhiteBermudan.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from matplotlib.ticker import LinearLocator, FormatStrFormatter 5 | 6 | import pandas 7 | 8 | import QuantLib as ql 9 | import QuantLibWrapper.YieldCurve as yc 10 | from QuantLibWrapper.HullWhiteModel import HullWhiteModel, HullWhiteModelWithDiscreteNumeraire 11 | from QuantLibWrapper.MCSimulation import MCSimulation 12 | from QuantLibWrapper import Payoffs 13 | from QuantLibWrapper.BermudanOption import BermudanOption 14 | from QuantLibWrapper.DensityIntegrations import DensityIntegrationWithBreakEven, \ 15 | SimpsonIntegration, HermiteIntegration, CubicSplineExactIntegration 16 | from QuantLibWrapper.PDESolver import PDESolver 17 | from QuantLibWrapper.AMCSolver import AMCSolver, AMCSolverOnlyExerciseRegression 18 | 19 | # yield curves 20 | 21 | flatCurve = yc.YieldCurve(['30y'],[0.03]) 22 | 23 | terms = [ '1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y', '10y', '12y', '15y', '20y', '25y', '30y' ] 24 | rates = [ 2.70e-2, 2.75e-2, 2.80e-2, 3.00e-2, 3.36e-2, 3.68e-2, 3.97e-2, 4.24e-2, 4.50e-2, 4.75e-2, 4.75e-2, 4.70e-2, 4.50e-2, 4.30e-2, 4.30e-2 ] 25 | fwdRateYC = yc.YieldCurve(terms,rates) 26 | 27 | # Hull-White model 28 | 29 | meanReversion = 0.05 30 | volatilityTimes = np.array([ 1.0 , 2.0 , 5.0 , 10.0 ]) 31 | volatilityValues = np.array([ 0.01, 0.01, 0.01, 0.01 ]) 32 | 33 | #hwModel = HullWhiteModel(fwdRateYC,meanReversion,volatilityTimes,volatilityValues) 34 | hwModel = HullWhiteModelWithDiscreteNumeraire(fwdRateYC,meanReversion,volatilityTimes,volatilityValues) 35 | #method = SimpsonIntegration(hwModel,101,5) 36 | #method = DensityIntegrationWithBreakEven(SimpsonIntegration(hwModel,101,5)) 37 | #method = HermiteIntegration(hwModel,10,101,5) 38 | #method = CubicSplineExactIntegration(hwModel,101,5) 39 | #method = DensityIntegrationWithBreakEven(CubicSplineExactIntegration(hwModel,101,5)) 40 | #method = PDESolver(hwModel,11,2.0,0.5,1.0/12.0,0.0) 41 | #method = PDESolver(hwModel,11,2.0,0.5,1.0/12.0) 42 | 43 | times = np.linspace(0.0,19.0,20) 44 | times = np.array([0.0, 2.0, 6.0] + [ 12.0+k for k in range(8)]) 45 | nPaths = 10000 46 | mcSim = MCSimulation(hwModel,times,nPaths) 47 | method = AMCSolver(mcSim,3,0.25) 48 | #method = AMCSolverOnlyExerciseRegression(mcSim,3,0.25) 49 | 50 | # now we test coupon bond opition pricing 51 | 52 | # coupon bond option details 53 | 54 | exercise = 12.0 55 | payTimes = [ 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 20.0 ] 56 | cashFlows = [ -1.0, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 1.0 ] 57 | 58 | print('Bond option with zero strike: ' + str(hwModel.couponBondOption(exercise,payTimes,cashFlows,0.0,1.0))) 59 | print('Bond option with unit strike: ' + str(hwModel.couponBondOption(exercise,payTimes[1:],cashFlows[1:],1.0,1.0))) 60 | 61 | expiryTimes = np.array([ 2.0, 6.0, 12.0]) 62 | underlyings = [ Payoffs.Zero(), Payoffs.Zero(), Payoffs.CouponBond(hwModel,12.0,payTimes,cashFlows) ] 63 | berm = BermudanOption(expiryTimes, underlyings, method) 64 | print('Pseudo-Bermudan opt. (3 ex.): ' + str(berm.npv())) 65 | payoff = Payoffs.Pay(Payoffs.VanillaOption(Payoffs.CouponBond(hwModel,12.0,payTimes,cashFlows),0.0,1.0),12.0) 66 | print('MC bond option : ' + str(mcSim.npv(payoff))) 67 | 68 | #exit() 69 | # test Bermudan bond option pricing 70 | 71 | expiryTimes = np.array([ 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0]) 72 | underlyings = [] 73 | print('European bond option prices 12y - 19y...') 74 | for k in range(8): 75 | pTimes = [payTimes[k]] + payTimes[(k+1):] 76 | cFlows = [-1.0 ] + cashFlows[(k+1):] 77 | print(hwModel.couponBondOption(payTimes[k],pTimes,cFlows,0.0,1.0)) 78 | underlyings.append(Payoffs.CouponBond(hwModel,payTimes[k],pTimes,cFlows)) 79 | berm = BermudanOption(expiryTimes, underlyings, method) 80 | print(berm.npv()) 81 | 82 | 83 | -------------------------------------------------------------------------------- /testHullWhiteModelVolatilities.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | from scipy.optimize import brentq 4 | 5 | import matplotlib.pyplot as plt 6 | from mpl_toolkits.mplot3d import Axes3D # for 3d plotting 7 | from matplotlib import cm 8 | from matplotlib.ticker import LinearLocator, FormatStrFormatter 9 | 10 | import pandas 11 | 12 | import QuantLib as ql 13 | 14 | from QuantLibWrapper.YieldCurve import YieldCurve 15 | from QuantLibWrapper.HullWhiteModel import HullWhiteModel 16 | from QuantLibWrapper.Swap import Swap 17 | from QuantLibWrapper.Swaption import Swaption, createSwaption 18 | 19 | # yield curves 20 | 21 | flatCurve = YieldCurve(['30y'],[0.03]) 22 | 23 | terms = [ '1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y', '10y', '12y', '15y', '20y', '25y', '30y', '50y' ] 24 | rates = [ 2.70e-2, 2.75e-2, 2.80e-2, 3.00e-2, 3.36e-2, 3.68e-2, 3.97e-2, 4.24e-2, 4.50e-2, 4.75e-2, 4.75e-2, 4.70e-2, 4.50e-2, 4.30e-2, 4.30e-2, 4.30e-2 ] 25 | rates2 = [ r+0.005 for r in rates ] 26 | 27 | discCurve = YieldCurve(terms,rates) 28 | projCurve = YieldCurve(terms,rates2) 29 | 30 | # Hull-White model mean reversion 31 | meanReversion = 0.05 32 | 33 | # first we have a look at the model-implied volatility smile 34 | 35 | fig = plt.figure(figsize=(4, 6)) 36 | atmRate = createSwaption('10y','10y',discCurve,projCurve).fairRate() 37 | relStrikes = [ -0.03+1e-3*k for k in range(61)] 38 | hwVols = [ 0.0050, 0.0075, 0.0100, 0.0125 ] 39 | for hwVol in hwVols: 40 | hwModel = HullWhiteModel(discCurve,meanReversion,np.array([30.0]),np.array([hwVol])) 41 | normalVols = [ createSwaption('10y','10y',discCurve,projCurve,atmRate+strike).npvHullWhite(hwModel,'v')*1e+4 42 | for strike in relStrikes ] 43 | plt.plot(relStrikes, normalVols, label='a='+str(meanReversion)+', sigma_r='+str(hwVol)) 44 | plt.ylim(0,250) 45 | plt.legend() 46 | plt.xlabel('Strike (relative to ATM)') 47 | plt.ylabel('Model-implied normal volatility (bp)') 48 | plt.show() 49 | 50 | # since Hull White smile is essentially flat we now consider ATM swaptions 51 | # we set up ATM swaptions on a grid of expiry and swap terms 52 | expiries = np.array([ (1*k+1) for k in range(20) ]) # in years 53 | swapterms = np.array([ (1*k+1) for k in range(20) ]) # in years relative to expiry 54 | vols = np.zeros([len(expiries),len(swapterms)]) 55 | 56 | # for this test we want to keep the model fixed to a particular vol point 57 | # if we change mean reversion 58 | def objective(sigma): 59 | tmpModel = HullWhiteModel(discCurve,meanReversion,np.array([30.0]),np.array([sigma])) 60 | return createSwaption('10y','10y',discCurve,projCurve).npvHullWhite(tmpModel,'v') - 0.01 # we calibrate to 100bp 10y-10y vols 61 | sigma = brentq(objective, 0.5e-2, 0.5e-1, xtol=1.0e-8) 62 | print('sigma_r: '+str(sigma)) 63 | hwModel = HullWhiteModel(discCurve,meanReversion,np.array([30.0]),np.array([sigma])) 64 | 65 | for expiry in expiries: 66 | for swapterm in swapterms: 67 | swaption = createSwaption(str(expiry)+'y',str(swapterm)+'y',discCurve,projCurve) 68 | vols[expiry-1][swapterm-1] = swaption.npvHullWhite(hwModel,'v')*1e+4 69 | print('.', end='', flush=True) 70 | print('') # newline 71 | 72 | # 3d surface plotting, see https://matplotlib.org/gallery/mplot3d/surface3d.html#sphx-glr-gallery-mplot3d-surface3d-py 73 | fig = plt.figure(figsize=(4, 6)) 74 | ax = fig.gca(projection='3d') 75 | X, Y = np.meshgrid(expiries, swapterms) 76 | surf = ax.plot_surface(X, Y, vols, cmap=cm.coolwarm, linewidth=0, antialiased=False) 77 | ax.xaxis.set_major_formatter(FormatStrFormatter('%2.0f')) 78 | ax.yaxis.set_major_formatter(FormatStrFormatter('%2.0f')) 79 | ax.zaxis.set_major_formatter(FormatStrFormatter('%2.0f')) 80 | ax.set_xlim(0, 20) 81 | ax.set_ylim(0, 20) 82 | ax.set_zlim(50, 150) 83 | ax.set_xticks([0, 5, 10, 15, 20]) 84 | ax.set_yticks([0, 5, 10, 15, 20]) 85 | ax.set_xlabel('Expiries (y)') 86 | ax.set_ylabel('Swap terms (y)') 87 | ax.set_zlabel('Model-implied normal volatility (bp)') 88 | # fig.colorbar(surf, shrink=0.5, aspect=5) 89 | plt.title('a = %4.2f' % meanReversion) 90 | plt.show() 91 | -------------------------------------------------------------------------------- /QuantLibWrapper/Swap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import pandas 4 | import QuantLib as ql 5 | 6 | class Swap: 7 | 8 | # Python constructor 9 | def __init__(self, startDate, endDate, fixedRate, discYieldCurve, projYieldCurve, payerOrReceiver=ql.VanillaSwap.Payer, notional=1.0e+4): 10 | # we need some swap details for swaption pricing 11 | self.payerOrReceiver = payerOrReceiver 12 | self.notional = notional 13 | self.fixedRate = fixedRate 14 | # for Hull-White pricing we need to reference the yield curves 15 | self.discYieldCurve = discYieldCurve 16 | self.projYieldCurve = projYieldCurve 17 | # we need handles of the yield curves... 18 | self.discHandle = ql.RelinkableYieldTermStructureHandle() 19 | self.projHandle = ql.RelinkableYieldTermStructureHandle() 20 | self.discHandle.linkTo(discYieldCurve.yts) 21 | self.projHandle.linkTo(projYieldCurve.yts) 22 | # schedule generation details 23 | fixedLegTenor = ql.Period('1y') 24 | floatLegTenor = ql.Period('6m') 25 | calendar = ql.TARGET() 26 | fixedLegAdjustment = ql.ModifiedFollowing 27 | floatLegAdjustment = ql.ModifiedFollowing 28 | endOfMonthFlag = False 29 | # schedule creation 30 | fixedSchedule = ql.Schedule(startDate, endDate, 31 | fixedLegTenor, calendar, 32 | fixedLegAdjustment, fixedLegAdjustment, 33 | ql.DateGeneration.Backward, endOfMonthFlag) 34 | floatSchedule = ql.Schedule(startDate, endDate, 35 | floatLegTenor, calendar, 36 | floatLegAdjustment, floatLegAdjustment, 37 | ql.DateGeneration.Backward, endOfMonthFlag) 38 | # interest rate details 39 | index = ql.Euribor(floatLegTenor,self.projHandle) 40 | spread = 0.0 # no floating rate spread applied 41 | fixedLegDayCounter = ql.Thirty360() 42 | floatLegDayCounter = index.dayCounter() 43 | # paymentAdjustment = ql.Following ... not exposed to user via Python 44 | # swap creation 45 | self.swap = ql.VanillaSwap(self.payerOrReceiver, self.notional, 46 | fixedSchedule, fixedRate, fixedLegDayCounter, 47 | floatSchedule, index, spread, 48 | floatLegDayCounter) 49 | # pricing engine to allow discounting etc. 50 | swapEngine = ql.DiscountingSwapEngine(self.discHandle) 51 | self.swap.setPricingEngine(swapEngine) 52 | 53 | def npv(self): 54 | return self.swap.NPV() 55 | 56 | def fairRate(self): 57 | return self.swap.fairRate() 58 | 59 | def annuity(self): 60 | return abs(self.swap.fixedLegBPS())/1.0e-4 61 | 62 | def fixedCashFlows(self): 63 | table = pandas.DataFrame( [ 64 | [ql.as_fixed_rate_coupon(cf).accrualStartDate() for cf in self.swap.fixedLeg()], 65 | [ql.as_fixed_rate_coupon(cf).accrualEndDate() for cf in self.swap.fixedLeg()], 66 | [ql.as_fixed_rate_coupon(cf).rate() for cf in self.swap.fixedLeg()], 67 | [cf.date() for cf in self.swap.fixedLeg()], 68 | [cf.amount() for cf in self.swap.fixedLeg()] 69 | ] ).T 70 | table.columns = [ 'AccrualStartDate', 'AccrualEndDate', 'Rate', 'PayDate', 'Amount' ] 71 | return table 72 | 73 | def floatCashFlows(self): 74 | table = pandas.DataFrame( [ 75 | [ql.as_floating_rate_coupon(cf).accrualStartDate() for cf in self.swap.floatingLeg()], 76 | [ql.as_floating_rate_coupon(cf).accrualEndDate() for cf in self.swap.floatingLeg()], 77 | [ql.as_floating_rate_coupon(cf).rate() for cf in self.swap.floatingLeg()], 78 | [cf.date() for cf in self.swap.floatingLeg()], 79 | [cf.amount() for cf in self.swap.floatingLeg()], 80 | [ql.as_floating_rate_coupon(cf).fixingDate() for cf in self.swap.floatingLeg()] 81 | ] ).T 82 | table.columns = [ 'AccrualStartDate', 'AccrualEndDate', 'Rate', 'PayDate', 'Amount', 'FixingDate' ] 83 | return table 84 | -------------------------------------------------------------------------------- /testHullWhiteModel.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from matplotlib.ticker import LinearLocator, FormatStrFormatter 5 | 6 | import pandas 7 | 8 | import QuantLib as ql 9 | import QuantLibWrapper.YieldCurve as yc 10 | 11 | from QuantLibWrapper.MCSimulation import MCSimulation 12 | from QuantLibWrapper import Payoffs 13 | from QuantLibWrapper.HullWhiteModel import HullWhiteModel, HullWhiteModelWithDiscreteNumeraire 14 | 15 | # yield curves 16 | 17 | flatCurve = yc.YieldCurve(['30y'],[0.03]) 18 | 19 | terms = [ '1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y', '10y', '12y', '15y', '20y', '25y', '30y' ] 20 | rates = [ 2.70e-2, 2.75e-2, 2.80e-2, 3.00e-2, 3.36e-2, 3.68e-2, 3.97e-2, 4.24e-2, 4.50e-2, 4.75e-2, 4.75e-2, 4.70e-2, 4.50e-2, 4.30e-2, 4.30e-2 ] 21 | fwdRateYC = yc.YieldCurve(terms,rates) 22 | 23 | # Hull-White model 24 | 25 | meanReversion = 0.05 26 | volatilityTimes = np.array([ 1.0 , 2.0 , 5.0 , 10.0 ]) 27 | volatilityValues = np.array([ 0.01, 0.01, 0.01, 0.01 ]) 28 | 29 | #hwModel = HullWhiteModel(fwdRateYC,meanReversion,volatilityTimes,volatilityValues) 30 | hwModel = HullWhiteModelWithDiscreteNumeraire(fwdRateYC,meanReversion,volatilityTimes,volatilityValues) 31 | 32 | # first we analyse future yield curves 33 | 34 | states = [ -0.10, -0.05, 0.0, 0.05, 0.1 ] 35 | futureTime = 5.0 36 | stepsize = 1.0/365 37 | 38 | times = [ k*stepsize for k in range(int(round(30.0/stepsize,0))+1) ] 39 | forwardRate = [ hwModel.yieldCurve.forwardRate(T) for T in times ] 40 | fig = plt.figure(figsize=(4, 6)) 41 | ax = fig.gca() 42 | plt.plot(times,forwardRate, label='f(0,T)') 43 | times = [ k*stepsize+futureTime for k in range(int(round(30.0/stepsize,0))+1) ] 44 | for x in states: 45 | forwardRate = [ hwModel.forwardRate(futureTime,x,T) for T in times ] 46 | plt.plot(times,forwardRate, label='x='+str(x)) 47 | 48 | plt.legend() 49 | plt.xlabel('Maturity T') 50 | plt.ylabel('Forward rate f(t,T)') 51 | ax.yaxis.set_major_formatter(FormatStrFormatter('%4.2f')) 52 | plt.title('a = %4.2f' % meanReversion) 53 | plt.show() 54 | 55 | # now we analyse coupon bond opition pricing 56 | 57 | # coupon bond option details 58 | 59 | exercise = 12.0 60 | payTimes = [ 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 20.0 ] 61 | cashFlows = [ 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 1.0 ] 62 | 63 | # analytical coupon bond option prices 64 | 65 | strikes = [ 0.70+0.01*k for k in range(41) ] 66 | calls = [ hwModel.couponBondOption(exercise,payTimes,cashFlows,strike,1.0) for strike in strikes ] 67 | puts = [ hwModel.couponBondOption(exercise,payTimes,cashFlows,strike,-1.0) for strike in strikes ] 68 | 69 | # cross check with MC prices 70 | 71 | # first we simulate all paths 72 | 73 | times = np.array([k*0.1 for k in range(121)]) 74 | times = np.array([0.0, 12.0]) 75 | nPaths = 1000 76 | mcSim = MCSimulation(hwModel,times,nPaths) 77 | 78 | # then calculate the payoffs 79 | 80 | mcStrikes = [ 0.70+0.05*k for k in range(9) ] 81 | bondPayoff = Payoffs.CouponBond(hwModel,exercise,payTimes,cashFlows) 82 | callPayoffs = [ Payoffs.Pay(Payoffs.VanillaOption(bondPayoff,strike,1.0) , exercise) for strike in mcStrikes ] 83 | putPayoffs = [ Payoffs.Pay(Payoffs.VanillaOption(bondPayoff,strike,-1.0), exercise) for strike in mcStrikes ] 84 | callsMC = [ mcSim.npv(payoff) for payoff in callPayoffs ] 85 | putsMC = [ mcSim.npv(payoff) for payoff in putPayoffs ] 86 | 87 | # gather results 88 | 89 | plt.plot(strikes,calls, label='analytic call') 90 | plt.plot(strikes,puts, label='analytic put') 91 | plt.plot(mcStrikes,callsMC, '*', label='MC call') 92 | plt.plot(mcStrikes,putsMC, '*', label='MC call') 93 | plt.legend() 94 | plt.xlabel('Strike') 95 | plt.ylabel('Bond option price') 96 | plt.show() 97 | 98 | table = pandas.DataFrame( [ 99 | mcStrikes, 100 | [ hwModel.couponBondOption(exercise,payTimes,cashFlows,strike,1.0) for strike in mcStrikes ], 101 | [ hwModel.couponBondOption(exercise,payTimes,cashFlows,strike,-1.0) for strike in mcStrikes ], 102 | callsMC, putsMC ] ).T 103 | table.columns = [ 'mcStrikes', 'calls', 'puts', 'callsMC', 'callsMC' ] 104 | print(table) -------------------------------------------------------------------------------- /testBermudanSwaption.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | from scipy.optimize import brentq 4 | 5 | import matplotlib.pyplot as plt 6 | from mpl_toolkits.mplot3d import Axes3D # for 3d plotting 7 | from matplotlib import cm 8 | from matplotlib.ticker import LinearLocator, FormatStrFormatter 9 | 10 | import pandas 11 | 12 | import QuantLib as ql 13 | 14 | from QuantLibWrapper.YieldCurve import YieldCurve 15 | from QuantLibWrapper.HullWhiteModel import HullWhiteModel 16 | from QuantLibWrapper.Swap import Swap 17 | from QuantLibWrapper.Swaption import Swaption, createSwaption 18 | from QuantLibWrapper.BermudanSwaption import BermudanSwaption 19 | 20 | 21 | # curves and vols 22 | 23 | atmVols = pandas.read_csv('swaptionATMVols.csv', sep=';', index_col=0 ) 24 | 25 | terms = [ '1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y', '10y', '12y', '15y', '20y', '25y', '30y', '50y' ] 26 | rates = [ 2.70e-2, 2.75e-2, 2.80e-2, 3.00e-2, 3.36e-2, 3.68e-2, 3.97e-2, 4.24e-2, 4.50e-2, 4.75e-2, 4.75e-2, 4.70e-2, 4.50e-2, 4.30e-2, 4.30e-2, 4.30e-2 ] 27 | rates = [ 0.025 for r in rates ] 28 | rates2 = [ r+0.005 for r in rates ] 29 | discCurve = YieldCurve(terms,rates) 30 | projCurve = YieldCurve(terms,rates2) 31 | meanReversion = 0.05 32 | normalVol = 0.01 33 | 34 | # swaption(s) 35 | 36 | maturity = 20 # in years 37 | swaptions = [] 38 | terms = [] 39 | inputVols = [] 40 | for k in range(1,maturity): 41 | expTerm = str(k)+'y' 42 | swpTerm = str(maturity-k)+'y' 43 | sigma = 0.01 # atmVols.loc[expTerm,swpTerm] 44 | terms.append(expTerm+'-'+swpTerm) 45 | inputVols.append(sigma) 46 | swaptions.append( createSwaption(expTerm,swpTerm, discCurve, projCurve, 0.03, ql.VanillaSwap.Receiver, sigma ) ) 47 | 48 | bermudanSwaption = BermudanSwaption(swaptions,meanReversion) 49 | 50 | table = pandas.DataFrame([ np.array(terms), 51 | np.array(inputVols), 52 | bermudanSwaption.model.volatilityTimes, 53 | bermudanSwaption.model.volatilityValues, 54 | bermudanSwaption.swaptionsNPV(), 55 | bermudanSwaption.bondOptionsNPV() 56 | ]).T 57 | table.columns = [ 'Terms', 'InputVols', 'Times', 'Vols', 'SwptNPV', 'CboNpv' ] 58 | 59 | bermT = pandas.DataFrame([ [ 'Berm', bermudanSwaption.npv() ] ]) 60 | bermT.columns = [ 'Terms', 'CboNpv' ] 61 | 62 | restT = table.append(bermT,ignore_index=True, sort=False) 63 | print(restT) 64 | 65 | x = 4*np.linspace(0,19,20) 66 | fig = plt.figure(figsize=(6, 4)) 67 | plt.bar(x,restT['CboNpv'],3.0) 68 | plt.xticks(x, restT['Terms'], rotation='vertical') 69 | plt.ylabel('option price') 70 | plt.ylim(0,4000) 71 | 72 | # plot short rate volatility 73 | T = np.linspace(0,20,2001) 74 | s = np.array([ bermudanSwaption.model.sigma(t)*10000 for t in T ]) 75 | fig = plt.figure(figsize=(6, 4)) 76 | plt.xticks(np.linspace(0,20,11)) 77 | plt.xlabel('time t') 78 | plt.ylabel('short rate volatility sigma(t) (bp)') 79 | plt.title('a = %4.2f' % meanReversion) 80 | plt.ylim(0,160) 81 | plt.plot(T,s) 82 | plt.show() 83 | 84 | 85 | # calculate short rate volatilities per mean reversion 86 | fig = plt.figure(figsize=(7, 4)) 87 | T = np.linspace(0,20,2001) 88 | for a in reversed([-0.05, -0.03, -0.01, 0.01, 0.03, 0.05, 0.07, 0.09, 0.11]): 89 | bermudanSwaption = BermudanSwaption(swaptions,a) # just set up and calibrate 90 | s = np.array([ bermudanSwaption.model.sigma(t)*10000 for t in T ]) 91 | plt.plot(T, s, label=str('a = %4.2f' % a)) 92 | plt.xticks(np.linspace(0,20,11)) 93 | plt.xlabel('time t') 94 | plt.ylabel('short rate volatility sigma(t) (bp)') 95 | plt.legend() 96 | plt.xlim(0,27) 97 | #plt.ylim(0,160) 98 | plt.show() 99 | 100 | exit() 101 | 102 | 103 | # calculate switch value per mean reversion 104 | maxEuropean = max([ swpt.npv() for swpt in swaptions ]) 105 | result = [] 106 | for a in [-0.05, -0.03, -0.01, 0.01, 0.03, 0.05, 0.07, 0.09, 0.11]: 107 | result.append([ a, BermudanSwaption(swaptions,a).npv()-maxEuropean ] ) 108 | 109 | table = pandas.DataFrame(result, columns=['MeanReversion', 'SwitchOption']) 110 | table.to_excel('SwitchOption-ITM.xls') 111 | 112 | 113 | ### read in aggregated results and plot data 114 | 115 | table = pandas.read_excel('SwitchOption.xls') 116 | print(table) 117 | 118 | fig = plt.figure(figsize=(6, 4)) 119 | plt.plot(table['MeanReversion'],table['ITM'],label='ITM, f=1%') 120 | plt.plot(table['MeanReversion'],table['ATM'],label='ATM, f=3%') 121 | plt.plot(table['MeanReversion'],table['OTM'],label='OTM, f=5%') 122 | plt.xlabel('Mean reversion a') 123 | plt.ylabel('Switch Option value') 124 | plt.ylim(0,600) 125 | plt.xlim(-0.06,0.12) 126 | plt.legend() 127 | 128 | plt.show() 129 | -------------------------------------------------------------------------------- /QuantLibWrapper/SabrModel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | from scipy.optimize import brentq 5 | from QuantLibWrapper.Helpers import Bachelier, BachelierImpliedVol 6 | 7 | class SabrModel: 8 | 9 | # Python constructor 10 | def __init__(self, forward, timeToExpiry, alpha, beta, nu, rho, shift=0.0): 11 | self.forward = forward 12 | self.timeToExpiry = timeToExpiry 13 | self.alpha = alpha 14 | self.beta = beta 15 | self.nu = nu 16 | self.rho = rho 17 | self.shift = shift 18 | 19 | # helpers 20 | def localVolC(self, rate): 21 | return np.power(rate+self.shift,self.beta) if rate>-self.shift else 0.0 22 | 23 | def localVolCPrime(self, rate): # for Milstein method 24 | return self.beta * np.power(rate+self.shift,self.beta-1) if rate>-self.shift else 0.0 25 | 26 | def sAverage(self, strike, forward): 27 | return (strike + forward) / 2.0 28 | # return np.power(strike*forward,0.5) # check consistency to QL 29 | 30 | def zeta(self, strike, forward): 31 | return self.nu / self.alpha * (np.power(forward+self.shift,1-self.beta)-np.power(strike+self.shift,1-self.beta)) / (1-self.beta) 32 | 33 | def chi(self, zeta): 34 | return np.log((np.sqrt(1-2*self.rho*zeta+zeta*zeta)-self.rho+zeta)/(1-self.rho)) 35 | 36 | # approximate implied normal volatility formula 37 | def normalVolatility(self,strike): 38 | Sav = self.sAverage(strike,self.forward) 39 | CSav = self.localVolC(Sav) 40 | gamma1 = self.beta / (Sav+self.shift) 41 | gamma2 = self.beta * (self.beta-1) / (Sav+self.shift) / (Sav+self.shift) 42 | I1 = (2*gamma2 - gamma1*gamma1) / 24 * self.alpha * self.alpha * CSav * CSav 43 | I1 = I1 + self.rho * self.nu * self.alpha * gamma1 / 4 * CSav 44 | I1 = I1 + (2 - 3*self.rho*self.rho) / 24 * self.nu * self.nu 45 | sigmaN = self.alpha * CSav # default, if close to ATM 46 | if np.fabs(strike-self.forward)>1.0e-8: # actual calculation for I0 47 | sigmaN = self.nu * (self.forward - strike) / self.chi(self.zeta(strike,self.forward)) 48 | sigmaN = sigmaN * (1 + I1*self.timeToExpiry) # higher order adjustment 49 | return sigmaN 50 | 51 | def calibrateATM(self, sigmaATM): 52 | def objective(alpha): 53 | self.alpha = alpha 54 | return self.normalVolatility(self.forward) - sigmaATM 55 | alpha0 = sigmaATM / self.localVolC(self.forward) 56 | self.alpha = brentq(objective,0.5*alpha0, 2.0*alpha0, xtol=1.0e-8) 57 | return self.alpha 58 | 59 | 60 | def vanillaPrice(self, strike, callOrPut): 61 | sigmaN = self.normalVolatility(strike) 62 | return Bachelier(strike,self.forward,sigmaN,self.timeToExpiry,callOrPut) 63 | 64 | def density(self, rate): 65 | eps = 1.0e-4 66 | cop = 1.0 67 | if (rate X(t0+dt) using independent Brownian increments dW 84 | # t0, dt are assumed float, X0, X1, dW are np.array 85 | def evolve(self, t0, X0, dt, dW): 86 | # first simulate stochastic volatility exact 87 | dZ = self.rho * dW[0] + np.sqrt(1-self.rho*self.rho)*dW[1] 88 | alpha0 = X0[1] 89 | alpha1 = alpha0*np.exp(-self.nu*self.nu/2*dt+self.nu*dZ*np.sqrt(dt)) 90 | alpha01 = np.sqrt(alpha0*alpha1) # average vol [t0, t0+dt] 91 | # simulate S via Milstein 92 | S0 = X0[0] 93 | S1 = S0 + alpha01*self.localVolC(S0)*dW[0]*np.sqrt(dt) \ 94 | + 0.5*alpha01*self.localVolC(S0)*alpha01*self.localVolCPrime(S0)*(dW[0]*dW[0]-1)*dt 95 | # gather results 96 | return np.array([S1, alpha1]) 97 | 98 | # calculate normal volatility smile from a MC simulation 99 | def monteCarloImpliedNormalVol(self, mcSimulation, strikes, fullOutput=False): 100 | if mcSimulation.times[-1]!=self.timeToExpiry: print('WARNING: times do not match.') 101 | # forward adjuster 102 | Sav = 0.0 103 | for j in range(mcSimulation.nPaths): Sav += mcSimulation.X[j][-1][0] 104 | Sav /= mcSimulation.nPaths 105 | S = np.array([(mcSimulation.X[j][-1][0]+self.forward-Sav) for j in range(mcSimulation.nPaths) ]) 106 | options = np.zeros(len(strikes)) 107 | vols = np.zeros(len(strikes)) 108 | for i in range(len(strikes)): 109 | cop = 1.0 if strikes[i]>self.forward else -1.0 110 | for j in range(mcSimulation.nPaths): 111 | options[i] += np.max([cop*(S[j]-strikes[i]), 0.0]) 112 | options[i] /= mcSimulation.nPaths 113 | try: vols[i] = BachelierImpliedVol(options[i],strikes[i],self.forward,mcSimulation.times[-1],cop) 114 | except: vols[i] = 0.0 115 | return vols if not fullOutput else np.array(strikes, options, vols) 116 | -------------------------------------------------------------------------------- /QuantLibWrapper/DensityIntegrations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | from scipy.stats import norm 5 | from scipy import integrate 6 | from scipy.interpolate import CubicSpline 7 | 8 | class DensityIntegration: # base class for other integration methods 9 | 10 | # Python constructor 11 | def __init__(self, hwModel, nGridPoints=101, stdDevs=5): 12 | self.hwModel = hwModel 13 | self.nGridPoints = nGridPoints 14 | self.stdDevs = stdDevs 15 | 16 | def xSet(self,expityTime): 17 | sigma = np.sqrt(self.hwModel.varianceX(0.0,expityTime)) 18 | if sigma==0: 19 | return np.array([0.0]) 20 | return np.linspace(-self.stdDevs*sigma,self.stdDevs*sigma,self.nGridPoints) 21 | 22 | 23 | class DensityIntegrationWithBreakEven(DensityIntegration): # decorate method with break-even methodology 24 | 25 | # Python constructor 26 | def __init__(self, method): 27 | DensityIntegration.__init__(self,method.hwModel,method.nGridPoints,method.stdDevs) 28 | self.method = method 29 | 30 | def rollBack(self, T0, T1, x1, U1, H1): 31 | # find break-even state and split grid 32 | roots = CubicSpline(x1,U1-H1).roots(discontinuity=False, extrapolate=False) 33 | if roots.shape[0]==0: # no break even point found 34 | return self.method.rollBack(T0,T1,x1,U1,H1) 35 | xStar = roots[0] 36 | VStar = CubicSpline(x1,U1)(xStar) 37 | # lower integrand 38 | lRange = np.where(x1 < xStar) 39 | lX1 = np.array([ x1[k] for k in lRange[0] ] + [xStar]) 40 | lU1 = np.array([ U1[k] for k in lRange[0] ] + [VStar]) 41 | lH1 = np.array([ H1[k] for k in lRange[0] ] + [VStar]) 42 | [x0, lV0] = self.method.rollBack(T0,T1,lX1,lU1,lH1) 43 | # upper integrand 44 | uRange = np.where(x1 > xStar) 45 | uX1 = np.array([xStar] + [ x1[k] for k in uRange[0] ]) 46 | uU1 = np.array([VStar] + [ U1[k] for k in uRange[0] ]) 47 | uH1 = np.array([VStar] + [ H1[k] for k in uRange[0] ]) 48 | [x0, uV0] = self.method.rollBack(T0,T1,uX1,uU1,uH1) 49 | # combine integrations 50 | return [x0, lV0+uV0] 51 | 52 | 53 | class SimpsonIntegration(DensityIntegration): 54 | 55 | # Python constructor 56 | def __init__(self, hwModel, nGridPoints=101, stdDevs=5): 57 | DensityIntegration.__init__(self,hwModel,nGridPoints,stdDevs) 58 | 59 | def rollBack(self, T0, T1, x1, U1, H1): 60 | x0 = self.xSet(T0) 61 | V0 = np.zeros(x0.shape[0]) 62 | sigma = np.sqrt(self.hwModel.varianceX(T0,T1)) 63 | V = np.array([ max(U1[k],H1[k]) for k in range(U1.shape[0]) ]) 64 | for i in range(x0.shape[0]): 65 | mu = self.hwModel.expectationX(T0, x0[i], T1) 66 | fx = np.array([ V[k] * norm.pdf((x1[k]-mu)/sigma)/sigma for k in range(x1.shape[0])]) 67 | I = integrate.simps(fx, x1) 68 | V0[i] = self.hwModel.zeroBond(T0,x0[i],T1) * I 69 | return [x0, V0] 70 | 71 | 72 | class HermiteIntegration(DensityIntegration): 73 | 74 | # Python constructor 75 | def __init__(self, hwModel, degree, nGridPoints=101, stdDevs=5): 76 | DensityIntegration.__init__(self,hwModel,nGridPoints,stdDevs) 77 | (self.hermX, self.hermW) = np.polynomial.hermite.hermgauss(degree) 78 | 79 | def rollBack(self, T0, T1, x1, U1, H1): 80 | x0 = self.xSet(T0) 81 | V0 = np.zeros(x0.shape[0]) 82 | sigma = np.sqrt(self.hwModel.varianceX(T0,T1)) 83 | V = CubicSpline(x1, np.array([ max(U1[k],H1[k]) for k in range(U1.shape[0]) ])) 84 | for i in range(x0.shape[0]): 85 | mu = self.hwModel.expectationX(T0, x0[i], T1) 86 | I = 0.0 87 | for k in range(self.hermX.shape[0]): 88 | I += self.hermW[k] * V(np.sqrt(2.0)*sigma*self.hermX[k] + mu) 89 | I /= np.sqrt(np.pi) 90 | V0[i] = self.hwModel.zeroBond(T0,x0[i],T1) * I 91 | return [x0, V0] 92 | 93 | 94 | class CubicSplineExactIntegration(DensityIntegration): 95 | 96 | # Python constructor 97 | def __init__(self, hwModel, nGridPoints=101, stdDevs=5): 98 | DensityIntegration.__init__(self,hwModel,nGridPoints,stdDevs) 99 | 100 | def rollBack(self, T0, T1, x1, U1, H1): 101 | x0 = self.xSet(T0) 102 | V0 = np.zeros(x0.shape[0]) 103 | sigma = np.sqrt(self.hwModel.varianceX(T0,T1)) 104 | V = CubicSpline(x1, np.array([ max(U1[k],H1[k]) for k in range(U1.shape[0]) ])) 105 | for i in range(x0.shape[0]): 106 | mu = self.hwModel.expectationX(T0, x0[i], T1) 107 | # we need to setup all the coefficients 108 | xBar = np.array([ (x-mu)/sigma for x in V.x ]) 109 | Phi = np.array([ norm.cdf(x) for x in xBar ]) 110 | PhiPrime = np.array([ norm.pdf(x) for x in xBar ]) 111 | F0 = Phi 112 | F1 = -1.0*PhiPrime 113 | F2 = Phi - xBar*PhiPrime 114 | F3 = -1.0*(xBar**2 + 2.0)*PhiPrime 115 | dF0 = F0[1:] - F0[:-1] 116 | dF1 = F1[1:] - F1[:-1] 117 | dF2 = F2[1:] - F2[:-1] 118 | dF3 = F3[1:] - F3[:-1] 119 | I0 = dF0 120 | I1 = sigma*dF1 - sigma*xBar[:-1]*I0 121 | I2 = (sigma**2)*dF2 - 2*sigma*xBar[:-1]*I1 - (sigma**2)*(xBar[:-1]**2)*I0 122 | I3 = (sigma**3)*dF3 - 3*sigma*xBar[:-1]*I2 - 3*(sigma**2)*(xBar[:-1]**2)*I1 - (sigma**3)*(xBar[:-1]**3)*I0 123 | # summing up 124 | I = 0.0 125 | for k in range(xBar.shape[0]-1): 126 | I += V.c[3][k]*I0[k] + V.c[2][k]*I1[k] + V.c[1][k]*I2[k] + V.c[0][k]*I3[k] 127 | V0[i] = self.hwModel.zeroBond(T0,x0[i],T1) * I 128 | return [x0, V0] 129 | 130 | -------------------------------------------------------------------------------- /QuantLibWrapper/HullWhiteModel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | from scipy.optimize import brentq 5 | from QuantLibWrapper.Helpers import Black, Bachelier, BachelierImpliedVol 6 | 7 | class HullWhiteModel: 8 | 9 | # Python constructor 10 | def __init__(self, yieldCurve, meanReversion, volatilityTimes, volatilityValues): 11 | self.yieldCurve = yieldCurve 12 | self.meanReversion = meanReversion 13 | self.volatilityTimes = volatilityTimes # assume positive and ascending 14 | self.volatilityValues = volatilityValues 15 | # pre-calculate y(t) on the time grid 16 | # y(t) = G'(s,t)^2 y(s) + sigma^2 [1 - exp{-2a(t-s)}] / (2a) 17 | t0 = 0.0 18 | y0 = 0.0 19 | self.y_ = np.zeros(len(self.volatilityTimes)) 20 | for i in range(len(self.y_)): 21 | self.y_[i] = (self.GPrime(t0,self.volatilityTimes[i])**2) * y0 + \ 22 | (self.volatilityValues[i]**2) * \ 23 | (1.0 - np.exp(-2*self.meanReversion*(self.volatilityTimes[i]-t0))) / \ 24 | (2.0 * self.meanReversion) 25 | t0 = self.volatilityTimes[i] 26 | y0 = self.y_[i] 27 | 28 | # auxilliary methods 29 | 30 | def G(self, t, T): 31 | return (1.0 - np.exp(-self.meanReversion*(T-t))) / self.meanReversion 32 | 33 | def GPrime(self, t, T): 34 | return np.exp(-self.meanReversion*(T-t)) 35 | 36 | def y(self,t): 37 | # find idx s.t. t[idx] < t < t[idx+1] 38 | idxSet = np.where(self.volatilityTimes X(t0+dt) using independent Brownian increments dW 118 | # t0, dt are assumed float, X0, X1, dW are np.array 119 | def evolve(self, t0, X0, dt, dW): 120 | x1 = self.riskNeutralExpectationX(t0,X0[0],t0+dt) 121 | # x1 = X0[0] + (self.y(t0) - self.meanReversion*X0[0])*dt 122 | nu = np.sqrt(self.varianceX(t0,t0+dt)) 123 | x1 = x1 + nu*dW[0] 124 | # s1 = s0 + \int_t0^t0+dt r dt via Trapezoidal rule 125 | r0 = self.yieldCurve.forwardRate(t0) + X0[0] 126 | r1 = self.yieldCurve.forwardRate(t0+dt) + x1 127 | s1 = X0[1] + (r0 + r1) * dt / 2 128 | # gather results 129 | return np.array([x1, s1]) 130 | 131 | 132 | class HullWhiteModelWithDiscreteNumeraire(HullWhiteModel): 133 | 134 | # Python constructor 135 | def __init__(self, yieldCurve, meanReversion, volatilityTimes, volatilityValues): 136 | HullWhiteModel.__init__(self,yieldCurve,meanReversion,volatilityTimes,volatilityValues) 137 | 138 | # evolve X(t0) -> X(t0+dt) using independent Brownian increments dW 139 | # t0, dt are assumed float, X0, X1, dW are np.array, 140 | # simulation is done with discretely compounded bank account numeraire 141 | # and rolling T-forward measure 142 | def evolve(self, t0, X0, dt, dW): 143 | x1 = self.expectationX(t0,X0[0],t0+dt) 144 | nu = np.sqrt(self.varianceX(t0,t0+dt)) 145 | x1 = x1 + nu*dW[0] 146 | s1 = X0[1] + np.log(1.0/self.zeroBond(t0,X0[0],t0+dt)) 147 | return np.array([x1, s1]) 148 | -------------------------------------------------------------------------------- /QuantLibWrapper/AMCSolver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import numpy as np 4 | 5 | from QuantLibWrapper.Regression import Regression 6 | from QuantLibWrapper.Payoffs import SwapRate 7 | 8 | class AMCSolver: 9 | 10 | # Python constructor 11 | def __init__(self, hwMcSimulation, maxPolynomialDegree=2, splitRatio=0.25): 12 | self.hwMcSimulation = hwMcSimulation 13 | self.maxPolynomialDegree = maxPolynomialDegree 14 | self.minSampleIdx = int(splitRatio*self.hwMcSimulation.nPaths) # we split training data and simulation data 15 | 16 | def getIndexWithTolerance(self,t): 17 | return np.where(abs(self.hwMcSimulation.times-t)<1.0e-8)[0][0] 18 | 19 | def xSet(self,expiryTime): 20 | idx = self.getIndexWithTolerance(expiryTime) 21 | return np.array([ self.hwMcSimulation.X[i][idx] for i in range(self.hwMcSimulation.X.shape[0]) ]) 22 | 23 | def rollBack(self, T0, T1, x1, U1, H1): 24 | x0 = self.xSet(T0) 25 | N0 = np.array([ self.hwMcSimulation.model.numeraire(x0[i]) for i in range(x1.shape[0]) ]) 26 | N1 = np.array([ self.hwMcSimulation.model.numeraire(x1[i]) for i in range(x1.shape[0]) ]) 27 | if self.minSampleIdx>0 and T0>0: # do not use regression for the last roll-back 28 | # we try state variable approach 29 | C = np.array([ [ x0[i][0] ] for i in range(self.minSampleIdx) ]) 30 | O = np.array([ N0[i]/N1[i]*max(U1[i],H1[i]) for i in range(self.minSampleIdx) ]) 31 | R = Regression(C,O,self.maxPolynomialDegree) 32 | else: 33 | R = None 34 | V0 = np.zeros(x1.shape[0]) 35 | for i in range(x1.shape[0]): 36 | V0[i] = N0[i]/N1[i]*max(U1[i],H1[i]) 37 | if R!=None: V0[i] = R.value(np.array([ x0[i][0] ])) 38 | if T0==0: 39 | sampleIdx = self.minSampleIdx if self.minSampleIdx0 and T0>0: # do not use regression for the last roll-back 55 | # we try state variable approach 56 | C = np.array([ [ x0[i][0] ] for i in range(self.minSampleIdx) ]) 57 | O = np.array([ U1[i] - H1[i] for i in range(self.minSampleIdx) ]) 58 | R = Regression(C,O,self.maxPolynomialDegree) 59 | else: 60 | R = None 61 | V0 = np.zeros(x1.shape[0]) 62 | for i in range(x1.shape[0]): 63 | I = U1[i] - H1[i] 64 | if R!=None: I = R.value(np.array([ x0[i][0] ])) 65 | V = U1[i] if I>0 else H1[i] 66 | V0[i] = N0[i] / N1[i] * V 67 | if T0==0: 68 | sampleIdx = self.minSampleIdx if self.minSampleIdx0 and T0>0: # do not use regression for the last roll-back 91 | # we use S and [S-K]^+ as basis functions 92 | #C = np.array([ [ S[i], Sp[i] ] for i in range(self.minSampleIdx) ]) 93 | C = np.array([ [ S[i], L[i] ] for i in range(self.minSampleIdx) ]) 94 | O = np.array([ N0[i]/N1[i]*max(U1[i],H1[i]) for i in range(self.minSampleIdx) ]) 95 | R = Regression(C,O,self.maxPolynomialDegree) 96 | else: 97 | R = None 98 | V0 = np.zeros(x1.shape[0]) 99 | for i in range(x1.shape[0]): 100 | V0[i] = N0[i]/N1[i]*max(U1[i],H1[i]) 101 | #if R!=None: V0[i] = R.value(np.array([ S[i], Sp[i] ])) 102 | if R!=None: V0[i] = R.value(np.array([ S[i], L[i] ])) 103 | if T0==0: 104 | sampleIdx = self.minSampleIdx if self.minSampleIdx0 and T0>0: # do not use regression for the last roll-back 128 | # we use S and [S-K]^+ as basis functions 129 | C = np.array([ [ S[i], Sp[i] ] for i in range(self.minSampleIdx) ]) 130 | #C = np.array([ [ S[i], L[i] ] for i in range(self.minSampleIdx) ]) 131 | O = np.array([ U1[i] - H1[i] for i in range(self.minSampleIdx) ]) 132 | R = Regression(C,O,self.maxPolynomialDegree) 133 | else: 134 | R = None 135 | V0 = np.zeros(x1.shape[0]) 136 | for i in range(x1.shape[0]): 137 | I = U1[i] - H1[i] 138 | if R!=None: I = R.value(np.array([ S[i], Sp[i] ])) 139 | #if R!=None: I = R.value(np.array([ S[i], L[i] ])) 140 | V = U1[i] if I>0 else H1[i] 141 | V0[i] = N0[i] / N1[i] * V 142 | if T0==0: 143 | sampleIdx = self.minSampleIdx if self.minSampleIdx 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------