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

One-month SofrFutureRateHelper start and end dates #1574

Open
bensonluk opened this issue Jan 28, 2023 · 5 comments
Open

One-month SofrFutureRateHelper start and end dates #1574

bensonluk opened this issue Jan 28, 2023 · 5 comments

Comments

@bensonluk
Copy link
Contributor

This concerns the fixing period of SofrFutureRateHelper with monthly frequency.

Take Jan 2023, we currently have getValidSofrStart returns 3 Jan (first business day of the month), while getValidSofrEnd gives 1 Feb. The helper would create a future referencing this period.

That appears inconsistent with the contract specification, which states we should take average over 1 Jan - 31 Jan fixing. The helper should instead start by looking for 30 Dec 2022 fixing, which should weigh for two days (1 and 2 Jan 2023). On the last business day of the month, similar logic applies. (assume 31 Jan and 1 Feb were holidays, it should look at the forward rate from 30 Jan to 2 Feb, but this rate should weigh only for 2 days - 30 and 31 Jan)

I believe we can NOT adjust for holiday when creating the rate helpers (i.e. we always set valueDate and maturityDate to be the first and last calendar days of the month - even if it is a holiday). Then, in OvernightIndexFuture::averagedRate(), we add the implementation of the logic above regarding holidays around the first and last fixings.

@jakeheke75
Copy link
Contributor

Hi @bensonluk could you please post a quick code that reproduces the user case?

@tomwhoiscontrary
Copy link
Contributor

In case anyone else is interested, the chapter and verse from the CME rulebook is:

46103.A. Final Settlement Price

The final settlement price for an expiring contract shall be calculated by the Exchange on the day on which the FRBNY publishes the SOFR value for the last day of such contract’s delivery month.

The SOFR value for the last day of such delivery month shall be as first published by the FRBNY.

The final settlement price shall be 100 minus the arithmetic average of daily SOFR values during the contract delivery month. For any day during such delivery month for which the FRBNY does not publish a SOFR value (eg, a weekend day, a US government securities market holiday), the SOFR value assigned to such day shall be the SOFR value for the last preceding day for which a SOFR value was published.

@bensonluk
Copy link
Contributor Author

@jakeheke75 Below is an example for illustration: assume all relevant historical fixings are zero, and a Jan 2023 futures price at 99 (implying average rate = 1% over Jan). The stripping result is 14.5%, indicating that the denominator used is 29 days only (because 1st and 2nd Jan are holidays) instead of 31 days.

Changing the 30 Dec fixing does not affect the result, whilst in reality it should (by affecting the 1 and 2 Jan values).

As a sanity check, changing fixings in Jan (e.g. 5 Jan) does affect the stripping result.

#include <iostream>
#include <ql/termstructures/yield/overnightindexfutureratehelper.hpp>
#include <ql/termstructures/yield/piecewiseyieldcurve.hpp>
#include <ql/indexes/ibor/sofr.hpp>


using namespace std;
using namespace QuantLib;


int main()
{
    Date todaysDate(30, Jan, 2023);
    Settings::instance().evaluationDate() = todaysDate;

    ext::shared_ptr<OvernightIndex> index = ext::make_shared<Sofr>();
    index->addFixing(Date(30, Dec, 2022),0.0);  // same result when this fixing is changed
    index->addFixing(Date(3, Jan, 2023), 0.0);
    index->addFixing(Date(4, Jan, 2023), 0.0);
    index->addFixing(Date(5, Jan, 2023), 0.0);  // change this to 1% and the 30th fixing becomes 14%
    index->addFixing(Date(6, Jan, 2023), 0.0);
    index->addFixing(Date(9, Jan, 2023), 0.0);
    index->addFixing(Date(10, Jan, 2023), 0.0);
    index->addFixing(Date(11, Jan, 2023), 0.0);
    index->addFixing(Date(12, Jan, 2023), 0.0);
    index->addFixing(Date(13, Jan, 2023), 0.0);
    index->addFixing(Date(17, Jan, 2023), 0.0);
    index->addFixing(Date(18, Jan, 2023), 0.0);
    index->addFixing(Date(19, Jan, 2023), 0.0);
    index->addFixing(Date(20, Jan, 2023), 0.0);
    index->addFixing(Date(23, Jan, 2023), 0.0);
    index->addFixing(Date(24, Jan, 2023), 0.0);
    index->addFixing(Date(25, Jan, 2023), 0.0);
    index->addFixing(Date(26, Jan, 2023), 0.0);
    index->addFixing(Date(27, Jan, 2023), 0.0);
 
    float convexityAdj = 0.0;

    vector<ext::shared_ptr<RateHelper> > helpers;
    helpers.push_back(ext::make_shared<SofrFutureRateHelper>(
        99.0, Jan, 2023, Monthly, 0));
    helpers.push_back(ext::make_shared<SofrFutureRateHelper>(
        102, Feb, 2023, Monthly, 0));


    ext::shared_ptr<PiecewiseYieldCurve<ForwardRate, BackwardFlat> > curve =
        ext::make_shared<PiecewiseYieldCurve<ForwardRate, BackwardFlat> >(todaysDate, helpers,
            Actual365Fixed());
    
    ext::shared_ptr<OvernightIndex> sofr =
        ext::make_shared<Sofr>(Handle<YieldTermStructure>(curve));


    vector<Date> dates = { Date(27, Jan, 2023), Date(30, Jan, 2023), Date(31, Jan, 2023), 
        Date(1, Feb, 2023) ,Date(2, Feb, 2023) };
    for (auto x : dates) {
        cout << "Fixing on " << x << ": " << sofr->fixing(x)*100 << "(%)" << endl;
    }
    
}

Output:

Fixing on January 27th, 2023: 0(%)
Fixing on January 30th, 2023: 14.5(%)
Fixing on January 31st, 2023: 14.5(%)
Fixing on February 1st, 2023: -2.00006(%)
Fixing on February 2nd, 2023: -2.00006(%)

@bensonluk
Copy link
Contributor Author

On a second thought it may be better to change the OvernightIndexFutureRateHelper by adding two arguments to let the helper know how many days the first and last fixings should weigh.

// QuantLib/ql/termstructures/yield/overnightindexfutureratehelper.hpp
        OvernightIndexFutureRateHelper(const Handle<Quote>& price,
                                       // first day of reference period
                                       const Date& valueDate,           // e.g. 30 Dec 2022
                                       // delivery date
                                       const Date& maturityDate,       // e.g. 1 Feb 2023
                                       const ext::shared_ptr<OvernightIndex>& overnightIndex,
                                       const Handle<Quote>& convexityAdjustment = {},
                                       RateAveraging::Type averagingMethod = RateAveraging::Compound,
                                       const Date& firstSettleDate = Date(),   // e.g. 1 Jan 2023 <- add this argument
                                       const Date& lastSettleDate = Date()     // e.g. 1 Feb 2023 <- add this argument
        );

Similarly, we need to add these two arguments to OvernightIndexFuture constructor. Where unspecified, firstSettleDate and lastSettleDate defaults to valueDate and maturityDate respectively. In this way the behaviour is unchanged when these two arguments are not given.

Then in OvernightIndexFuture::averagedRate(), account for special cases in the first and last fixings.

Finally, SofrFutureRateHelper will refine the way it the dates: valueDate should be the first business day of the month adjusted backward; provide the two new arguments firstSettleDate and lastSettleDate to be the first calendar days of the current and following month respectively.

Any thoughts?

@mdelmedico
Copy link
Contributor

mdelmedico commented Apr 22, 2023

Here's the CME example for settlement calculations on both 3M and 1M SOFR futures (link below). This should help make a set of tests for both types of contracts. Start and end dates for 1M contract are simply first and last day of month, while the 3M contract SR3M23 has start date 21-JUN-2023 (the M23 IMM date) and end date 19-SEP-2023 (the U23 IMM date minus one day). Example Settlement Calcs PDF

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

No branches or pull requests

5 participants