-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
base: master
Are you sure you want to change the base?
Changes from all commits
5f7acde
e25c32c
076f8d4
1ba2f50
b20080a
aff982f
70cacfe
ffad4a7
0ca6ff0
0a4e976
6c150d0
02d7fde
ede4091
4b45d72
2b52546
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ | |
|
||
#include <ql/indexes/iborindex.hpp> | ||
#include <ql/termstructures/yieldtermstructure.hpp> | ||
#include <ql/time/schedule.hpp> | ||
#include <utility> | ||
|
||
namespace QuantLib { | ||
|
@@ -60,16 +61,15 @@ | |
|
||
ext::shared_ptr<IborIndex> IborIndex::clone( | ||
const Handle<YieldTermStructure>& h) const { | ||
return ext::make_shared<IborIndex>( | ||
familyName(), | ||
tenor(), | ||
fixingDays(), | ||
currency(), | ||
fixingCalendar(), | ||
businessDayConvention(), | ||
endOfMonth(), | ||
dayCounter(), | ||
h); | ||
return ext::make_shared<IborIndex>(familyName(), | ||
tenor(), | ||
fixingDays(), | ||
currency(), | ||
fixingCalendar(), | ||
businessDayConvention(), | ||
endOfMonth(), | ||
dayCounter(), | ||
h); | ||
} | ||
|
||
|
||
|
@@ -82,6 +82,84 @@ | |
: IborIndex(familyName, 1*Days, settlementDays, curr, | ||
fixCal, Following, false, dc, h) {} | ||
|
||
void OvernightIndex::addFixing(const Date& fixingDate, Real fixing, bool forceOverwrite) { | ||
calculated_ = false; | ||
Index::addFixing(fixingDate, fixing, forceOverwrite); | ||
} | ||
|
||
void OvernightIndex::addFixings(const TimeSeries<Real>& t, bool forceOverwrite) { | ||
calculated_ = false; | ||
Index::addFixings(t, forceOverwrite); | ||
} | ||
|
||
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; | ||
} | ||
|
||
Real OvernightIndex::compoundedFactor(const Date& fromFixingDate, const Date& toFixingDate) { | ||
calculate(); | ||
auto compIndexEnd = compoundIndex_[toFixingDate]; | ||
auto compIndexStart = compoundIndex_[fromFixingDate]; | ||
|
||
if (compIndexStart == Null<Real>() || compIndexEnd == Null<Real>()) | ||
return Null<Real>(); | ||
|
||
return compIndexEnd / compIndexStart; | ||
} | ||
|
||
void OvernightIndex::performCalculations() const { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see, because you've removed the part in |
||
auto getLastFixingDate = [](Calendar fixingCalendar, std::vector<Date>& fixingDates){ | ||
Date lastFixingDay = fixingDates.front(); | ||
std::for_each(fixingDates.begin() + 1, fixingDates.end(), [&](Date& fixingDate){ | ||
if (fixingCalendar.advance(lastFixingDay, Period(1, Days)) == fixingDate) | ||
lastFixingDay = fixingDate; | ||
}); | ||
return lastFixingDay; | ||
}; | ||
|
||
const auto& ts = Index::timeSeries(); | ||
auto fixingDates = ts.dates(); | ||
if (fixingDates.empty()) { | ||
compoundIndex_ = TimeSeries<Real>(); | ||
return; | ||
} | ||
|
||
auto fixingCalendar = InterestRateIndex::fixingCalendar(); | ||
auto lastFixingDate = getLastFixingDate(fixingCalendar, fixingDates); | ||
Schedule schedule = MakeSchedule() | ||
.from(fixingDates.front()) | ||
.to(lastFixingDate) | ||
Comment on lines
+138
to
+139
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
.withTenor(Period(1, Days)) | ||
.withCalendar(fixingCalendar) | ||
.withConvention(Following) | ||
.withTerminationDateConvention(Following); | ||
std::vector<Real> compIndexValues(schedule.size()); | ||
Real lastCompIndexValue = 1.000; | ||
compIndexValues[0] = lastCompIndexValue; | ||
|
||
std::transform(schedule.begin(), schedule.end() - 1, schedule.begin() + 1, compIndexValues.begin() + 1, | ||
[&](const Date& d1, const Date& d2) { | ||
lastCompIndexValue *= (1 + ts[d1] * dayCounter_.yearFraction(d1, d2)); | ||
return lastCompIndexValue; | ||
}); | ||
|
||
compoundIndex_ = TimeSeries<Real>(schedule.begin(), | ||
schedule.end(), | ||
compIndexValues.begin()); | ||
} | ||
|
||
void OvernightIndex::update() { | ||
notifyObservers(); | ||
} | ||
|
||
ext::shared_ptr<IborIndex> OvernightIndex::clone( | ||
const Handle<YieldTermStructure>& h) const { | ||
return ext::shared_ptr<IborIndex>( | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 theCompoundedOvernightIndexPricer
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.There was a problem hiding this comment.
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:
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.
There was a problem hiding this comment.
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"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are fixings for a given week, we should also have the compounded fixings, shouldn't we? In what case do you think we might end up having holes in the compounded series?
I'm not sure if IndexManager is the correct place, because there's not just one compounded series for a given index. The compounded series for 2 lookback days and observation shift is different from the compounded series for 2 lookback days and no observation shift, which is also different from the compounded series for 3 lookback days and no observation shift. You could try to put all of that into a tag, but you might also think about having a dedicated class to store them—I'm not sure.