Skip to content

Feature: Fast Valuation of Seasoned OIS Swaps #2213

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

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

paolodelia99
Copy link
Contributor

Added a compoundIndex attribute in the OvernightIndex to keep track of the past fixings and speed up the calculation of compounded fixings for Seasoned OIS Swap as described in #1653.

Changes in the PR include:

  • Made the OvernightIndex Lazy, so the compounded index is calculated on demand.
    • Added support for computing compounded fixings and factors over a given date range in the OvernightIndex class, with new methods compoundedFixings and compoundedFactor. These methods leverage historical fixings to calculate rates and factors efficiently.
  • Refactoring of the averageRate code in CompoundingOvernightIndexedCouponPricer by extracting logic for handling past, today, and future fixings into separate private methods (handlePastFixings, handleTodayFixing, handleFutureFixings) to improve code readability and maintainability.

@coveralls
Copy link

coveralls commented May 2, 2025

Coverage Status

coverage: 73.331% (+0.02%) from 73.311%
when pulling 2b52546 on paolodelia99:feature/fast-eval-ois-swap
into 29147de on lballabio:master.

@paolodelia99 paolodelia99 marked this pull request as ready for review May 2, 2025 13:24
Comment on lines +95 to +105
Rate OvernightIndex::compoundedFixings(const Date& fromFixingDate, const Date& toFixingDate) {
calculate();
auto yearFraction = dayCounter_.yearFraction(fromFixingDate, toFixingDate);
auto compIndexEnd = compoundIndex_[toFixingDate];
auto compIndexStart = compoundIndex_[fromFixingDate];

if (compIndexStart == Null<Real>() || compIndexEnd == Null<Real>())
return Null<Real>();

return (compoundIndex_[toFixingDate] / compoundIndex_[fromFixingDate] - 1) / yearFraction;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this belongs into the index. It should be in the coupon or the coupon pricer—especially because the way the composition is done doesn't depend only on the start and end date, but also on the lookback days and whether or not one must apply the observation shift, which are parameters of the coupon.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, the compounded index should be linked to the Overnight Index itself, unless we are using the same CompoundedOvernightIndexPricer within the same Leg. I don’t see any performance improvements from having a different instance of the CompoundedOvernightIndexPricer for each coupon, especially if each has an associated compoundedIndex attribute; each pricer is going to recalculate those compounded fixings in the past, not leveraging the compounded index.

Aren't the lookback days and the observation shift logics that concern the Coupon itself? I feel like they shouldn't concern the compounded index. I don't really see why the compounded index should have taken into account that logic, since we could have coupons on the same Overnight index with different lookback days, and so on.

Another practical way to store the compoundedIndex could be by utilizing a sort of IndexManager, similar to what you have implemented with the standard fixings.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lookback days and the observation shift concern the Coupon, and they have an effect on the compounding. As an example, let's consider a coupon starting today, Monday May 12th, with lookback days = 2, so we have for instance these initial compounding dates:

date fixing date
Monday May 12th Thursday May 8th
Tuesday May 13th Friday May 9th
Wednesday May 14th Monday May 12th
Thursday May 15th Tuesday May 13th
Friday May 16th Wednesday May 14th
Monday May 19th Thursday May 15th

If the observation shift is set to false, the coupon dates in the first column determine the compounding days; so for instance, the index fixing for Friday May 9th is compounded for 1 day (May 13th to May 14th) and the fixing for Wednesday May 14th is compounded for 3 days (May 16th to May 19th). If the observation shift is set to true, it's the fixing dates in the second column that determine the compounding days, so the index fixing for Friday May 9th is compounded for 3 days and the fixing for Wednesday May 14th is compounded for 1 day. A different number of lookback days would give you other results.

The index alone can't know this. It can only give you single fixings. It's the coupon that should manage this calculation.

--

About having different instances of the coupon pricer: yes, the cache should be shared between pricers, like the IndexManager.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you're saying, and I agree that it’s ultimately the coupon that should handle the compounding, not the compounded index itself. The compounded index can give us the individual fixings (since that’s what it essentially stores), but the logic for compounding over a period is typically managed within the coupon itself.

How would you suggest to tackle this problem then? I was thinking to resister and additional index in in the IndexManager (with the name "<Overnight_index> Comp"), and store the compounded index there since now if I feel like it's the natural place to store it. But again the problems will come from the fact that we might have non-contiguous fixing (calendar-wise I mean), so we could have the comp index for a period (like from the 1st of Janurary to the 31st of Janurary) but then let's say we are missing some fixings (first week of February) and we have the fixing after those. How should we deal with that situation?
My idea is to not store anything and then fallback on normal fixing (stored in the normal index).

Example of CompIndex in IndexManager (assuming that the available fixings start from the 1st of January 2025)
Name: "Estr Comp"

Date CompIndexValue
02-01-2025 1.0000
03-01-2025 1.000081
06-01-2025 1.000324
07-01-2025 1.000405
08-01-2025 1.000487
09-01-2025 1.000568
10-01-2025 ...

return compIndexEnd / compIndexStart;
}

void OvernightIndex::performCalculations() const {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure LazyObject is the right mechanism for this. This calculation will be executed again and again every time the index (or the pricer, if you move the calculation there) receives a notification; for instance, when the forecast curve changes, even though it has no effect on the stored past prices on which this calculation depends.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the calculation be triggered also when the forecast curve changes as well? Not just when I call the addFixing or addFixings? It's there where I set calculated_ = false.

Copy link
Owner

@lballabio lballabio May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see, because you've removed the part in update where the lazy object sets calculated_ to false. Hmm, yes, it would possibly work, but I'm not sure if I'm comfortable with changing the way the lazy object works. Also, you might add a fixing through a different instance of the index, and this instance would still have calculated_ set to false and ignore the new fixing.

Comment on lines +138 to +139
.from(fixingDates.front())
.to(lastFixingDate)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using performCalculations(), which has no arguments, forced you to calculate compounded values for the whole history, but that might be unnecessary. If possible, I'd calculate the series of compounded values only on the requested range, extending it as needed with each call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's inefficient. Ideally, assuming that one should add fixings from the last fixing day that we have in history in the compounded index, the optimization that can be put in place could be to compute the compounded index from the last past fixing date to the new past date of the next fixing.

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 this pull request may close these issues.

3 participants