Skip to content

Commit

Permalink
Merge pull request #1301.
Browse files Browse the repository at this point in the history
Add interpolation method to `CPI` struct
  • Loading branch information
lballabio committed Feb 19, 2022
2 parents ca54352 + b690d7b commit 2898ced
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 24 deletions.
7 changes: 6 additions & 1 deletion ql/cashflows/cpicoupon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,13 @@ namespace QuantLib {
Rate baseCPI() const;
//! how do you observe the index? as-is, flat, linear?
CPI::InterpolationType observationInterpolation() const;
//! utility method, calls indexFixing

/*! \deprecated Use CPI::laggedFixing instead.
Deprecated in version 1.26.
*/
QL_DEPRECATED
Rate indexObservation(const Date& onDate) const;

//! index used
ext::shared_ptr<ZeroInflationIndex> cpiIndex() const;
//@}
Expand Down
25 changes: 2 additions & 23 deletions ql/cashflows/zeroinflationcashflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,35 +55,14 @@ namespace QuantLib {

Real ZeroInflationCashFlow::amount() const {

auto baseDatePeriod = inflationPeriod(baseDate(), zeroInflationIndex()->frequency());
auto fixingDatePeriod = inflationPeriod(fixingDate(), zeroInflationIndex()->frequency());

Real I0, I1;

if (observationInterpolation_ == CPI::AsIndex) {
I0 = zeroInflationIndex_->fixing(baseDate());
I1 = zeroInflationIndex_->fixing(fixingDate());
} else if (observationInterpolation_ == CPI::Linear) {
auto getInterpolatedFixing = [this](const std::pair<Date, Date>& fixingPeriod,
const Date& date) -> Real {
auto oneDay = Period(1, Days);
auto startIndex = zeroInflationIndex_->fixing(fixingPeriod.first);
auto endIndex = zeroInflationIndex_->fixing(fixingPeriod.second + oneDay);

auto interpolationPeriod = inflationPeriod(date, zeroInflationIndex()->frequency());

return startIndex + (endIndex - startIndex) * (date - interpolationPeriod.first) /
(Real)((interpolationPeriod.second + oneDay) - interpolationPeriod.first);
};

I0 = getInterpolatedFixing(baseDatePeriod, startDate_);
I1 = getInterpolatedFixing(fixingDatePeriod, endDate_);
} else if (observationInterpolation_ == CPI::Flat) {
I0 = zeroInflationIndex_->fixing(baseDatePeriod.first);
I1 = zeroInflationIndex_->fixing(fixingDatePeriod.first);
} else {
// We should not end up here...
QL_FAIL("Unknown ZeroInflationInterpolationType.");
I0 = CPI::laggedFixing(zeroInflationIndex_, startDate_, observationLag_, observationInterpolation_);
I1 = CPI::laggedFixing(zeroInflationIndex_, endDate_, observationLag_, observationInterpolation_);
}

if (growthOnly())
Expand Down
38 changes: 38 additions & 0 deletions ql/indexes/inflationindex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,44 @@

namespace QuantLib {

Real CPI::laggedFixing(const ext::shared_ptr<ZeroInflationIndex>& index,
const Date& date,
const Period& observationLag,
CPI::InterpolationType interpolationType) {

switch (interpolationType) {
case AsIndex: {
return index->fixing(date - observationLag);
}
case Flat: {
auto fixingPeriod = inflationPeriod(date - observationLag, index->frequency());
return index->fixing(fixingPeriod.first);
}
case Linear: {
auto fixingPeriod = inflationPeriod(date - observationLag, index->frequency());
auto interpolationPeriod = inflationPeriod(date, index->frequency());

if (date == interpolationPeriod.first) {
// special case; no interpolation. This avoids asking for
// the fixing at the end of the period, which might need a
// forecast curve to be set.
return index->fixing(fixingPeriod.first);
}

static const auto oneDay = Period(1, Days);

auto I0 = index->fixing(fixingPeriod.first);
auto I1 = index->fixing(fixingPeriod.second + oneDay);

return I0 + (I1 - I0) * (date - interpolationPeriod.first) /
(Real)((interpolationPeriod.second + oneDay) - interpolationPeriod.first);
}
default:
QL_FAIL("unknown CPI interpolation type: " << int(interpolationType));
}
}


InflationIndex::InflationIndex(std::string familyName,
Region region,
bool revised,
Expand Down
18 changes: 18 additions & 0 deletions ql/indexes/inflationindex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,31 @@ namespace QuantLib {
class ZeroInflationTermStructure;
class YoYInflationTermStructure;

class ZeroInflationIndex;

struct CPI {
//! when you observe an index, how do you interpolate between fixings?
enum InterpolationType {
AsIndex, //!< same interpolation as index
Flat, //!< flat from previous fixing
Linear //!< linearly between bracketing fixings
};

//! interpolated inflation fixing
/*! \param index The index whose fixing should be retrieved
\param date The date without lag; usually, the payment
date for some inflation-based coupon.
\param observationLag The observation lag to be subtracted from the
passed date; for instance, if the passed date is
in May and the lag is three months, the inflation
fixing from February (and March, in case of
interpolation) will be observed.
\param interpolationType The interpolation type (flat or linear)
*/
static Real laggedFixing(const ext::shared_ptr<ZeroInflationIndex>& index,
const Date& date,
const Period& observationLag,
InterpolationType interpolationType);
};


Expand Down
90 changes: 90 additions & 0 deletions test-suite/inflation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,93 @@ void InflationTest::testPeriod() {
}
}

void InflationTest::testCpiFlatInterpolation() {
BOOST_TEST_MESSAGE("Testing CPI flat interpolation...");

SavedSettings backup;
IndexHistoryCleaner cleaner;

Settings::instance().evaluationDate() = Date(10, February, 2022);

auto testIndex = ext::make_shared<UKRPI>(false);
testIndex->addFixing(Date(1, November, 2020), 293.5);
testIndex->addFixing(Date(1, December, 2020), 295.4);
testIndex->addFixing(Date(1, January, 2021), 294.6);
testIndex->addFixing(Date(1, February, 2021), 296.0);
testIndex->addFixing(Date(1, March, 2021), 296.9);

Real calculated = CPI::laggedFixing(testIndex, Date(10, February, 2021), 3 * Months, CPI::Flat);
Real expected = 293.5;

BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
"failed to retrieve inflation fixing" <<
"\n expected: " << expected <<
"\n calculated: " << calculated);

calculated = CPI::laggedFixing(testIndex, Date(12, May, 2021), 3 * Months, CPI::Flat);
expected = 296.0;

BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
"failed to retrieve inflation fixing" <<
"\n expected: " << expected <<
"\n calculated: " << calculated);

calculated = CPI::laggedFixing(testIndex, Date(25, June, 2021), 3 * Months, CPI::Flat);
expected = 296.9;

BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
"failed to retrieve inflation fixing" <<
"\n expected: " << expected <<
"\n calculated: " << calculated);
}

void InflationTest::testCpiInterpolation() {
BOOST_TEST_MESSAGE("Testing CPI linear interpolation...");

SavedSettings backup;
IndexHistoryCleaner cleaner;

Settings::instance().evaluationDate() = Date(10, February, 2022);

auto testIndex = ext::make_shared<UKRPI>(false);
testIndex->addFixing(Date(1, November, 2020), 293.5);
testIndex->addFixing(Date(1, December, 2020), 295.4);
testIndex->addFixing(Date(1, January, 2021), 294.6);
testIndex->addFixing(Date(1, February, 2021), 296.0);
testIndex->addFixing(Date(1, March, 2021), 296.9);

Real calculated = CPI::laggedFixing(testIndex, Date(10, February, 2021), 3 * Months, CPI::Linear);
Real expected = 293.5 * (19/28.0) + 295.4 * (9/28.0);

BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
"failed to retrieve inflation fixing" <<
"\n expected: " << expected <<
"\n calculated: " << calculated);

calculated = CPI::laggedFixing(testIndex, Date(12, May, 2021), 3 * Months, CPI::Linear);
expected = 296.0 * (20/31.0) + 296.9 * (11/31.0);

BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
"failed to retrieve inflation fixing" <<
"\n expected: " << expected <<
"\n calculated: " << calculated);

// this would require April's fixing
BOOST_CHECK_THROW(
calculated = CPI::laggedFixing(testIndex, Date(25, June, 2021), 3 * Months, CPI::Linear),
Error);

// however, this is a special case
calculated = CPI::laggedFixing(testIndex, Date(1, June, 2021), 3 * Months, CPI::Linear);
expected = 296.9;

BOOST_CHECK_MESSAGE(std::fabs(calculated-expected) < 1e-8,
"failed to retrieve inflation fixing" <<
"\n expected: " << expected <<
"\n calculated: " << calculated);

}

test_suite* InflationTest::suite() {

auto* suite = BOOST_TEST_SUITE("Inflation tests");
Expand All @@ -1064,5 +1151,8 @@ test_suite* InflationTest::suite() {
suite->add(QUANTLIB_TEST_CASE(&InflationTest::testYYIndex));
suite->add(QUANTLIB_TEST_CASE(&InflationTest::testYYTermStructure));

suite->add(QUANTLIB_TEST_CASE(&InflationTest::testCpiFlatInterpolation));
suite->add(QUANTLIB_TEST_CASE(&InflationTest::testCpiInterpolation));

return suite;
}
2 changes: 2 additions & 0 deletions test-suite/inflation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class InflationTest {
static void testInterpolatedZeroTermStructure();
static void testYYIndex();
static void testYYTermStructure();
static void testCpiFlatInterpolation();
static void testCpiInterpolation();
static boost::unit_test_framework::test_suite* suite();
};

Expand Down

0 comments on commit 2898ced

Please sign in to comment.