5
$\begingroup$

I generated/calibrated a SOFR Curve using Quantlib Python and would like to know why when repricing the swaps have non-zero NPVs. Appreciate any assistance. Thank you.

Parameters

mktDate = ql.Date(8,3,2021)
ql.Settings.instance().evaluationDate = mktDate
Settlement = 2
Calendar = ql.UnitedStates()
DayCount = ql.Actual360()

OIS Curve Helper

oisHelper = []
for quote in marketQuotes:
    oisHelper.append(ql.OISRateHelper(Settlement, ql.Period(quote[0]), 
ql.QuoteHandle(ql.SimpleQuote(quote[1]/100)), ql.Sofr()))

Curve Specification

sofrCurve = ql.PiecewiseLinearZero(Settlement, Calendar, oisHelper, DayCount)
valCurve = ql.YieldTermStructureHandle(sofrCurve)
sofrIndex = ql.Sofr(valCurve)
swapEngine = ql.DiscountingSwapEngine(valCurve)

Reprice Quotes

for quote in marketQuotes:
    start = Calendar.advance(mktDate, Settlement, ql.Days)
    schedule = ql.MakeSchedule(start, Calendar.advance(start, ql.Period(quote[0])), ql.Period('1Y'), calendar = Calendar)
    fixedRate = quote[1]/100
    oisSwap = ql.OvernightIndexedSwap(
        ql.OvernightIndexedSwap.Receiver, 
        1E6, 
        schedule, 
        fixedRate, 
        DayCount,
        sofrIndex)
    oisSwap.setPricingEngine(swapEngine)
    print(quote, round(oisSwap.NPV(),3)) 

Output

('1W', 0.01982) 0.0
('2W', 0.02394) -0.0
('3W', 0.02503) -0.0
('1M', 0.02897) -0.0
('3M', 0.037) 0.0
('4M', 0.041) -0.0
('5M', 0.043) 0.0
('6M', 0.04597) 0.0
('7M', 0.04797) 0.0
('8M', 0.04997) -0.0
('9M', 0.05197) 0.0
('10M', 0.0535) 0.0
('11M', 0.055) 0.0
('1Y', 0.0565) -0.0
('15M', 0.06) -0.0
('18M', 0.069) 0.003
('21M', 0.083) 0.004
('2Y', 0.10403) 0.0
('3Y', 0.27409) 0.049
('4Y', 0.50109) -0.0
('5Y', 0.718) -0.0
('6Y', 0.90703) 0.0
('7Y', 1.066) -0.0
('8Y', 1.19203) 0.83
('9Y', 1.29306) 0.521
('10Y', 1.37903) -0.0
('12Y', 1.51294) -0.0
('15Y', 1.63591) -0.0
('20Y', 1.72494) 0.766
('25Y', 1.75318) 1.322
('30Y', 1.76979) -0.0
('40Y', 1.71094) 0.0
('50Y', 1.63649) -0.0
$\endgroup$

2 Answers 2

2
$\begingroup$

The difference probably comes from not having exactly the same conventions. When you use ql.MakeOIS all the conventions will come from the ql.Sofr index, but when you build the Instrument manually with ql.OvernightIndexedSwap you are entering all the convention by hand, namely for the schedule.

The ql.MakeSchedule class has many more parameters which mostly default to None:

  • convention=None
  • terminalDateConvention=None,
  • rule=None
  • forwards=False
  • backwards=False,
  • endOfMonth=None
  • firstDate=None
  • nextToLastDate=None
$\endgroup$
2
  • $\begingroup$ Thank you David. Appreciate the feedback. I will test each parameter. $\endgroup$
    – lee lo
    Commented Apr 1, 2021 at 9:12
  • $\begingroup$ Still struggling. I tried various configurations with the parameters with no luck. $\endgroup$
    – lee lo
    Commented Apr 1, 2021 at 12:39
0
$\begingroup$

Appears to work when using ql.MakeOIS. Still need to understand why there is a discrepancy. Any thoughts?

Reprice Quotes

for quote in marketQuotes:
    swapTenor = ql.Period(quote[0])
    fixedRate = quote[1]/100
    oisSwap = ql.MakeOIS(swapTenor, sofrIndex, fixedRate, nominal=1E6)
    print(quote, round(oisSwap.NPV(),3)) 

Output

('1W', 0.01982) 0.0
('2W', 0.02394) 0.0
('3W', 0.02503) 0.0
('1M', 0.02897) 0.0
('3M', 0.037) -0.0
('4M', 0.041) 0.0
('5M', 0.043) 0.0
('6M', 0.04597) 0.0
('7M', 0.04797) 0.0
('8M', 0.04997) 0.0
('9M', 0.05197) -0.0
('10M', 0.0535) -0.0
('11M', 0.055) -0.0
('1Y', 0.0565) 0.0
('15M', 0.06) 0.0
('18M', 0.069) 0.0
('21M', 0.083) -0.0
('2Y', 0.10403) -0.0
('3Y', 0.27409) -0.0
('4Y', 0.50109) 0.0
('5Y', 0.718) 0.0
('6Y', 0.90703) -0.0
('7Y', 1.066) 0.0
('8Y', 1.19203) -0.0
('9Y', 1.29306) -0.0
('10Y', 1.37903) 0.0
('12Y', 1.51294) 0.0
('15Y', 1.63591) 0.0
('20Y', 1.72494) 0.0
('25Y', 1.75318) 0.0
('30Y', 1.76979) 0.0
('40Y', 1.71094) 0.0
('50Y', 1.63649) 0.0
$\endgroup$
3
  • $\begingroup$ I'd check the code of the MakeOIS class to see the defaults it uses: github.com/lballabio/QuantLib/blob/master/ql/instruments/… $\endgroup$ Commented May 14, 2021 at 13:31
  • $\begingroup$ Thank you Luigi, I will have a look. $\endgroup$
    – lee lo
    Commented May 27, 2021 at 13:29
  • $\begingroup$ If some are repricing but not others, it could be the direction of the schedule. For the 18M for example check the stub is at the front or end in both the bootstrap and repricing. $\endgroup$ Commented Sep 1, 2021 at 11:11

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.