2
$\begingroup$

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 :

enter image description here

Swap description :

enter image description here

enter image description here

$\endgroup$
0

1 Answer 1

2
$\begingroup$

By using ql.MakeVanillaSwap, you're creating a swap that pays LIBOR vs fixed, not an OIS like the ones you used to bootstrap the curve. If you actually want to use vanilla swaps, you need to use SwapRateHelper, not OISRateHelper. If you do want to use OIS instead, you'll have to use OvernightIndexedSwap to build the swap and retrieve the fair rate.

$\endgroup$
5
  • $\begingroup$ Thank you very much. I encountered a RuntimeError: 2nd leg: Missing USD Overnight IndexSN Actual/360 fixing for May 23rd, 2023. Does this error indicate that I need to use index.addFixing(ql.Date(23, 5, 2023), fixing_rate)? Alternatively, does QuantLib have access to OIS history? $\endgroup$
    – TourEiffel
    May 25, 2023 at 9:12
  • $\begingroup$ You do need to add the fixings. QuantLib doesn't have those data. $\endgroup$ May 25, 2023 at 9:13
  • $\begingroup$ I would also check that you're setting the global evaluation date to the one you want for your calculation—if you're trying to reprice the OIS that you're using for bootstrapping, they shouldn't need past fixings. Their start date should be at or after the evaluation date. $\endgroup$ May 25, 2023 at 9:16
  • $\begingroup$ I'm having trouble understanding this concept. Based on your explanation, it seems logical to me that the cash flow should start at the evaluation date or after. However, in my code, I have set the evaluation date as today, so it's impossible for the cash flow to start two days before. I suspect there might be an issue with my bootstrapping method. I would greatly appreciate your assistance in resolving this matter. $\endgroup$
    – TourEiffel
    May 25, 2023 at 9:26
  • $\begingroup$ For reference Yield curve bootstrapping not producing expected cash flow start date. Hope you can help on this weird issue. $\endgroup$
    – TourEiffel
    May 25, 2023 at 9:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.