import QuantLib as ql
import pandas as pd
Asset swaps
= ql.Date(20, ql.May, 2021)
today = today ql.Settings.instance().evaluationDate
The AssetSwap
class builds a swap that exchanges the coupons from a given bond for floating-rate coupons.
Let’s take a fixed-rate bond as an example:
= ql.Schedule(
schedule 8, ql.February, 2020),
ql.Date(8, ql.February, 2025),
ql.Date(6, ql.Months),
ql.Period(
ql.TARGET(),
ql.Following,
ql.Following,
ql.DateGeneration.Backward,False,
)= 3
settlementDays = 100
faceAmount = [0.03]
coupons = ql.Thirty360(ql.Thirty360.BondBasis)
paymentDayCounter
= ql.FixedRateBond(
bond
settlementDays, faceAmount, schedule, coupons, paymentDayCounter )
Besides the bond, the AssetSwap
constructor takes the floating-rate index used to fix the exchanged coupons…
= ql.RelinkableYieldTermStructureHandle(
forecast_curve 0.01, ql.Actual360())
ql.FlatForward(today,
)= ql.Euribor6M(forecast_curve) index
…and other parameters: a spread over the floating rate, the bond price, the schedule for the floating-rate coupons (which is optional: if we pass an empty one, the swap will use the same as the bond), the day-count convention for the floating-rate coupons, and a couple of flags specifying the kind of swap we’re creating.
= 0.0050
spread = 103.0
bond_price
= True
pay_fixed = False
par_asset_swap
= ql.AssetSwap(
swap
pay_fixed,
bond,
bond_price,
index,
spread,
ql.Schedule(),
index.dayCounter(),
par_asset_swap, )
When par_asset_swap = False
, the swap creates floating-rate coupons paid on a notional equal to the bond price. As for bonds, it’s possible to extract the coupons and retrieve information on each one:
def print_coupon_info(cashflows):
= []
data for cf in cashflows:
= ql.as_coupon(cf)
c if c is not None:
data.append((c.date(), c.rate(), c.nominal(), c.amount()))else:
None, None, cf.amount()))
data.append((cf.date(),
return pd.DataFrame(
=["date", "rate", "notional", "amount"]
data, columnsformat(
).style."amount": "{:.2f}", "notional": "{:.2f}", "rate": "{:.2%}"}
{ )
print_coupon_info(bond.cashflows())
date | rate | notional | amount | |
---|---|---|---|---|
0 | August 10th, 2020 | 3.00% | 100.00 | 1.50 |
1 | February 8th, 2021 | 3.00% | 100.00 | 1.48 |
2 | August 9th, 2021 | 3.00% | 100.00 | 1.51 |
3 | February 8th, 2022 | 3.00% | 100.00 | 1.49 |
4 | August 8th, 2022 | 3.00% | 100.00 | 1.50 |
5 | February 8th, 2023 | 3.00% | 100.00 | 1.50 |
6 | August 8th, 2023 | 3.00% | 100.00 | 1.50 |
7 | February 8th, 2024 | 3.00% | 100.00 | 1.50 |
8 | August 8th, 2024 | 3.00% | 100.00 | 1.50 |
9 | February 10th, 2025 | 3.00% | 100.00 | 1.52 |
10 | February 10th, 2025 | nan% | nan | 100.00 |
0)) print_coupon_info(swap.leg(
date | rate | notional | amount | |
---|---|---|---|---|
0 | August 9th, 2021 | 3.00% | 100.00 | 1.51 |
1 | February 8th, 2022 | 3.00% | 100.00 | 1.49 |
2 | August 8th, 2022 | 3.00% | 100.00 | 1.50 |
3 | February 8th, 2023 | 3.00% | 100.00 | 1.50 |
4 | August 8th, 2023 | 3.00% | 100.00 | 1.50 |
5 | February 8th, 2024 | 3.00% | 100.00 | 1.50 |
6 | August 8th, 2024 | 3.00% | 100.00 | 1.50 |
7 | February 10th, 2025 | 3.00% | 100.00 | 1.52 |
8 | February 10th, 2025 | nan% | nan | 100.00 |
1)) print_coupon_info(swap.leg(
date | rate | notional | amount | |
---|---|---|---|---|
0 | August 10th, 2021 | 1.50% | 103.89 | 0.33 |
1 | February 10th, 2022 | 1.50% | 103.89 | 0.80 |
2 | August 10th, 2022 | 1.50% | 103.89 | 0.78 |
3 | February 10th, 2023 | 1.50% | 103.89 | 0.80 |
4 | August 10th, 2023 | 1.50% | 103.89 | 0.78 |
5 | February 12th, 2024 | 1.50% | 103.89 | 0.81 |
6 | August 12th, 2024 | 1.50% | 103.89 | 0.79 |
7 | February 10th, 2025 | 1.50% | 103.89 | 0.79 |
8 | February 10th, 2025 | nan% | nan | 103.89 |
When par_asset_swap = True
, the floating-rate coupons are paid on a notional equal to 100 and the swap includes an upfront payment:
= True
par_asset_swap
= ql.AssetSwap(
swap
pay_fixed,
bond,
bond_price,
index,
spread,
ql.Schedule(),
index.dayCounter(),
par_asset_swap, )
0)) print_coupon_info(swap.leg(
date | rate | notional | amount | |
---|---|---|---|---|
0 | August 9th, 2021 | 3.00% | 100.00 | 1.51 |
1 | February 8th, 2022 | 3.00% | 100.00 | 1.49 |
2 | August 8th, 2022 | 3.00% | 100.00 | 1.50 |
3 | February 8th, 2023 | 3.00% | 100.00 | 1.50 |
4 | August 8th, 2023 | 3.00% | 100.00 | 1.50 |
5 | February 8th, 2024 | 3.00% | 100.00 | 1.50 |
6 | August 8th, 2024 | 3.00% | 100.00 | 1.50 |
7 | February 10th, 2025 | 3.00% | 100.00 | 1.52 |
8 | February 10th, 2025 | nan% | nan | 100.00 |
1)) print_coupon_info(swap.leg(
date | rate | notional | amount | |
---|---|---|---|---|
0 | May 25th, 2021 | nan% | nan | 3.89 |
1 | August 10th, 2021 | 1.50% | 100.00 | 0.32 |
2 | February 10th, 2022 | 1.50% | 100.00 | 0.77 |
3 | August 10th, 2022 | 1.50% | 100.00 | 0.76 |
4 | February 10th, 2023 | 1.50% | 100.00 | 0.77 |
5 | August 10th, 2023 | 1.50% | 100.00 | 0.76 |
6 | February 12th, 2024 | 1.50% | 100.00 | 0.78 |
7 | August 12th, 2024 | 1.50% | 100.00 | 0.76 |
8 | February 10th, 2025 | 1.50% | 100.00 | 0.76 |
9 | February 10th, 2025 | nan% | nan | 100.00 |
In both cases, once we give it an discounting engine, the swap can return more information.
= ql.YieldTermStructureHandle(
discount_curve 0.02, ql.Actual360())
ql.FlatForward(today,
) swap.setPricingEngine(ql.DiscountingSwapEngine(discount_curve))
The NPV
and legNPV
methods return the value of the swap or of either leg. In this case we’re paying the bond coupons, therefore the corresponding leg has a negative value.
print(swap.NPV())
print(swap.legNPV(0))
print(swap.legNPV(1))
-2.230481904331157
-104.26057030905751
102.03008840472636
It’s also possible to retrieve the spread over the floating index that would make the swap fair:
= swap.fairSpread()
fair_spread print(fair_spread)
0.01117507740466585
We can test it by re-building the swap with this spread and asking for the NPV again:
= ql.AssetSwap(
swap
pay_fixed,
bond,
bond_price,
index,
fair_spread,
ql.Schedule(),
index.dayCounter(),
par_asset_swap,
) swap.setPricingEngine(ql.DiscountingSwapEngine(discount_curve))
print(swap.NPV())
print(swap.legNPV(0))
print(swap.legNPV(1))
0.0
-104.26057030905751
104.26057030905751
0)) print_coupon_info(swap.leg(
date | rate | notional | amount | |
---|---|---|---|---|
0 | August 9th, 2021 | 3.00% | 100.00 | 1.51 |
1 | February 8th, 2022 | 3.00% | 100.00 | 1.49 |
2 | August 8th, 2022 | 3.00% | 100.00 | 1.50 |
3 | February 8th, 2023 | 3.00% | 100.00 | 1.50 |
4 | August 8th, 2023 | 3.00% | 100.00 | 1.50 |
5 | February 8th, 2024 | 3.00% | 100.00 | 1.50 |
6 | August 8th, 2024 | 3.00% | 100.00 | 1.50 |
7 | February 10th, 2025 | 3.00% | 100.00 | 1.52 |
8 | February 10th, 2025 | nan% | nan | 100.00 |
1)) print_coupon_info(swap.leg(
date | rate | notional | amount | |
---|---|---|---|---|
0 | May 25th, 2021 | nan% | nan | 3.89 |
1 | August 10th, 2021 | 2.12% | 100.00 | 0.45 |
2 | February 10th, 2022 | 2.12% | 100.00 | 1.08 |
3 | August 10th, 2022 | 2.12% | 100.00 | 1.07 |
4 | February 10th, 2023 | 2.12% | 100.00 | 1.08 |
5 | August 10th, 2023 | 2.12% | 100.00 | 1.07 |
6 | February 12th, 2024 | 2.12% | 100.00 | 1.10 |
7 | August 12th, 2024 | 2.12% | 100.00 | 1.07 |
8 | February 10th, 2025 | 2.12% | 100.00 | 1.07 |
9 | February 10th, 2025 | nan% | nan | 100.00 |
Asset swaps can be built based on other kinds of bonds besides fixed-rate ones; the resulting instances work the same way.