I am trying to price plain vanilla interest rate swap (IRS) using QuantLib. What I am trying to do is to generate a path of simulated IRS price by simulating the interest rates using HW 1 Factor model. Here is what I have accomplished so far:
import prettytable as pt
import matplotlib.pyplot as plt
import numpy as np
calendar = ql.TARGET()
evaluationDate = ql.Date(15, ql.April, 2019)
ql.Settings.instance().evaluationDate = evaluationDate
# discount curve
curveDates = [
ql.Date(15, ql.April, 2019), ql.Date(23, ql.April, 2019), ql.Date(16, ql.May, 2019), ql.Date(16, ql.July, 2019),
ql.Date(16, ql.October, 2019), ql.Date(16, ql.April, 2020), ql.Date(16, ql.October, 2020), ql.Date(16, ql.April, 2021),
ql.Date(19, ql.April, 2022), ql.Date(17, ql.April, 2023), ql.Date(16, ql.April, 2024), ql.Date(16, ql.April, 2025),
ql.Date(16, ql.April, 2026), ql.Date(16, ql.April, 2027), ql.Date(18, ql.April, 2028), ql.Date(16, ql.April, 2029),
ql.Date(16, ql.April, 2030), ql.Date(16, ql.April, 2031), ql.Date(17, ql.April, 2034), ql.Date(18, ql.April, 2039),
ql.Date(19, ql.April, 2044), ql.Date(20, ql.April, 2049), ql.Date(16, ql.April, 2054), ql.Date(16, ql.April, 2059),
ql.Date(16, ql.April, 2064), ql.Date(16, ql.April, 2069)]
discountFactors = [
1.0, 1.0000735, 1.0003059, 1.0007842, 1.0011807, 1.0023373, 1.0033115,
1.0039976, 1.0039393, 1.0015958, 0.9972325, 0.9907452, 0.9820912, 0.9715859,
0.9591332, 0.9455427, 0.9311096, 0.9161298, 0.8705738, 0.8017461, 0.7464983,
0.7010373, 0.6626670, 0.6289098, 0.5974307, 0.5684840]
discountCurve = ql.DiscountCurve(
curveDates,
discountFactors,
ql.Actual360(), #
calendar)
discountCurveHandle = ql.YieldTermStructureHandle(discountCurve)
euriborIndex = ql.Euribor6M(discountCurveHandle)
# add fixing dates and rates for floating leg
unusedRate = 0.0 # not used in pricing
#euribor-6M at 2019-01-17
rate20190117 = -0.00236
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.January, 2007), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.July, 2007), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.January, 2008), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.July, 2008), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(15, ql.January, 2009), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(16, ql.July, 2009), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(15, ql.January, 2010), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(15, ql.July, 2010), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.January, 2011), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(15, ql.July, 2011), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.January, 2012), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.July, 2012), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.January, 2013), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.July, 2013), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(16, ql.January, 2014), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.July, 2014), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(15, ql.January, 2015), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(16, ql.July, 2015), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(15, ql.January, 2016), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(15, ql.July, 2016), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.January, 2017), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.July, 2017), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.January, 2018), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.July, 2018), fixing=unusedRate)
euriborIndex.addFixing(fixingDate=ql.Date(17, ql.January, 2019), fixing=rate20190117)
# swap contract
nominal = 36200000.0
spread = 0.009
swapType = ql.VanillaSwap.Receiver
lengthInYears = 25
effectiveDate = ql.Date(19, ql.January, 2007)
terminationDate = effectiveDate + ql.Period(lengthInYears, ql.Years)
# fixed leg
fixedLegFrequency = ql.Period(ql.Semiannual)
fixedLegConvention = ql.ModifiedFollowing
fixedLegDayCounter = ql.Thirty360(ql.Thirty360.USA)
fixedDateGeneration = ql.DateGeneration.Forward
fixedRate = 0.045
fixedSchedule = ql.Schedule(
effectiveDate,
terminationDate,
fixedLegFrequency,
calendar,
fixedLegConvention,
fixedLegConvention,
fixedDateGeneration,
False)
# floating leg
floatingLegFrequency = ql.Period(ql.Semiannual)
floatingLegConvention = ql.ModifiedFollowing
floatingLegDayCounter = ql.Actual360()
floatingDateGeneration = ql.DateGeneration.Forward
floatSchedule = ql.Schedule(
effectiveDate,
terminationDate,
floatingLegFrequency,
calendar,
floatingLegConvention,
floatingLegConvention,
floatingDateGeneration,
False)
spot25YearSwap = ql.VanillaSwap(
swapType,
nominal,
fixedSchedule,
fixedRate,
fixedLegDayCounter,
floatSchedule,
euriborIndex,
spread,
floatingLegDayCounter)
swapEngine = ql.DiscountingSwapEngine(discountCurveHandle)
spot25YearSwap.setPricingEngine(swapEngine)
fixedNpv = 0.0
floatingNpv = 0.0
for cf in spot25YearSwap.fixedLeg():
if cf.date() > evaluationDate:
fixedNpv = fixedNpv + discountCurveHandle.discount(cf.date()) * cf.amount()
print('Fixed Leg Cash Flows (no nominal):',fixedNpv)
fixedNpv = fixedNpv + discountCurveHandle.discount(spot25YearSwap.fixedLeg()[-1].date()) * nominal
for cf in spot25YearSwap.floatingLeg():
if cf.date() > evaluationDate:
floatingNpv = floatingNpv + discountCurveHandle.discount(cf.date()) * cf.amount()
print('Floating Leg Cash Flows (no nominal):', floatingNpv)
floatingNpv = floatingNpv + discountCurveHandle.discount(spot25YearSwap.floatingLeg()[-1].date()) * nominal
print('Sum of Fixed Leg Cash Flows (no nominal):',fixedNpv)
print('Sum of Floating Leg Cash Flows (no nominal):', floatingNpv)
print('Price of the Swap is ',fixedNpv - floatingNpv)
###################################################Simulating Short Rates##################################################
sigma = 0.1
a = 0.03
timestep = 360
length = 15 # in years
forward_rate = 0.05
day_count = ql.Thirty360()
todays_date = ql.Date(15, 4, 2019)
ql.Settings.instance().evaluationDate = todays_date
#spot_curve = ql.FlatForward(todays_date, ql.QuoteHandle(ql.SimpleQuote(forward_rate)), day_count)
#spot_curve_handle = ql.YieldTermStructureHandle(spot_curve)
hw_process = ql.HullWhiteProcess(discountCurveHandle, a, sigma)
rng = ql.GaussianRandomSequenceGenerator(ql.UniformRandomSequenceGenerator(timestep, ql.UniformRandomGenerator()))
seq = ql.GaussianPathGenerator(hw_process, length, timestep, rng, False)
def generate_paths(num_paths, timestep):
arr = np.zeros((num_paths, timestep+1))
for i in range(num_paths):
sample_path = seq.next()
path = sample_path.value()
time = [path.time(j) for j in range(len(path))]
value = [path[j] for j in range(len(path))]
arr[i, :] = np.array(value)
return np.array(time), arr
num_paths = 1
time, paths = generate_paths(num_paths, timestep)
for i in range(num_paths):
plt.plot(time, paths[i, :], lw=0.8, alpha=0.6)
plt.title("Hull-White Short Rate Simulation")
plt.show()
num_paths = 1000
time, paths = generate_paths(num_paths, timestep)
vol = [np.var(paths[:, i]) for i in range(timestep+1)]
plt.plot(time, vol, "r-.", lw=3, alpha=0.6)
plt.plot(time,sigma*sigma/(2*a)*(1.0-np.exp(-2.0*a*np.array(time))), "b-", lw=2, alpha=0.5)
plt.title("Variance of Short Rates")
def alpha(forward, sigma, a, t):
return forward + 0.5* np.power(sigma/a*(1.0 - np.exp(-a*t)), 2)
avg = [np.mean(paths[:, i]) for i in range(timestep+1)]
plt.plot(time, avg, "r-.", lw=3, alpha=0.6)
plt.plot(time,alpha(forward_rate, sigma, a, time), "b-", lw=2, alpha=0.6)
plt.title("Mean of Short Rates")