I struggle to understand why my market rates does not match my bootstrap model. So I wonder why the spread is that high between market & model.
maturity | market | model
1W | 0.050640 | 0.050626
2W | 0.050670 | 0.050631
3W | 0.050720 | 0.050655
1M | 0.051021 | 0.050916
2M | 0.051391 | 0.051178
3M | 0.051745 | 0.051415
4M | 0.051940 | 0.051493
5M | 0.051980 | 0.051424
6M | 0.051820 | 0.051149
7M | 0.051584 | 0.050821
8M | 0.051310 | 0.050454
9M | 0.050924 | 0.049979
10M | 0.050604 | 0.049582
11M | 0.050121 | 0.049026
12M | 0.049550 | 0.048386
18M | 0.045585 | 0.044806
2Y | 0.042631 | 0.041774
3Y | 0.038952 | 0.038230
4Y | 0.036976 | 0.036321
5Y | 0.035919 | 0.035297
6Y | 0.035350 | 0.034745
7Y | 0.034998 | 0.034403
8Y | 0.034808 | 0.034219
9Y | 0.034738 | 0.034151
10Y | 0.034712 | 0.034125
12Y | 0.034801 | 0.034210
15Y | 0.034923 | 0.034327
20Y | 0.034662 | 0.034075
25Y | 0.033750 | 0.033193
30Y | 0.032826 | 0.032298
40Y | 0.030835 | 0.030369
50Y | 0.028960 | 0.028548
My curve only contain swaps.
Fixed leg :
- Discounting OIS
- Settlement T+2 Days
- Term 2 Week
- Day Count ACT/360
- Pay Freq Annual
- Bus Adj ModifiedFollowing
- Adjust Accrl and Pay Dates
- Roll Conv Backward (EOM)
- Calc Cal FD
- Pay Delay 2 Business Days
Float Leg
- Day Count ACT/360
- Pay Freq Annual
- Index SOFRRATE Index
- Reset Freq Daily
- Bus Adj ModifiedFollowing
self.swaps = {Period("1W"): 0.05064, Period("2W"): 0.05067, Period("3W"): 0.05072, Period("1M"): 0.051021000000000004, Period("2M"): 0.051391, Period("3M"): 0.051745, Period("4M"): 0.05194, Period("5M"): 0.051980000000000005, Period("6M"): 0.051820000000000005, Period("7M"): 0.051584000000000005, Period("8M"): 0.05131, Period("9M"): 0.050924, Period("10M"): 0.050603999999999996, Period("11M"): 0.050121, Period("12M"): 0.049550000000000004, Period("18M"): 0.04558500000000001, Period("2Y"): 0.042630999999999995, Period("3Y"): 0.038952, Period("4Y"): 0.036976, Period("5Y"): 0.035919, Period("6Y"): 0.03535, Period("7Y"): 0.034998, Period("8Y"): 0.034808, Period("9Y"):
0.034738000000000005, Period("10Y"): 0.034712, Period("12Y"): 0.034801, Period("15Y"): 0.034923, Period("20Y"): 0.034662, Period("25Y"): 0.03375, Period("30Y"): 0.032826, Period("40Y"): 0.030834999999999998, Period("50Y"): 0.02896}
Below is how I use OISRateHelper
rate_helpers = []
for tenor, rate in self.swaps.items():
helper = ql.OISRateHelper(2, tenor, ql.QuoteHandle(ql.SimpleQuote(rate)), self.swap_underlying)
rate_helpers.append(helper)
And below is how I compare new rates to given rates :
self.curve = ql.PiecewiseSplineCubicDiscount(calculation_date, rate_helpers, self.swap_day_count_conv)
yts = ql.YieldTermStructureHandle(self.curve)
# Link index to discount curve
index = index.clone(yts)
# Create engine with yield term structure
engine = ql.DiscountingSwapEngine(yts)
# Check the swaps reprice
print("maturity | market | model")
for tenor, rate in self.swaps.items():
swap = ql.MakeVanillaSwap(tenor,
index, 0.01,
ql.Period('0D'),
fixedLegTenor=ql.Period('2D'),
fixedLegDayCount=self.swap_day_count_conv,
fixedLegCalendar=ql.UnitedStates(ql.UnitedStates.GovernmentBond),
floatingLegCalendar=ql.UnitedStates(ql.UnitedStates.GovernmentBond),
pricingEngine=engine)
print(f" {tenor} | {rate:.6f} | {swap.fairRate():.6f}")
Also note that :
self.swap_underlying = ql.OvernightIndex("USD Overnight Index", 2, ql.USDCurrency(), ql.UnitedStates(ql.UnitedStates.Settlement), ql.Actual360())
self.swap_day_count_conv = ql.Actual360()
Did I miss something? Is the implementation I made correct? Are there any discrepancies in the parameters?
Note Curve description :
Swap description :