Skip to content
All library documents

Swap Roll-Down: Modeling the Later Swap and Repricing the Curve

Article Quant Q&A · Author: lcheng

Summary

The discussion examines how to estimate the six-month roll-down of a five-year interest rate swap. It challenges simply subtracting a five-year spot rate and a four-and-a-half-year spot rate, then explains how to represent the original swap after time passes. A shortened swap built from the same start date creates a stub fixed coupon, which changes its fair rate and makes the comparison inconsistent.

Two QuantLib approaches are described: construct a five-year swap with an earlier start date so its remaining cash flows match the rolled swap, or use one swap with a curve whose reference date moves with the evaluation date and recalculate its fair rate later. Another reply frames roll-down under a static curve assumption as involving the already-known initial fixing and the forward swap portion, which is expected to converge toward the shorter-tenor swap. The examples illustrate cash-flow schedule effects, but results depend on curve construction, conventions, and the chosen definition of roll-down.

Key ideas

  • A shortened swap can create a stub fixed coupon and distort the roll-down comparison.
  • The later instrument should preserve the original swap's remaining cash-flow structure.
  • An earlier-start five-year swap can represent the original swap after six months have elapsed.
  • A moving-reference-date curve allows fair-rate repricing of the same swap at a later evaluation date.
  • Roll-down estimates depend on the curve assumption and the precise definition being used.

Tags

Full text
# Use QuantLib Python to calculate roll-down of a swap


# Use QuantLib Python to calculate roll-down of a swap












I would want to use QuantLib Python to calculate 6-month roll-down of a 5-year swap.

I believe that the calculation I need to do is as follows:

$Rolldown=r_{0,5Y}-r_{0,4.5Y}$

Where $r_{0,5Y}$ is the 5Y spot rate and $r_{0,4.5Y}$ is the 4Y spot rate.

You can find the definition of roll-down in the following links:

https://e-markets.nordea.com/api/research/attachment/2796

http://www.gioa.us/presentations/2012/4-Rajappa.pdf

Please let me know whether I got my equation correct.

Assuming that my equation is correct, the following is how I tried to calculate roll-down of the swap using QuantLib Python:

```
from QuantLib import *

# global data
calendar = TARGET()
todaysDate = Date(6,November,2001);
Settings.instance().evaluationDate = todaysDate
settlementDate = Date(8,November,2001);

# market quotes
deposits = { (1,Weeks): 0.0382,
             (1,Months): 0.0372,
             (3,Months): 0.0363,
             (6,Months): 0.0353,
             (9,Months): 0.0348,
             (1,Years): 0.0345 }

swaps = { (2,Years): 0.037125,
          (3,Years): 0.0398,
          (5,Years): 0.0443,
          (10,Years): 0.05165,
          (15,Years): 0.055175 }

# convert them to Quote objects
for n,unit in deposits.keys():
    deposits[(n,unit)] = SimpleQuote(deposits[(n,unit)])
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),(9,Months),(1,Years)] ]

fixedLegFrequency = Annual
fixedLegTenor = Period(1,Years)
fixedLegAdjustment = Unadjusted
fixedLegDayCounter = Thirty360()
floatingLegFrequency = Semiannual
floatingLegTenor = Period(6,Months)
floatingLegAdjustment = ModifiedFollowing
swapHelpers = [ SwapRateHelper(QuoteHandle(swaps[(n,unit)]),
                               Period(n,unit), calendar,
                               fixedLegFrequency, fixedLegAdjustment,
                               fixedLegDayCounter, Euribor6M())
                for n, unit in swaps.keys() ]

# term structure handles

discountTermStructure = RelinkableYieldTermStructureHandle()
forecastTermStructure = RelinkableYieldTermStructureHandle()

# term-structure construction

helpers = depositHelpers + swapHelpers
depoSwapCurve = PiecewiseFlatForward(settlementDate, helpers, Actual360())

swapEngine = DiscountingSwapEngine(discountTermStructure)

# 5Y Swap 

nominal = 1000000
maturity1 = calendar.advance(settlementDate,5,Years)

fixedLegFrequency = Annual
fixedLegAdjustment = Unadjusted
fixedLegDayCounter = Thirty360()
fixedRate = 0.04

floatingLegFrequency = Semiannual
spread = 0.0
fixingDays = 2
index = Euribor6M(forecastTermStructure)
floatingLegAdjustment = ModifiedFollowing
floatingLegDayCounter = index.dayCounter()

fixedSchedule1 = Schedule(settlementDate, maturity1,
                         fixedLegTenor, calendar,
                         fixedLegAdjustment, fixedLegAdjustment,
                         DateGeneration.Forward, False)
floatingSchedule1 = Schedule(settlementDate, maturity1,
                            floatingLegTenor, calendar,
                            floatingLegAdjustment, floatingLegAdjustment,
                            DateGeneration.Forward, False)

spot1 = VanillaSwap(VanillaSwap.Receiver, nominal,
                   fixedSchedule1, fixedRate, fixedLegDayCounter,
                   floatingSchedule1, index, spread,
                   floatingLegDayCounter)
spot1.setPricingEngine(swapEngine)

# 4.5Y Swap 

rolldown_period = Period(6, Months)

maturity2 = calendar.advance(maturity1, -rolldown_period)

fixedSchedule2 = Schedule(settlementDate, maturity2,
                         fixedLegTenor, calendar,
                         fixedLegAdjustment, fixedLegAdjustment,
                         DateGeneration.Forward, False)
floatingSchedule2 = Schedule(settlementDate, maturity2,
                            floatingLegTenor, calendar,
                            floatingLegAdjustment, floatingLegAdjustment,
                            DateGeneration.Forward, False)

spot2 = VanillaSwap(VanillaSwap.Receiver, nominal,
                   fixedSchedule2, fixedRate, fixedLegDayCounter,
                   floatingSchedule2, index, spread,
                   floatingLegDayCounter)
spot2.setPricingEngine(swapEngine)

# price on two different evaluation dates

discountTermStructure.linkTo(depoSwapCurve)
forecastTermStructure.linkTo(depoSwapCurve)

spot5Y = spot1.fairRate()
spot4Y6M = spot2.fairRate()

print('5Y spot rate')
print(spot5Y)
print('4.5Y spot rate')
print(spot4Y6M)
print('6 month roll down')
print(spot5Y - spot4Y6M)
```

Am I correct in how to use QuantLib Python to calculate 6-month roll-down of a 5-year swap? Is it necessary to create 2 swap objects like what I have done in order to calculate swap roll-down?

## Answer by Luigi Ballabio (score 5, accepted)

https://quant.stackexchange.com/a/36942

No, I think you're getting a wrong figure. The general idea might work, but the construction of the second swap must be changed. (Or you can use just one swap; I'll get to that later.)

As I read the documentation you linked, the idea of the second swap would be to model the first swap after 6 months have passed. However, let's look at the two swaps. I'll define a helper function to look at their coupons:

```
def show_cashflows(leg):
    for c in leg:
        print '%20s | %s | %.4f%%' % (c.date(), c.amount(),
                                      as_coupon(c).rate()*100)
```

This is what the fixed leg of the 5-years swap pays...

```
show_cashflows(spot1.fixedLeg())

 November 8th, 2002 | 40000.0 | 4.0000%
November 10th, 2003 | 40000.0 | 4.0000%
 November 8th, 2004 | 40000.0 | 4.0000%
 November 8th, 2005 | 40000.0 | 4.0000%
 November 8th, 2006 | 40000.0 | 4.0000%
```

...and here is the floating leg:

```
show_cashflows(spot1.floatingLeg())

      May 8th, 2002 | 17748.0555555 | 3.5300%
 November 8th, 2002 | 16930.6254304 | 3.3125%
      May 8th, 2003 | 19219.7194265 | 3.8227%
November 10th, 2003 | 19755.8616671 | 3.8237%
     May 10th, 2004 | 22498.6599297 | 4.4503%
 November 8th, 2004 | 22498.6599297 | 4.4503%
      May 9th, 2005 | 25550.9745581 | 5.0540%
 November 8th, 2005 | 25693.1528497 | 5.0544%
      May 8th, 2006 | 25408.8159749 | 5.0537%
 November 8th, 2006 | 25835.3508522 | 5.0547%
```

Your second swap should give you the same coupons, as seen from an evaluation date 6 months later. The floating leg looks the same, except that one coupon is gone:

```
show_cashflows(spot2.floatingLeg())

      May 8th, 2002 | 17748.0555555 | 3.5300%
 November 8th, 2002 | 16930.6254304 | 3.3125%
      May 8th, 2003 | 19219.7194265 | 3.8227%
November 10th, 2003 | 19755.8616671 | 3.8237%
     May 10th, 2004 | 22498.6599297 | 4.4503%
 November 8th, 2004 | 22498.6599297 | 4.4503%
      May 9th, 2005 | 25550.9745581 | 5.0540%
 November 8th, 2005 | 25693.1528497 | 5.0544%
      May 8th, 2006 | 25408.8159749 | 5.0537%
```

(It looks like the last coupon is gone, not the first; but that's because you're assuming that the curve stays the same and shifts in time, and therefore the fixings of the second coupon in six months will equal the fixing of the first coupon now.)

However, here's what the fixed leg looks like:

```
show_cashflows(spot2.fixedLeg())

 November 8th, 2002 | 40000.0 | 4.0000%
November 10th, 2003 | 40000.0 | 4.0000%
 November 8th, 2004 | 40000.0 | 4.0000%
 November 8th, 2005 | 40000.0 | 4.0000%
      May 8th, 2006 | 20000.0 | 4.0000%
```

Since you created a 4.5 years schedule, the last coupon is shorter and is missing half the amount. This, of course, causes the fair rate to be off.

```
print spot2.fairRate()

0.0434536470094
```

You have a couple of ways to fix this.

First, instead of creating a 4.5 years swap, you can create a 5-years swap starting 6 months in the past:

```
issue2 = calendar.advance(settlementDate, -rolldown_period)
maturity2 = calendar.advance(issue2, 5, Years)

fixedSchedule2 = Schedule(issue2, maturity2,
                         fixedLegTenor, calendar,
                         fixedLegAdjustment, fixedLegAdjustment,
                         DateGeneration.Forward, False)
floatingSchedule2 = Schedule(issue2, maturity2,
                            floatingLegTenor, calendar,
                            floatingLegAdjustment, floatingLegAdjustment,
                            DateGeneration.Forward, False)

swap3 = VanillaSwap(VanillaSwap.Receiver, nominal,
                    fixedSchedule2, fixedRate, fixedLegDayCounter,
                    floatingSchedule2, index, spread,
                    floatingLegDayCounter)
swap3.setPricingEngine(swapEngine)
```

Now the fixed leg pays the correct amounts...

```
show_cashflows(swap3.fixedLeg())

     May 8th, 2002 | 40000.0 | 4.0000%
     May 8th, 2003 | 40000.0 | 4.0000%
    May 10th, 2004 | 40000.0 | 4.0000%
     May 9th, 2005 | 40000.0 | 4.0000%
     May 8th, 2006 | 40000.0 | 4.0000%
```

...and the fair rate changes accordingly.

```
print swap3.fairRate()

0.0387677146311
```

The second way to fix it is, as you suggested, to use a single swap.

You can build the term structure so that it moves with the evaluation date; instead of passing the reference date explicitly, tell the curve that the reference date should be two business days after the evaluation date.

```
depoSwapCurve = PiecewiseFlatForward(2, TARGET(), helpers, Actual360())
```

The rest of the initialization stays the same. With this setup, you can simply ask the fair rate of the swap `spot1` at two different evaluation dates:

```
Settings.instance().evaluationDate = todaysDate
print spot1.fairRate()

0.0443

Settings.instance().evaluationDate = calendar.advance(todaysDate, 6, Months)
print spot1.fairRate()

0.038847797035
```

(The fair rate after 6 months is similar, but not quite the same as the one calculated in the first way. It's probably due to the curve being a bit different due; the tenors of the deposits used for bootstrapping might equal a different number of days depending on the spot date.)

## Answer by Attack68 (score 4)

https://quant.stackexchange.com/a/36941

Don't use the Nordea piece - it's not correct. Let me explain why;

If rolldown is the assumption that the IRS curve remains static through time then for a 5Y IRS you have two components; the first fixing, i.e. 0.5Y part which is known, and the 0.5Y4.5Y fwd IRS, which is floating and yet to fix.

If, through rolldown, you assume that the 0.5Y4.5Y IRS will converge to the current 0Y4.5Y IRS then that is what you should be subtracting in order to obtain the number of basis points that are applicable to the portion of risk that exists for the extent of the 0.5Y4.5Y swap.

I don't know why carry and rolldown are so poorly and universally misunderstood in finance but many research pieces from banks are wrong or, if not wrong, poorly conceived and overly complicated. If you want to read accurate details for IRSs consider "pricing and trading interest rate derivatives: a pratical guide to swaps" by JHM Darbyshire (this is my book), and also take a look at these other answers...

Carry calculation on an interest rate swap

Question on pure carry for two bonds

question regarding carry & roll of a bond

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.