0
$\begingroup$

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")
$\endgroup$
3
  • $\begingroup$ It seems to be that the code both finds the price of an IRS and simulates paths from the Hull-White 1-factor model, so I am not quite sure what the question is? $\endgroup$
    – mmencke
    Sep 1 at 21:25
  • $\begingroup$ Can we simulate the price of IRS using Hull White 1-Factor model? $\endgroup$
    – Desi_Quant
    Sep 3 at 6:24
  • $\begingroup$ Sorry, I misread the question. I have tried to answer it to the best of my ability. $\endgroup$
    – mmencke
    Sep 4 at 20:03
1
$\begingroup$

For a plain vanilla swap we can define the annuity $$A(t)\equiv\sum_{i=0}^{N-1} \tau_i P(t,T_{i+1})$$ and write the swap rate as $$S(t)\equiv\frac{P(t,T_0)-P(t,T_N)}{A(t)}$$ such that the swap price can be written as $$V_{swap}(t)=A(t)(S(t)-c)$$ where $c$ is the fixed rate coupon. It would be great if we were able to simulate the swap rate (and annuity) directly, but I am not quite sure whether this is possible in the Hull-White model. On pages 422 to 423 in Andersen & Piterbarg (2010) there is an approximation for the distribution of the swap rate for a general Gaussian one-factor short rate model and how this can be used for pricing swaptions. Maybe the results can be used for simulating the swap rate directly, but I am not sure. Be aware that the specification is a bit more general with Hull-White being a special case.

Another possibility would be to construct the implied discount curve on each step of the path, update the interest rate index and re-price the swap. This is however very computationally expensive and not very elegant. I have made some C++ code here that does this to price Credit Value Adjustment, which you can use for inspiration if you choose to pursue this option.

$\endgroup$
1
  • $\begingroup$ Thanks mmencke! Let me try running the C++ code. $\endgroup$
    – Desi_Quant
    Sep 5 at 13:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.