Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Potential Issue: missing rate on June 19th, 2024 for index SOFRON Actual/360 #1999

Closed
JustCallMeDavid opened this issue Jun 27, 2024 · 20 comments · Fixed by #2013
Closed

Potential Issue: missing rate on June 19th, 2024 for index SOFRON Actual/360 #1999

JustCallMeDavid opened this issue Jun 27, 2024 · 20 comments · Fixed by #2013

Comments

@JustCallMeDavid
Copy link

JustCallMeDavid commented Jun 27, 2024

QuantLib Version: 1.33

I am trying to build a SOFR index in Python but I get the error
RuntimeError: 1st iteration: failed at 3rd alive instrument, pillar September 18th, 2024, maturity September 18th, 2024, reference date June 27th, 2024: missing rate on June 19th, 2024 for index SOFRON Actual/360

Which does not make sense as the date is a holiday for the SOFR calendar (and every other QuantLib calendar except NERC, as verified using holidayList). In fact, if I attempt to add the date I expectedly get
RuntimeError: At least one invalid fixing provided: Wednesday June 19th, 2024, 0.0532

So it seems to raise an error whether the date is provided or not. Could there be an issue with the library that causes this?

(It appears that in ql/instruments/overnightfutures.cpp the line Date d2 = calendar.advance(d1, 1, Days); hits a holiday somehow, but I have not gotten around to seeing the details in a debugger.)

(While I cannot share the direct code, what I do is comparable to the test in sofrfutures.cpp)

Copy link

boring-cyborg bot commented Jun 27, 2024

Thanks for posting! It might take a while before we look at your issue, so don't worry if there seems to be no feedback. We'll get to it.

@lballabio
Copy link
Owner

Hi—that error comes from the bootstrapping of the SOFR curve. Can you post at least the evaluation date and the dates of the helpers you're using to create the curve?

@JustCallMeDavid
Copy link
Author

JustCallMeDavid commented Jun 27, 2024

Hi @lballabio

Thank you for your reply. Sure.

{'closes': [95.89, 95.82, 95.76, 94.65, 95.245, 95.895, 96.08, 96.095, 96.005, 94.65, 94.65, 95.865, 95.81, 95.755, 94.665, 95.45, 95.97, 96.095, 96.085, 95.97, 94.705, 94.76, 95.845, 95.79, 95.77, 94.82, 95.635, 96.02, 96.105, 96.07, 95.94, 94.96, 95.83, 95.77, 95.03, 95.785, 96.055, 96.105, 96.04, 95.91], 'months': [3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 12, 12, 12, 12, 12, 12, 12, 12], 'years': [2030, 2031, 2032, 2024, 2025, 2026, 2027, 2028, 2029, 2024, 2024, 2030, 2031, 2033, 2024, 2025, 2026, 2027, 2028, 2029, 2024, 2024, 2030, 2031, 2033, 2024, 2025, 2026, 2027, 2028, 2029, 2024, 2030, 2031, 2024, 2025, 2026, 2027, 2028, 2029]}

And the evaluation date is 27.6.2024.

I build the helpers like

for close, month, year in zip(
            curve_data["closes"], curve_data["months"], curve_data["years"]
        ):
            close = ql.QuoteHandle(ql.SimpleQuote(close))
            rate = ql.SofrFutureRateHelper(close, month, year, ql.Quarterly)
            helpers.append(rate)

@JustCallMeDavid
Copy link
Author

JustCallMeDavid commented Jun 28, 2024

This code should reproduce the issue in C++. I adapted it from sofrfutures.cpp.

#include <ql/exercise.hpp>
#include <ql/termstructures/yieldtermstructure.hpp>
#include <ql/quote.hpp>
#include <ql/patterns/observable.hpp>
#include <ql/time/daycounters/actual365fixed.hpp>
#include <ql/instruments/overnightindexfuture.hpp>
#include <ql/indexes/ibor/sofr.hpp>
#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
#include <ql/termstructures/yield/overnightindexfutureratehelper.hpp>
#include <ql/settings.hpp>
#include <vector>


using namespace QuantLib;

struct SofrQuotes {
    Frequency freq;
    Month month;
    Year year;
    Real price;
    RateAveraging::Type averagingMethod;
};



int main(){

    Date today = Date(26, June, 2024);
    Settings::instance().evaluationDate() = today;

    const SofrQuotes sofrQuotes[] = {
            {Quarterly, Mar, 2030, 95.89, RateAveraging::Compound},
            {Quarterly, Mar, 2031, 95.82, RateAveraging::Compound},
            {Quarterly, Mar, 2032, 95.76, RateAveraging::Compound},
            {Quarterly, Mar, 2024, 94.65, RateAveraging::Compound},
            {Quarterly, Mar, 2025, 95.245, RateAveraging::Compound},
            {Quarterly, Mar, 2026, 95.895, RateAveraging::Compound},
            {Quarterly, Mar, 2027, 96.08, RateAveraging::Compound},
            {Quarterly, Mar, 2028, 96.095, RateAveraging::Compound},
            {Quarterly, Mar, 2029, 96.005, RateAveraging::Compound},
            {Quarterly, Apr, 2024, 94.65, RateAveraging::Compound},
            {Quarterly, May, 2024, 94.65, RateAveraging::Compound},
            {Quarterly, Jun, 2030, 95.865, RateAveraging::Compound},
            {Quarterly, Jun, 2031, 95.81, RateAveraging::Compound},
            {Quarterly, Jun, 2033, 95.755, RateAveraging::Compound},
            {Quarterly, Jun, 2024, 94.665, RateAveraging::Compound},
            {Quarterly, Jun, 2025, 95.45, RateAveraging::Compound},
            {Quarterly, Jun, 2026, 95.97, RateAveraging::Compound},
            {Quarterly, Jun, 2027, 96.095, RateAveraging::Compound},
            {Quarterly, Jun, 2028, 96.085, RateAveraging::Compound},
            {Quarterly, Jun, 2029, 95.97, RateAveraging::Compound},
            {Quarterly, Jul, 2024, 94.705, RateAveraging::Compound},
            {Quarterly, Aug, 2024, 94.76, RateAveraging::Compound},
            {Quarterly, Sep, 2030, 95.845, RateAveraging::Compound},
            {Quarterly, Sep, 2031, 95.79, RateAveraging::Compound},
            {Quarterly, Sep, 2033, 95.77, RateAveraging::Compound},
            {Quarterly, Sep, 2024, 94.82, RateAveraging::Compound},
            {Quarterly, Sep, 2025, 95.635, RateAveraging::Compound},
            {Quarterly, Sep, 2026, 96.02, RateAveraging::Compound},
            {Quarterly, Sep, 2027, 96.105, RateAveraging::Compound},
            {Quarterly, Sep, 2028, 96.07, RateAveraging::Compound},
            {Quarterly, Sep, 2029, 95.94, RateAveraging::Compound},
            {Quarterly, Nov, 2024, 94.96, RateAveraging::Compound},
            {Quarterly, Dec, 2030, 95.83, RateAveraging::Compound},
            {Quarterly, Dec, 2031, 95.77, RateAveraging::Compound},
            {Quarterly, Dec, 2024, 95.03, RateAveraging::Compound},
            {Quarterly, Dec, 2025, 95.785, RateAveraging::Compound},
            {Quarterly, Dec, 2026, 96.055, RateAveraging::Compound},
            {Quarterly, Dec, 2027, 96.105, RateAveraging::Compound},
            {Quarterly, Dec, 2028, 96.04, RateAveraging::Compound},
            {Quarterly, Dec, 2029, 95.91, RateAveraging::Compound}
    };


    ext::shared_ptr<OvernightIndex> index = ext::make_shared<Sofr>();
    index->addFixing(Date(26, June, 2024), 0.0534);
    index->addFixing(Date(25, June, 2024), 0.0533);
    index->addFixing(Date(24, June, 2024), 0.0531);
    index->addFixing(Date(21, June, 2024), 0.0531);
    index->addFixing(Date(20, June, 2024), 0.0532);
    index->addFixing(Date(18, June, 2024), 0.0533);
    index->addFixing(Date(17, June, 2024), 0.0533);
    index->addFixing(Date(14, June, 2024), 0.0531);
    index->addFixing(Date(13, June, 2024), 0.0531);
    index->addFixing(Date(12, June, 2024), 0.0531);
    index->addFixing(Date(11, June, 2024), 0.0532);
    index->addFixing(Date(10, June, 2024), 0.0532);
    index->addFixing(Date(7, June, 2024), 0.0533);
    index->addFixing(Date(June, June, 2024), 0.0533);
    index->addFixing(Date(5, June, 2024), 0.0533);
    index->addFixing(Date(4, June, 2024), 0.0533);
    index->addFixing(Date(3, June, 2024), 0.0535);
    index->addFixing(Date(31, May, 2024), 0.0534);
    index->addFixing(Date(30, May, 2024), 0.0533);
    index->addFixing(Date(29, May, 2024), 0.0533);
    index->addFixing(Date(28, May, 2024), 0.0532);
    index->addFixing(Date(24, May, 2024), 0.0532);
    index->addFixing(Date(23, May, 2024), 0.0531);
    index->addFixing(Date(22, May, 2024), 0.0531);
    index->addFixing(Date(21, May, 2024), 0.0531);
    index->addFixing(Date(20, May, 2024), 0.0531);
    index->addFixing(Date(17, May, 2024), 0.0531);
    index->addFixing(Date(16, May, 2024), 0.0531);
    index->addFixing(Date(15, May, 2024), 0.0531);
    index->addFixing(Date(14, May, 2024), 0.0531);
    index->addFixing(Date(13, May, 2024), 0.0531);
    index->addFixing(Date(10, May, 2024), 0.0531);
    index->addFixing(Date(9, May, 2024), 0.0531);
    index->addFixing(Date(8, May, 2024), 0.0531);
    index->addFixing(Date(7, May, 2024), 0.0531);
    index->addFixing(Date(6, May, 2024), 0.0531);
    index->addFixing(Date(3, May, 2024), 0.0531);
    index->addFixing(Date(2, May, 2024), 0.0531);
    index->addFixing(Date(1, May, 2024), 0.0532);
    index->addFixing(Date(30, April, 2024), 0.0534);
    index->addFixing(Date(29, April, 2024), 0.0532);
    index->addFixing(Date(26, April, 2024), 0.0532);
    index->addFixing(Date(25, April, 2024), 0.0531);
    index->addFixing(Date(24, Apr, 2024), 0.0531);
    index->addFixing(Date(23, Apr, 2024), 0.0531);
    index->addFixing(Date(22, Apr, 2024), 0.0531);
    index->addFixing(Date(19, Apr, 2024), 0.0531);
    index->addFixing(Date(18, Apr, 2024), 0.053);
    index->addFixing(Date(17, Apr, 2024), 0.0531);
    index->addFixing(Date(16, Apr, 2024), 0.0531);
    index->addFixing(Date(15, Apr, 2024), 0.0532);
    index->addFixing(Date(12, Apr, 2024), 0.0531);
    index->addFixing(Date(11, Apr, 2024), 0.0531);
    index->addFixing(Date(10, Apr, 2024), 0.0531);
    index->addFixing(Date(9, Apr, 2024), 0.0531);
    index->addFixing(Date(8, Apr, 2024), 0.0531);
    index->addFixing(Date(5, Apr, 2024), 0.0532);
    index->addFixing(Date(4, Apr, 2024), 0.0532);
    index->addFixing(Date(3, Apr, 2024), 0.0532);
    index->addFixing(Date(2, Apr, 2024), 0.0534);
    index->addFixing(Date(1, Apr, 2024), 0.0535);
    index->addFixing(Date(28, March, 2024), 0.0534);
    index->addFixing(Date(27, March, 2024), 0.0533);
    index->addFixing(Date(26, March, 2024), 0.0532);
    index->addFixing(Date(25, March, 2024), 0.0531);
    index->addFixing(Date(22, March, 2024), 0.0531);
    index->addFixing(Date(21, March, 2024), 0.0531);
    index->addFixing(Date(20, March, 2024), 0.0531);
    index->addFixing(Date(19, March, 2024), 0.0531);
    index->addFixing(Date(18, March, 2024), 0.0531);
    index->addFixing(Date(15, March, 2024), 0.0531);
    index->addFixing(Date(14, March, 2024), 0.0531);
    index->addFixing(Date(13, March, 2024), 0.0531);
    index->addFixing(Date(12, March, 2024), 0.0531);
    index->addFixing(Date(11, March, 2024), 0.0531);
    index->addFixing(Date(8, March, 2024), 0.0531);
    index->addFixing(Date(7, March, 2024), 0.0531);
    index->addFixing(Date(6, March, 2024), 0.0531);
    index->addFixing(Date(5, March, 2024), 0.0531);
    index->addFixing(Date(4, March, 2024), 0.0531);
    index->addFixing(Date(1, March, 2024), 0.0531);
    index->addFixing(Date(29, February, 2024), 0.0532);
    index->addFixing(Date(28, February, 2024), 0.0531);
    index->addFixing(Date(27, February, 2024), 0.0531);
    index->addFixing(Date(26, February, 2024), 0.0531);
    index->addFixing(Date(23, February, 2024), 0.0531);
    index->addFixing(Date(22, February, 2024), 0.053);
    index->addFixing(Date(21, February, 2024), 0.053);
    index->addFixing(Date(20, February, 2024), 0.053);
    index->addFixing(Date(16, February, 2024), 0.053);
    index->addFixing(Date(15, February, 2024), 0.0531);
    index->addFixing(Date(14, February, 2024), 0.053);
    index->addFixing(Date(13, February, 2024), 0.0531);
    index->addFixing(Date(12, February, 2024), 0.0531);
    index->addFixing(Date(9, February, 2024), 0.0531);
    index->addFixing(Date(8, February, 2024), 0.0531);
    index->addFixing(Date(7, February, 2024), 0.0531);
    index->addFixing(Date(6, February, 2024), 0.0531);
    index->addFixing(Date(5, February, 2024), 0.0531);
    index->addFixing(Date(2, February, 2024), 0.0532);
    index->addFixing(Date(1, February, 2024), 0.0532);
    index->addFixing(Date(31, January, 2024), 0.0532);
    index->addFixing(Date(30, January, 2024), 0.0531);
    index->addFixing(Date(29, January, 2024), 0.0531);
    index->addFixing(Date(26, January, 2024), 0.0532);
    index->addFixing(Date(25, January, 2024), 0.0532);
    index->addFixing(Date(24, January, 2024), 0.0531);
    index->addFixing(Date(23, January, 2024), 0.0531);
    index->addFixing(Date(22, January, 2024), 0.0531);
    index->addFixing(Date(19, January, 2024), 0.0531);
    index->addFixing(Date(18, January, 2024), 0.0531);
    index->addFixing(Date(17, January, 2024), 0.0532);
    index->addFixing(Date(16, January, 2024), 0.0532);
    index->addFixing(Date(12, January, 2024), 0.0531);
    index->addFixing(Date(11, January, 2024), 0.0531);
    index->addFixing(Date(10, January, 2024), 0.0531);
    index->addFixing(Date(9, January, 2024), 0.0531);
    index->addFixing(Date(8, January, 2024), 0.0531);
    index->addFixing(Date(5, January, 2024), 0.0531);
    index->addFixing(Date(4, January, 2024), 0.0532);


    std::vector<ext::shared_ptr<RateHelper> > helpers;
    for (const auto& sofrQuote : sofrQuotes) {
        helpers.push_back(ext::make_shared<SofrFutureRateHelper>(
                sofrQuote.price, sofrQuote.month, sofrQuote.year, sofrQuote.freq));
    }

    ext::shared_ptr<PiecewiseYieldCurve<Discount, Linear> > curve =
            ext::make_shared<PiecewiseYieldCurve<Discount, Linear> >(today, helpers,
                                                                     Actual365Fixed());

    // test curve with one of the futures
    ext::shared_ptr<OvernightIndex> sofr =
            ext::make_shared<Sofr>(Handle<YieldTermStructure>(curve));
    OvernightIndexFuture sf(sofr, Date(25, June, 2024), Date(19, December, 2024));
    Real error = std::fabs(sf.NPV());
}

When I try to run it I get

terminate called after throwing an instance of 'QuantLib::Error'
  what():  missing rate on June 19th, 2024 for index SOFRON Actual/360

I was not able to spot something off with my fixings or sofr quotes, but they are retrieved automatically so it could be I am not seeing something.

@lballabio
Copy link
Owner

Thanks, yes, I can reproduce it. I'll try to have a look next week.

@pcaspers
Copy link
Contributor

pcaspers commented Jul 2, 2024

The startDate of the SOFR Future 2024-09 is determined to be 2024-06-19 (3rd Wed of 3rd month preceding delivery month consistent with the specs here: https://www.cmegroup.com/markets/interest-rates/stirs/three-month-sofr.contractSpecs.html).

But then here https://github.com/lballabio/QuantLib/blob/master/ql/instruments/overnightindexfuture.cpp#L83 (and similar for averaged futures) we read the SOFR fixing on 2024-06-19 which leads to the error.

I can't find the definite convention to apply, but I would assume that the SOFR rate from the preceding business day is used on the holiday.

@pcaspers
Copy link
Contributor

pcaspers commented Jul 2, 2024

For background, we are hit by this issue as well.

@lballabio
Copy link
Owner

I see—so maybe it's already fixed by #1996? (It will be in 1.35)

@lballabio
Copy link
Owner

Unless it's not a real fix because the start date should indeed be June 19th. In that case, yes, we should accrue the previous fixing for that day.

@pcaspers
Copy link
Contributor

pcaspers commented Jul 2, 2024

I was going to say that. :-) So you are saying #1996 is actually not correct and should be rolled back (that's what I am thinking anyway).

@lballabio
Copy link
Owner

I don't know. I guess the question is: does https://www.cmegroup.com/markets/interest-rates/stirs/three-month-sofr.contractSpecs.html not mention holidays because the only possible case is Juneteenth and this was written before it was declared a holiday? (In which case #1996 might be right.). Or is this on purpose, in which case we roll back #1996 and fix https://github.com/lballabio/QuantLib/blob/master/ql/instruments/overnightindexfuture.cpp#L81-L89 so that it accounts for the start date being a holiday? Do you have access to the corresponding market data?

@pcaspers
Copy link
Contributor

pcaspers commented Jul 2, 2024

I see yes. I'll try to find out!

@lballabio
Copy link
Owner

The example in https://www.cmegroup.com/education/files/sofr-futures-settlement-calculation-methodologies.pdf suggests that at least 1M futures can start on a holiday. I'd expect 3M to do the same.

@pcaspers
Copy link
Contributor

pcaspers commented Jul 3, 2024

Probably. Although 1M had holidays at start / end all along. But they would have updated the 3M specs I think. Still trying to confirm, but I'd go for that as well.

@pcaspers
Copy link
Contributor

pcaspers commented Jul 8, 2024

I can not get a definite confirmation. I would suggest to take their spec seriously and assume no adjustment of the reference period.

@pcaspers
Copy link
Contributor

pcaspers commented Jul 8, 2024

If you want I can submit a PR for this, we need to fix this anyhow in the short term.

@lballabio
Copy link
Owner

If you have a PR ready, please go ahead—otherwise let me know and I'll do it.

@pcaspers
Copy link
Contributor

pcaspers commented Jul 8, 2024

I haven't started, so if you can take this that'd be great too!

@lballabio
Copy link
Owner

It should be fixed in #2013 — please have a look.

@JustCallMeDavid
Copy link
Author

@pcaspers @lballabio thank you a lot for your help with the issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants