2
$\begingroup$

Just starting to learn Quantlib for Python.

I am trying to figure out how you create an interest rate swap where the floating leg is a percent of the floating index. For example, the floating leg would be 70% of 1M USD LIBOR.

Any help is greatly appreciated.

Note: For those who are curious, the % of LIBOR swaps are a common structure for municipal entities in the United States. These entities can issue tax-exempt debt and the floating leg is adjusted so the % = (1 - marginal tax rate).

$\endgroup$
2
$\begingroup$

There are two ways to create the swap you want: (1) creating individual legs (ql.FixedRateLeg and IborLeg) where you can provide a gearing to the ql.IborLeg and build a swap (ql.Swap) with those or (2) Use the ql.NonstandardSwap class

First I'll define the general boilerplate code to use in the examples:

import QuantLib as ql
import pandas as pd

yts = ql.YieldTermStructureHandle(ql.FlatForward(2, ql.TARGET(), 0.05, ql.Actual360()))
engine = ql.DiscountingSwapEngine(yts)
index = ql.USDLibor(ql.Period('6M'), yts)

schedule = ql.MakeSchedule(ql.Date(15,6,2021), ql.Date(15,6,2023), ql.Period('6M'))
nominal = [10e6]

1. Creating individual legs

Before defining a gearing, we can build a simple swap and examine it's floating leg:

fixedLeg = ql.FixedRateLeg(schedule, index.dayCounter(), nominal, [0.05])
floatingLeg = ql.IborLeg(nominal, schedule, index)
swap = ql.Swap(fixedLeg, floatingLeg)
swap.setPricingEngine(engine)

print(f"Floating leg NPV: {swap.legNPV(1):,.2f}\n")
pd.DataFrame([{
    'fixingDate': cf.fixingDate().ISO(),
    'accrualStart': cf.accrualStartDate().ISO(),
    'accrualEnd': cf.accrualEndDate().ISO(),
    "paymentDate": cf.date().ISO(),
    'gearing': cf.gearing(),
    'forward': cf.indexFixing(),
    'rate': cf.rate(),
    "amount": cf.amount()
} for cf in map(ql.as_floating_rate_coupon, swap.leg(1))])

Notice by default the gearing will be 1 so the leg rate with be the same as the fixing/forwards.

enter image description here

Next, we use the "gearings" parameter of the ql.IborLeg constructor:

floatingLeg = ql.IborLeg(nominal, schedule, index, gearings=[0.7])
swap = ql.Swap(fixedLeg, floatingLeg)
swap.setPricingEngine(engine)

print(f"Floating leg NPV: {swap.legNPV(1):,.2f}\n")
pd.DataFrame([{
    'fixingDate': cf.fixingDate().ISO(),
    'accrualStart': cf.accrualStartDate().ISO(),
    'accrualEnd': cf.accrualEndDate().ISO(),
    "paymentDate": cf.date().ISO(),
    'gearing': cf.gearing(),
    'forward': cf.indexFixing(),
    'rate': cf.rate(),
    "amount": cf.amount()
} for cf in map(ql.as_floating_rate_coupon, swap.leg(1))])

Notice here, we have ajusted the leg rate to be the fixing/forwards times the gearing.

enter image description here

Another, a bit more sloppy, way to do it would be to multiply the notional by the gearing on the iborLeg instead of using the gearing parameter.

2. NonstandardSwap

The exact same thing can be done with the ql.NonstandardSwap class, although you have to be more careful with the constructor as it expects arrays of the notional, rate, spreads, gearings, etc, with the same size as the respective payment schedule.

swapType = ql.VanillaSwap.Payer
numDates = (len(schedule)-1)
gearing = [0.7] * numDates
spread = [0.0] * numDates
fixedRateArray = [0.05] * numDates
nominalArray = nominal * numDates
nsSwap = ql.NonstandardSwap(
    swapType, nominalArray, nominalArray,
    schedule, fixedRateArray, index.dayCounter(),
    schedule, index, gearing, spread, index.dayCounter())

nsSwap.setPricingEngine(engine)
print(f"Floating leg NPV: {nsSwap.legNPV(1):,.2f}\n")
pd.DataFrame([{
    'fixingDate': cf.fixingDate().ISO(),
    'accrualStart': cf.accrualStartDate().ISO(),
    'accrualEnd': cf.accrualEndDate().ISO(),
    "paymentDate": cf.date().ISO(),
    'gearing': cf.gearing(),
    'forward': cf.indexFixing(),
    'rate': cf.rate(),
    "amount": cf.amount()
} for cf in map(ql.as_floating_rate_coupon, swap.leg(1))])

enter image description here

| improve this answer | |
$\endgroup$
  • $\begingroup$ Thank you for the very detailed response. I will likely use the non-standard swap as almost all the swaps I would be evaluating are amortizing and so I'll already be working with arrays. The code mapping into the dataframe is also very helpful. I have the payment schedule so I can use that and compare against the schedule generated by QuantLib. $\endgroup$ – jDraper Oct 26 at 14:29
3
$\begingroup$

This feature is called "gearing".

If you look in https://github.com/lballabio/QuantLib/blob/master/ql/cashflows/floatingratecoupon.hpp , you see

Real gearing = 1.0,

Related question: https://stackoverflow.com/questions/40283195/

| improve this answer | |
$\endgroup$
  • $\begingroup$ Much appreciated. For Python, do you know if the gearing setting is only available for the ql.NonstandardSwap instrument? It does not appear to be available to ql.Swap or ql.VanillaSwap. $\endgroup$ – jDraper Oct 23 at 22:49
  • 2
    $\begingroup$ As Luigi wrote in his answer, gearing is not exposed in VanillaSwap . You may be able to create FixedLeg and IborLeg (not sure how to say .withGearings(gearing) in Python) and pass them both to Swap. Or maybe it is not exposed in Python at all. By the way we already saw a SOFR-linked municipal bond with gearing, issued by NY Metropolitan Transportation Authority. bondbuyer.com/news/… $\endgroup$ – Dimitri Vulis Oct 23 at 23:04
  • 2
    $\begingroup$ quantlib-python-docs.readthedocs.io/en/latest/… the NonStandardSwap could be a solution. $\endgroup$ – Kermittfrog Oct 24 at 3:50

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.