Why Swap Curve Nodes Can Fall After Their Tenor Dates
Summary
The document shows a QuantLib workflow for bootstrapping a yield curve from deposit rates, futures, and swap rates, then asks why some curve node dates do not match the apparent swap tenor dates. The example uses a U.S. calendar, modified-following business-day conventions, and semiannual fixed swap legs. The resulting nodes include dates several days later than the dates the author expected for longer swaps.
The answer attributes the difference to calendar adjustment: a tenor date that falls on a weekend or holiday is moved forward to a valid business day. In the cited example, the 2023 date adjustment is associated with the U.S. calendar and the Good Friday and Easter Monday holiday period. This illustrates why curve-helper dates depend on calendars and business-day conventions, not just tenor arithmetic. The short explanation does not examine every helper setting or establish that the supplied curve construction is otherwise correct; users should verify the calendar and date-generation rules relevant to their instruments.
Key ideas
- Bootstrapped curve nodes reflect instrument helper dates and business-day conventions.
- A swap tenor date can be adjusted when it falls on a weekend or holiday.
- The example uses a U.S. calendar and modified-following adjustment for swap dates.
- Unexpected node dates should be checked against the calendar and each helper's date rules.
Tags
Full text
# Swap date doesn't match
# Swap date doesn't match
I use quantlib to bootstrap yield curve from deposit rate, future price and swap rate. But after I get curve, the date in curve.nodes() doesn't match the date of swap.
Here is the code:
```
ql.Settings.instance().evaluationDate = calc_date
calendar = ql.UnitedStates()
settlementDays = 2
buss_convention = ql.ModifiedFollowing
mkt_data = pd.read_csv('mkt_data.csv')
depo_rate = mkt_data.iloc[:9,:]
future_rate = mkt_data.iloc[9:17,:]
swap_rate = mkt_data.iloc[17:,:]
depo_rate.iloc[0,0] = '1D'
depo_rate.iloc[1,0] = '1D'
depo_rate.iloc[2,0] = '1D'
depo_rate['Rate'] = depo_rate['Rate']*0.01
swap_rate['Rate'] = swap_rate['Rate']*0.01
settle_time = [ql.Date(17,6,2020), ql.Date(16,9,2020),ql.Date(16,12,2020),ql.Date(17,3,2021),ql.Date(16,6,2021),\
ql.Date(15,9,2021),ql.Date(15,12,2021),ql.Date(16,3,2022)]
future_rate.loc[:,'Tenor'] = settle_time
for i in [depo_rate,future_rate,swap_rate]:
i['Rate'] = i['Rate'].apply(ql.SimpleQuote)
depo_dayCounter = ql.Actual360()
depositHelpers = []
depositHelpers.append(ql.DepositRateHelper(ql.QuoteHandle(depo_rate.loc[0,'Rate']),ql.Period(depo_rate.loc[0,'Tenor']), \
0,calendar, buss_convention,False, depo_dayCounter))
depositHelpers.append(ql.DepositRateHelper(ql.QuoteHandle(depo_rate.loc[1,'Rate']),ql.Period(depo_rate.loc[1,'Tenor']), \
1,calendar, buss_convention,False, depo_dayCounter))
depositHelpers.append(ql.DepositRateHelper(ql.QuoteHandle(depo_rate.loc[2,'Rate']),ql.Period(depo_rate.loc[2,'Tenor']), \
2,calendar, buss_convention,False, depo_dayCounter))
depositHelpers = depositHelpers + [ql.DepositRateHelper(ql.QuoteHandle(depo_rate.loc[i,'Rate']),ql.Period(depo_rate.loc[i,'Tenor']), \
settlementDays,calendar, buss_convention,False, depo_dayCounter) for i in range(3,len(depo_rate))]
future_dayCounter = ql.Actual360()
months = 3
futuresHelpers = [ ql.FuturesRateHelper(ql.QuoteHandle(row['Rate']),
row['Tenor'], months,
calendar, buss_convention,
True, future_dayCounter,
ql.QuoteHandle(ql.SimpleQuote(0.0)))
for _,row in future_rate.iterrows() ]
fixedLegFrequency = ql.Semiannual
fixedLegDayCounter = ql.Thirty360()
fixedLegAdjustment = ql.ModifiedFollowing
swapHelpers = [ ql.SwapRateHelper(ql.QuoteHandle(row['Rate']),
ql.Period(row['Tenor']), calendar,
fixedLegFrequency, fixedLegAdjustment,
fixedLegDayCounter, ql.USDLibor(ql.Period('3M')))
for _,row in swap_rate.iterrows() ]
helpers = [depositHelpers[6],depositHelpers[8]] + futuresHelpers + swapHelpers
depoFuturesSwapCurve = ql.PiecewiseLogLinearDiscount(calc_date, helpers,depo_dayCounter)
```
Here is the node in depoFuturesSwapCurve
```
((Date(3,4,2020), 1.0),
(Date(7,5,2020), 0.9994808093846632),
(Date(7,7,2020), 0.996613537585222),
(Date(17,9,2020), 0.9939711099817702),
(Date(16,12,2020), 0.9903528102781002),
(Date(16,3,2021), 0.9863826202316677),
(Date(17,6,2021), 0.9821580155124806),
(Date(16,9,2021), 0.9776810270922704),
(Date(15,12,2021), 0.9728854145806802),
(Date(15,3,2022), 0.9676119295645534),
(Date(16,6,2022), 0.9617853248262576),
(Date(11,4,2023), 0.9412966524570305),
(Date(8,4,2024), 0.9120125569930791),
(Date(7,4,2025), 0.8809279129552777),
(Date(7,4,2026), 0.8499139922097416),
(Date(7,4,2027), 0.8191409122567342),
(Date(7,4,2028), 0.7894300580352771),
(Date(9,4,2029), 0.76058975808144),
(Date(8,4,2030), 0.7330282687994317),
(Date(7,4,2032), 0.678916115276019),
(Date(9,4,2035), 0.6059459638985687),
(Date(9,4,2040), 0.5158558171872417),
(Date(11,4,2045), 0.442323362422574),
(Date(7,4,2050), 0.3764502885959702))
```
Here is the data:
I think for 3Y and 25Y swap, the date should be around April 7th. But in my result, the date is April 11th. Why is this happen?
## Answer by David Duarte (score 1)
https://quant.stackexchange.com/a/54908
You have weekend and holidays so the end date adjusts forward... Check 2023 calendar for United States. You have good Friday and Easter MondayShown 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.