Skip to content

Commit

Permalink
Inflation indexes are now better at deciding when to forecast (#2070)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio authored Sep 17, 2024
2 parents 2ccdba7 + ecd3f9a commit 4491778
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 138 deletions.
145 changes: 71 additions & 74 deletions ql/indexes/inflationindex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ namespace QuantLib {
bool /*forecastTodaysFixing*/) const {
if (!needsForecast(fixingDate)) {
const Real I1 = pastFixing(fixingDate);
QL_REQUIRE(I1 != Null<Real>(), "Missing "
<< name() << " fixing for "
<< inflationPeriod(fixingDate, frequency_).first);
QL_REQUIRE(I1 != Null<Real>(),
"Missing " << name() << " fixing for "
<< inflationPeriod(fixingDate, frequency_).first);

return I1;
} else {
Expand All @@ -139,34 +139,25 @@ namespace QuantLib {

bool ZeroInflationIndex::needsForecast(const Date& fixingDate) const {

// Stored fixings are always non-interpolated.
// If an interpolated fixing is required then
// the availability lag + one inflation period
// must have passed to use historical fixings
// (because you need the next one to interpolate).
// The interpolation is calculated (linearly) on demand.

Date today = Settings::instance().evaluationDate();
Date todayMinusLag = today - availabilityLag_;

Date historicalFixingKnown =
inflationPeriod(todayMinusLag, frequency_).first-1;
Date latestNeededDate = fixingDate;
auto latestPossibleHistoricalFixingPeriod =
inflationPeriod(today - availabilityLag_, frequency_);

// Zero-index fixings are always non-interpolated.
auto fixingPeriod = inflationPeriod(fixingDate, frequency_);
Date latestNeededDate = fixingPeriod.first;

if (latestNeededDate <= historicalFixingKnown) {
if (latestNeededDate < latestPossibleHistoricalFixingPeriod.first) {
// the fixing date is well before the availability lag, so
// we know that fixings were provided.
// we know that fixings must be provided.
return false;
} else if (latestNeededDate > today) {
// the fixing can't be available, no matter what's in the
// time series
} else if (latestNeededDate > latestPossibleHistoricalFixingPeriod.second) {
// the fixing can't be available yet
return true;
} else {
// we're not sure, but the fixing might be there so we
// check. Todo: check which fixings are not possible, to
// avoid using fixings in the future
Date first = Date(1, latestNeededDate.month(), latestNeededDate.year());
Real f = timeSeries()[first];
// we're not sure, but the fixing might be there so we check.
Real f = timeSeries()[latestNeededDate];
return (f == Null<Real>());
}
}
Expand All @@ -179,9 +170,9 @@ namespace QuantLib {
name() << " index fixing at base date " << baseDate << " is not available");
Real baseFixing = fixing(baseDate);

std::pair<Date, Date> p = inflationPeriod(fixingDate, frequency_);
std::pair<Date, Date> fixingPeriod = inflationPeriod(fixingDate, frequency_);

Date firstDateInPeriod = p.first;
Date firstDateInPeriod = fixingPeriod.first;
Rate Z1 = zeroInflation_->zeroRate(firstDateInPeriod, Period(0,Days), false);
Time t1 = inflationYearFraction(frequency_, false, zeroInflation_->dayCounter(),
baseDate, firstDateInPeriod);
Expand Down Expand Up @@ -224,23 +215,45 @@ namespace QuantLib {

Rate YoYInflationIndex::fixing(const Date& fixingDate,
bool /*forecastTodaysFixing*/) const {
if (needsForecast(fixingDate)) {
return forecastFixing(fixingDate);
} else {
return pastFixing(fixingDate);
}
}

bool YoYInflationIndex::needsForecast(const Date& fixingDate) const {
Date today = Settings::instance().evaluationDate();
Date todayMinusLag = today - availabilityLag_;
std::pair<Date,Date> lim = inflationPeriod(todayMinusLag, frequency_);
Date lastFix = lim.first-1;

Date flatMustForecastOn = lastFix+1;
Date interpMustForecastOn = lastFix+1 - Period(frequency_);
auto fixingPeriod = inflationPeriod(fixingDate, frequency_);
Date latestNeededDate;
if (!interpolated() || fixingDate == fixingPeriod.first)
latestNeededDate = fixingPeriod.first;
else
latestNeededDate = fixingPeriod.second + 1;

if (interpolated() && fixingDate >= interpMustForecastOn) {
return forecastFixing(fixingDate);
}

if (!interpolated() && fixingDate >= flatMustForecastOn) {
return forecastFixing(fixingDate);
if (ratio()) {
return underlyingIndex_->needsForecast(latestNeededDate);
} else {
auto latestPossibleHistoricalFixingPeriod =
inflationPeriod(today - availabilityLag_, frequency_);

if (latestNeededDate < latestPossibleHistoricalFixingPeriod.first) {
// the fixing date is well before the availability lag, so
// we know that fixings must be provided.
return false;
} else if (latestNeededDate > latestPossibleHistoricalFixingPeriod.second) {
// the fixing can't be available yet
return true;
} else {
// we're not sure, but the fixing might be there so we check.
Real f = timeSeries()[latestNeededDate];
return (f == Null<Real>());
}
}
}

Real YoYInflationIndex::pastFixing(const Date& fixingDate) const {
if (ratio()) {

auto interpolationType = interpolated() ? CPI::Linear : CPI::Flat;
Expand All @@ -252,44 +265,28 @@ namespace QuantLib {

} else { // NOT ratio

if (interpolated()) { // NOT ratio, IS interpolated
const TimeSeries<Real>& ts = timeSeries();

std::pair<Date,Date> lim = inflationPeriod(fixingDate, frequency_);
Real dp = lim.second + 1 - lim.first;
Real dl = fixingDate - lim.first;
Rate limFirstFix = ts[lim.first];
QL_REQUIRE(limFirstFix != Null<Rate>(),
"Missing " << name() << " fixing for "
<< lim.first );
Rate limSecondFix = ts[lim.second+1];
QL_REQUIRE(limSecondFix != Null<Rate>(),
"Missing " << name() << " fixing for "
<< lim.second+1 );
Real linearNow = limFirstFix + (limSecondFix-limFirstFix)*dl/dp;

return linearNow;

} else { // NOT ratio, NOT interpolated
// so just flat

Rate pastFixing = this->pastFixing(fixingDate);
QL_REQUIRE(pastFixing != Null<Rate>(),
"Missing " << name() << " fixing for "
<< inflationPeriod(fixingDate, frequency_).first);
return pastFixing;
const auto& ts = timeSeries();
auto [periodStart, periodEnd] = inflationPeriod(fixingDate, frequency_);

}
}
}
Rate YY0 = ts[periodStart];
QL_REQUIRE(YY0 != Null<Rate>(),
"Missing " << name() << " fixing for " << periodStart);

Real YoYInflationIndex::pastFixing(const Date& fixingDate) const {
if (!ratio() && !interpolated()) {
const auto& ts = timeSeries();
std::pair<Date, Date> lim = inflationPeriod(fixingDate, frequency_);
return ts[lim.first];
if (!interpolated() || /* degenerate case */ fixingDate == periodStart) {

return YY0;

} else {

Real dp = periodEnd + 1 - periodStart;
Real dl = fixingDate - periodStart;
Rate YY1 = ts[periodEnd+1];
QL_REQUIRE(YY1 != Null<Rate>(),
"Missing " << name() << " fixing for " << periodEnd+1);
return YY0 + (YY1 - YY0) * dl / dp;

}
}
QL_FAIL("pastFixing is only supported for non-ratio and non-interpolated YOY indices");
}

Real YoYInflationIndex::forecastFixing(const Date& fixingDate) const {
Expand All @@ -300,8 +297,8 @@ namespace QuantLib {
} else {
// if the value is not interpolated use the starting value
// by internal convention this will be consistent
std::pair<Date,Date> lim = inflationPeriod(fixingDate, frequency_);
d = lim.first;
std::pair<Date,Date> fixingPeriod = inflationPeriod(fixingDate, frequency_);
d = fixingPeriod.first;
}
return yoyInflation_->yoyRate(d,0*Days);
}
Expand Down
61 changes: 20 additions & 41 deletions ql/indexes/inflationindex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,34 +75,27 @@ namespace QuantLib {
//@{
std::string name() const override;

/*! Inflation indices do not have fixing calendars. An
inflation index value is valid for every day (including
weekends) of a calendar period. I.e. it uses the
NullCalendar as its fixing calendar.
/*! Inflation indices are not associated to a particular day,
but to months or quarters. Therefore, they do not have
fixing calendars. Since we're forced by the base `Index`
interface to add one, this method returns a NullCalendar
instance.
*/
Calendar fixingCalendar() const override;
bool isValidFixingDate(const Date&) const override { return true; }

/*! Forecasting index values requires an inflation term
structure. The inflation term structure (ITS) defines the
usual lag (not the index). I.e. an ITS is always relatve
to a base date that is earlier than its asof date. This
must be so because indices are available only with a lag.
However, the index availability lag only sets a minimum
lag for the ITS. An ITS may be relative to an earlier
date, e.g. an index may have a 2-month delay in
publication but the inflation swaps may take as their base
the index 3 months before.
structure, with a base date that is earlier than its asof
date. This must be so because indices are available only
with a lag. Usually, it makes sense for the base date to
be the first day of the month of the last published
fixing.
*/
Real fixing(const Date& fixingDate, bool forecastTodaysFixing = false) const override = 0;

//! returns a past fixing at the given date
Real pastFixing(const Date& fixingDate) const override = 0;

/*! this method creates all the "fixings" for the relevant
period of the index. E.g. for monthly indices it will put
the same value in every calendar day in the month.
*/
void addFixing(const Date& fixingDate, Rate fixing, bool forceOverwrite = false) override;
//@}

Expand All @@ -111,20 +104,13 @@ namespace QuantLib {
std::string familyName() const;
Region region() const;
bool revised() const;
/*! Forecasting index values using an inflation term structure
uses the interpolation of the inflation term structure
unless interpolation is set to false. In this case the
extrapolated values are constant within each period taking
the mid-period extrapolated value.
*/

Frequency frequency() const;
/*! The availability lag describes when the index is
<i>available</i>, not how it is used. Specifically the
fixing for, say, January, may only be available in April
but the index will always return the index value
applicable for January as its January fixing (independent
of the lag in availability).
/*! The availability lag describes when the index might be
available; for instance, the inflation value for January
may only be available in April. This doesn't mean that
that inflation value is considered as the April fixing; it
remains the January fixing, independently of the lag in
availability.
*/
Period availabilityLag() const;
Currency currency() const;
Expand Down Expand Up @@ -162,17 +148,16 @@ namespace QuantLib {
the Index interface) is currently ignored.
*/
Real fixing(const Date& fixingDate, bool forecastTodaysFixing = false) const override;
//! returns a past fixing at the given date
Real pastFixing(const Date& fixingDate) const override;
//@}
//! \name Other methods
//@{
Date lastFixingDate() const;
Handle<ZeroInflationTermStructure> zeroInflationTermStructure() const;
ext::shared_ptr<ZeroInflationIndex> clone(const Handle<ZeroInflationTermStructure>& h) const;
bool needsForecast(const Date& fixingDate) const;
//@}
private:
bool needsForecast(const Date& fixingDate) const;
Real forecastFixing(const Date& fixingDate) const;
Handle<ZeroInflationTermStructure> zeroInflation_;
};
Expand All @@ -187,7 +172,7 @@ namespace QuantLib {
//! \name Constructors
//@{
//! Constructor for year-on-year indices defined as a ratio.
/*! An index build with this constructor doesn't need to store
/*! An index build with this constructor won't store
past fixings of its own; they will be calculated as a
ratio from the past fixings stored in the underlying index.
*/
Expand Down Expand Up @@ -219,27 +204,21 @@ namespace QuantLib {
the Index interface) is currently ignored.
*/
Rate fixing(const Date& fixingDate, bool forecastTodaysFixing = false) const override;

/*! returns a past fixing at the given date
* \warning This is only supported for flat YOY indices providing their own timeseries
* via the `addFixing` or `addFixings` method,
* aka where ratio() == interpolated() == false.
*/
Real pastFixing(const Date& fixingDate) const override;
//@}

//! \name Other methods
//@{
// Override the deprecation above
bool interpolated() const;
bool ratio() const;
ext::shared_ptr<ZeroInflationIndex> underlyingIndex() const;
Handle<YoYInflationTermStructure> yoyInflationTermStructure() const;

ext::shared_ptr<YoYInflationIndex> clone(const Handle<YoYInflationTermStructure>& h) const;
bool needsForecast(const Date& fixingDate) const;
//@}

protected:
// Override the deprecation above
bool interpolated_;

private:
Expand Down
Loading

0 comments on commit 4491778

Please sign in to comment.