Sunday, March 23, 2014

European style Interest Rate Swaption pricing using - Python-Quantlib


In previous posts, I have described how to construct USD LIBOR Swap Yield curve then we have looked at how to price an interest rate swap. In this note I will discuss what is European Swaption and how to value such a product using Quantlib.

Product Description:

 European Swaptions are instruments that give holder of the option right to Pay or receive fixed rate. This option can be classified into two different types. Payer Swaption in which option buyer gets right to receive fixed rate and pay floating rate at the exercise date if exercised. Receiver Swaption works exactly opposite to the payer swaption where the option holder has the right to receive fixed rate.


Variety of players,including, Hedge funds, banks and pension funds are actively participating in this market to monetize their views on the shapes of yield curve or conduct risk management of their existing portfolios. Consequently it becomes imperative to know if the price they are paying or getting on the swaption is right. To understand this one has to have a tool set to price a swaption.

Pricing and Modeling of the Swaption:

 I have covered some details with regards to how to price a 5y into 5y ATM (at the money) swaption. To be able to value this swaption, I have constructed an yield curve ( with the details of the instruments and curve construction provided below) and then priced a 5y forward 5y swap. Then applying a 15.3% implied volatility I have priced a payer swaption yielding price of 23,162. In general these long dated swaptions are vega sesitive. In other words they appreciate or depreciate when Implied volatility changes significantly. To understand this effect,I have changed the price of the option to 60,000 and implied volatility changed to 40%. Calculations for how to perform these computations are provided step by step using python and Quantlib.
Feel free to use these in your calculation or learning process. Also available other articles on this blog that discuss nuances of swaption market, pricing model and risk management.

# Copyright (C) 2014, Khandrika Capital Markets

# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the license for more details.

from QuantLib import *
import datetime
import numpy as np
import matplotlib.pyplot as plt


# global data
# Here enter the Yield Curve reference Data
calendar = TARGET()
todaysDate = Date(28,February,2014);
Settings.instance().evaluationDate = todaysDate
settlementDate = Date(4,March,2014);

# market quotes
# Update deposit Rates ( usual source will be LIBOR Fixings on the Curve Date
deposits = { (1,Weeks): 0.0023,
             (1,Months): 0.0023,
             (3,Months): 0.0023,
             (6,Months): 0.0023}
# Obtain Futures prices from CME traded Euro Dollar Futures
futures = { Date(19,3,2014): 99.765,
            Date(18,6,2014): 99.75,
            Date(17,9,2014): 99.73,
            Date(17,12,2014): 99.69,
            Date(18,3,2015): 99.605,
            Date(17,6,2015): 99.47,
            Date(16,9,2015): 99.3,
            Date(16,12,2015): 99.085 }
# Obtain Swap rates from Traded Swaps on the Curve data
swaps = { (3,Years): 0.0079,
          (4,Years): 0.012,
          (5,Years): 0.0157,
          (6,Years): 0.01865,
          (7,Years): 0.0216,
          (8,Years): 0.0235,
          (9,Years): 0.0254,
          (10,Years): 0.0273,
          (15,Years): 0.0297,
          (20,Years): 0.0316,
          (25,Years): 0.0335,
          (30,Years): 0.0354}

# convert them to Quote objects
for n,unit in deposits.keys():
    deposits[(n,unit)] = SimpleQuote(deposits[(n,unit)])
for d in futures.keys():
    futures[d] = SimpleQuote(futures[d])
for n,unit in swaps.keys():
    swaps[(n,unit)] = SimpleQuote(swaps[(n,unit)])

# build rate helpers

dayCounter = Actual360()
settlementDays = 2
depositHelpers = [ DepositRateHelper(QuoteHandle(deposits[(n,unit)]),
                                     Period(n,unit), settlementDays,
                                     calendar, ModifiedFollowing,
                                     False, dayCounter)
                   for n, unit in [(1,Weeks),(1,Months),(3,Months),
                                   (6,Months)] ]

dayCounter = Actual360()
months = 3
futuresHelpers = [ FuturesRateHelper(QuoteHandle(futures[d]),
                                     d, months,
                                     calendar, ModifiedFollowing,
                                     True, dayCounter,
                                     QuoteHandle(SimpleQuote(0.0)))
                   for d in futures.keys() ]

settlementDays = 2
fixedLegFrequency = Semiannual
fixedLegTenor = Period(6,Months)
fixedLegAdjustment = Unadjusted
fixedLegDayCounter = Thirty360()
floatingLegFrequency = Quarterly
floatingLegTenor = Period(3,Months)
floatingLegAdjustment = ModifiedFollowing
swapHelpers = [ SwapRateHelper(QuoteHandle(swaps[(n,unit)]),
                               Period(n,unit), calendar,
                               fixedLegFrequency, fixedLegAdjustment,
                               fixedLegDayCounter, Euribor3M())
                for n, unit in swaps.keys() ]

# term structure handles

discountTermStructure = RelinkableYieldTermStructureHandle()
forecastTermStructure = RelinkableYieldTermStructureHandle()

# term-structure construction

helpers = depositHelpers[:2] + futuresHelpers + swapHelpers[1:]
depoFuturesSwapCurve = PiecewiseFlatForward(settlementDate, helpers,
                                            Actual360())
print depoFuturesSwapCurve.dates()
df=[]
dates1=[]
for c in depoFuturesSwapCurve.dates():
    df.append(depoFuturesSwapCurve.discount(c))
    dates1.append(c)
    print depoFuturesSwapCurve.discount(c)

termStructure = YieldTermStructureHandle(depoFuturesSwapCurve)

    

#End of Yield Curve Construction

#Begin building forward Curve

# Forward swap underlying the Swaption to be priced
# In this case I am pricing a 5y into 5Y swap
swapEngine = DiscountingSwapEngine(discountTermStructure)

nominal = 1000000
length = 5
maturity = calendar.advance(settlementDate,length,Years)
payFixed = True

fixedLegFrequency = Semiannual
fixedLegAdjustment = Unadjusted
fixedLegDayCounter = Thirty360()

floatingLegFrequency = Quarterly
spread = 0.0
fixingDays = 2
index = Euribor3M(forecastTermStructure)
floatingLegAdjustment = ModifiedFollowing
floatingLegDayCounter = index.dayCounter()

#ATM forward Rate
fixedRate = 0.040852

forwardStart = calendar.advance(settlementDate,5,Years)
forwardEnd = calendar.advance(forwardStart,length,Years)
fixedSchedule = Schedule(forwardStart, forwardEnd,
                         fixedLegTenor, calendar,
                         fixedLegAdjustment, fixedLegAdjustment,
                         DateGeneration.Forward, False)
floatingSchedule = Schedule(forwardStart, forwardEnd,
                            floatingLegTenor, calendar,
                            floatingLegAdjustment, floatingLegAdjustment,
                            DateGeneration.Forward, False)

forward = VanillaSwap(VanillaSwap.Payer, nominal,
                      fixedSchedule, fixedRate, fixedLegDayCounter,
                      floatingSchedule, index, spread,
                      floatingLegDayCounter)
forward.setPricingEngine(swapEngine)




def formatPrice(p,digits=2):
    format = '%%.%df' % digits
    return format % p

def formatRate(r,digits=2):
    format = '%%.%df %%%%' % digits
    return format % (r*100)

headers = ("term structure", "net present value",
           "fair spread", "fair fixed rate" )
separator = " | "

format = ''
width = 0
for h in headers[:-1]:
    format += '%%%ds' % len(h)
    format += separator
    width += len(h) + len(separator)
format += '%%%ds' % len(headers[-1])
width += len(headers[-1])

rule = "-" * width
dblrule = "=" * width
tab = " " * 8

def report(swap, name):
    print format % (name, formatPrice(swap.NPV(),2),
                    formatRate(swap.fairSpread(),4),
                    formatRate(swap.fairRate(),4))

print dblrule
print "5-year market Spot swap-rate = %s" % formatRate(swaps[(5,Years)].value())
print dblrule

discountTermStructure.linkTo(depoFuturesSwapCurve)
forecastTermStructure.linkTo(depoFuturesSwapCurve)
report(forward,'depo-fut-swap')


##############################################
# Bulding the European Swaption pricer part

exercise = maturity
exercised = EuropeanExercise(exercise)
settlementtype="physical"
atmswaption = Swaption(forward,exercised)
#Applying a 15.3% implied volatility to 5y into 5y ATM swaption
vol1  = QuoteHandle(SimpleQuote(0.1533))
atmswaption.setPricingEngine(BlackSwaptionEngine(termStructure,vol1))
print atmswaption.NPV()

#*****************************
#Now given Market Premium implying the underlying volatility

index = Euribor3M(termStructure)
#Place holder for the iterator to hold the implied volatility in the
#swaption Helper
swaptionVols = [ # maturity,          length,             volatility
                 (Period(5, Years), Period(5, Years), 0.1533)]

helpers = [ SwaptionHelper(maturity, length,
                           QuoteHandle(SimpleQuote(vol)),
                           index, index.tenor(), index.dayCounter(),
                           index.dayCounter(), termStructure)
                    for maturity, length, vol in swaptionVols ]

for swaption, helper in zip(swaptionVols, helpers):
        maturity, length, vol = swaption
        print swaption
        helper.setPricingEngine(BlackSwaptionEngine(termStructure,vol1))
        NPV = helper.modelValue()
        print NPV
        NPV=0.06   # here we are adding a premium of 60,000 per 1 MM Notional
        implied = helper.impliedVolatility(NPV, 1.0e-4, 1000, 0.05, 0.50)
        print implied

5 comments:

  1. your examples are really SUPER helpful thank you so much. I have one question, how do you obtain the swap rates from traded swaps? (Obtain Swap rates from Traded Swaps on the Curve data)?

    ReplyDelete
  2. Nevermind, I figured it out. It feels silly not seeing that earlier :-) THank you very much for your work on this website.

    ReplyDelete
  3. Hi. I have a question in regards to the ATM forward rate. the ATM forward rate is the rate at time of the Swaption expiry, derived from the currency of the underlying? Please correct me if i am wrong.

    ReplyDelete
    Replies
    1. Yes thats correct. Forward rate underlying swaption will have a start date as option expiry date and maturity date as underlying swap maturity date. You can derive this from today;s yield curve.

      Delete