Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions OREAnalytics/orea/app/marketcalibrationreport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,9 @@ void MarketCalibrationReport::addInflationCurveImpl(
std::string key1 = ore::data::to_string(z->pillarDates[i]);
addRowReport(type, id, "time", key1, "", "", z->times.at(i));
addRowReport(type, id, "zeroRate", key1, "", "", z->zeroRates.at(i));
addRowReport(type, id, "unSeasonalizedZeroRate", key1, "", "", z->unSeasonalizedZeroRates.at(i));
addRowReport(type, id, "cpi", key1, "", "", z->forwardCpis.at(i));
addRowReport(type, id, "unSeasonalizedCpi", key1, "", "", z->unSeasonalizedForwardCpis.at(i));
}
}

Expand Down
79 changes: 69 additions & 10 deletions OREData/ored/marketdata/inflationcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ InflationCurve::InflationCurve(Date asof, InflationCurveSpec spec, const Loader&
QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");

if ((md->instrumentType() == MarketDatum::InstrumentType::ZC_INFLATIONSWAP ||
(md->instrumentType() == MarketDatum::InstrumentType::YY_INFLATIONSWAP &&
config->type() == InflationCurveConfig::Type::YY))) {
(md->instrumentType() == MarketDatum::InstrumentType::YY_INFLATIONSWAP))) {

QuantLib::ext::shared_ptr<ZcInflationSwapQuote> q = QuantLib::ext::dynamic_pointer_cast<ZcInflationSwapQuote>(md);
if (q) {
Expand All @@ -117,12 +116,15 @@ InflationCurve::InflationCurve(Date asof, InflationCurveSpec spec, const Loader&
}
}

// do we have all quotes and do we derive yoy quotes from zc ?
// do we have all quotes and do we derive yoy quotes from zc or zc from yoy?
for (Size i = 0; i < strQuotes.size(); ++i) {
QL_REQUIRE(!quotes[i].empty(), "quote " << strQuotes[i] << " not found in market data.");
QL_REQUIRE(isZc[i] == isZc[0], "mixed zc and yoy quotes");
}
bool derive_yoy_from_zc = (config->type() == InflationCurveConfig::Type::YY && isZc[0]);
bool derive_zc_from_yoy = (config->type() == InflationCurveConfig::Type::ZC && !isZc[0]);
if (derive_zc_from_yoy) {
WLOG(config->curveID() << ": Curve type is ZC but YY benchmarks has been passed in, will convert YY benchmarks to ZC benchmarks.");
}

// construct seasonality
QuantLib::ext::shared_ptr<Seasonality> seasonality;
Expand Down Expand Up @@ -179,12 +181,66 @@ InflationCurve::InflationCurve(Date asof, InflationCurveSpec spec, const Loader&
interpolatedIndex_ = conv->interpolated();
CPI::InterpolationType observationInterpolation = interpolatedIndex_ ? CPI::Linear : CPI::Flat;
QuantLib::ext::shared_ptr<YoYInflationIndex> zc_to_yoy_conversion_index;
if (config->type() == InflationCurveConfig::Type::ZC || derive_yoy_from_zc) {
if (config->type() == InflationCurveConfig::Type::ZC || derive_yoy_from_zc || derive_zc_from_yoy) {
// ZC Curve
std::vector<QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper>> instruments;
QuantLib::ext::shared_ptr<ZeroInflationIndex> index = conv->index();
QuantLib::Date baseDate = QuantExt::ZeroInflation::curveBaseDate(
config->useLastAvailableFixingAsBaseDate(), asof, curveObsLag, config->frequency(), index);

// Check if YoY quotes need conversion to ZC
if (derive_zc_from_yoy) {
std::vector<QuantLib::ext::shared_ptr<YoYInflationTraits::helper>> yoyInstruments;
std::vector<Date> yoyPillarDates;
for (Size i = 0; i < strQuotes.size(); i++) {
if (isZc[i]) {
continue;
}
Date maturity = swapStart + terms[i];
QuantLib::ext::shared_ptr<YoYInflationIndex> yoyIndex =
QuantLib::ext::make_shared<QuantExt::YoYInflationIndexWrapper>(index);

Date yoyStart = conv->fixCalendar().advance(maturity, -1 * Years, conv->fixConvention());
Date yoyMaturity = conv->fixCalendar().advance(yoyStart, 1 * Years, conv->fixConvention());
QuantLib::ext::shared_ptr<YoYInflationTraits::helper> tmp =
QuantLib::ext::make_shared<YearOnYearInflationSwapHelper>(
quotes[i], conv->observationLag(), yoyMaturity, conv->fixCalendar(), conv->fixConvention(),
conv->dayCounter(), yoyIndex, observationInterpolation, nominalTs, yoyStart);
tmp->unregisterWith(Settings::instance().evaluationDate());
yoyInstruments.push_back(tmp);
yoyPillarDates.push_back(tmp->pillarDate());
DLOG("Building YoY Pillar " << terms[i] << " - Start: " << yoyStart << ", Maturity: " << yoyMaturity
<< ", YearOnYearRate: " << quotes[i]->value());
}
Real baseRate = yoyInstruments.front()->quote()->value();
curve_ = QuantLib::ext::make_shared<PiecewiseYoYInflationCurve<Linear>>(
asof, baseDate, baseRate, config->lag(), config->frequency(), config->dayCounter(), yoyInstruments,
seasonality);
auto yoyCurve = QuantLib::ext::dynamic_pointer_cast<YoYInflationTermStructure>(curve_);
Handle<YoYInflationTermStructure> yoyHandle(yoyCurve);
auto yoyIndex = QuantLib::ext::make_shared<QuantExt::YoYInflationIndexWrapper>(index, yoyHandle, yoyInstruments.front()->earliestDate());
for (Size i = 0; i < yoyInstruments.size(); i++) {
DLOG("YoY Pillar " << terms[i] << " (CPI: " << yoyIndex->forwardCpi(yoyPillarDates[i], true)
<< ", Seasonalized: " << yoyIndex->forwardCpi(yoyPillarDates[i], false)
<< ") --> Converted to ZC benchmark using implied zero rate "
<< yoyIndex->impliedZeroRate(yoyPillarDates[i], conv->dayCounter()));
Date maturity = swapStart + terms[i];
Rate converted_yoy_to_zc = yoyIndex->impliedZeroRate(yoyPillarDates[i], conv->dayCounter());
QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper> instrument =
QuantLib::ext::make_shared<ZeroCouponInflationSwapHelper>(
Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(converted_yoy_to_zc)),
conv->observationLag(), maturity, conv->fixCalendar(), conv->fixConvention(),
conv->dayCounter(), index, observationInterpolation, nominalTs, swapStart);
instrument->unregisterWith(Settings::instance().evaluationDate());
instruments.push_back(instrument);
}
}

for (Size i = 0; i < strQuotes.size(); ++i) {
// QL conventions do not incorporate settlement delay => patch here once QL is patched
if (!isZc[i]) {
continue;
}
Date maturity = swapStart + terms[i];
QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper> instrument =
QuantLib::ext::make_shared<ZeroCouponInflationSwapHelper>(
Expand All @@ -196,9 +252,8 @@ InflationCurve::InflationCurve(Date asof, InflationCurveSpec spec, const Loader&
instrument->unregisterWith(Settings::instance().evaluationDate());
instruments.push_back(instrument);
}
QuantLib::Date baseDate = QuantExt::ZeroInflation::curveBaseDate(
config->useLastAvailableFixingAsBaseDate(), asof, curveObsLag, config->frequency(), index);



if (config->interpolationVariable() == InflationCurveConfig::InterpolationVariable::ZeroRate) {

curve_ = QuantLib::ext::make_shared<PiecewiseZeroInflationCurve<Linear>>(
Expand All @@ -213,7 +268,7 @@ InflationCurve::InflationCurve(Date asof, InflationCurveSpec spec, const Loader&



auto baseFixing = index->fixing(baseDate, true);
auto baseFixing = index->fixing(baseDate);
if (config->interpolationMethod().empty() || config->interpolationMethod() == "Linear"){

curve_ = QuantLib::ext::make_shared<QuantExt::PiecewiseCPIInflationCurve<Linear>>(
Expand Down Expand Up @@ -360,13 +415,17 @@ InflationCurve::InflationCurve(Date asof, InflationCurveSpec spec, const Loader&
for (Size i = 0; i < pillarDates.size(); ++i) {
calInfo->pillarDates.push_back(pillarDates[i]);
calInfo->zeroRates.push_back(zcCurve->zeroRate(pillarDates[i], 0 * Days));
calInfo->times.push_back(zcCurve->timeFromReference(pillarDates[i]));
calInfo->unSeasonalizedZeroRates.push_back(zcCurve->zeroRate(pillarDates[i], 0 * Days, false, false, false));
calInfo->times.push_back(inflationYearFraction(curve_->frequency(), false, curve_->dayCounter(), curve_->baseDate(), pillarDates[i]));
Real cpi = 0.0;
Real unSeasonalizedCpi = 0.0;
try {
cpi = zcIndex->fixing(pillarDates[i]);
unSeasonalizedCpi = zcIndex->fixing(pillarDates[i], false, false);
} catch (...) {
}
calInfo->forwardCpis.push_back(cpi);
calInfo->unSeasonalizedForwardCpis.push_back(unSeasonalizedCpi);
}
calibrationInfo_ = calInfo;
}
Expand Down
2 changes: 2 additions & 0 deletions OREData/ored/marketdata/todaysmarketcalibrationinfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ struct InflationCurveCalibrationInfo {
struct ZeroInflationCurveCalibrationInfo : public InflationCurveCalibrationInfo {
double baseCpi = 0.0;
std::vector<double> zeroRates;
std::vector<double> unSeasonalizedZeroRates;
std::vector<double> forwardCpis;
std::vector<double> unSeasonalizedForwardCpis;
};

struct YoYInflationCurveCalibrationInfo : public InflationCurveCalibrationInfo {
Expand Down
47 changes: 44 additions & 3 deletions QuantExt/qle/indexes/inflationindexwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ Rate ZeroInflationIndexWrapper::fixing(const Date& fixingDate, bool /*forecastTo
}

YoYInflationIndexWrapper::YoYInflationIndexWrapper(const QuantLib::ext::shared_ptr<ZeroInflationIndex> zeroIndex,
const Handle<YoYInflationTermStructure>& ts)
: YoYInflationIndex(zeroIndex, ts), zeroIndex_(zeroIndex) {
const Handle<YoYInflationTermStructure>& ts, const Date& firstPillarDate)
: YoYInflationIndex(zeroIndex, ts), zeroIndex_(zeroIndex), firstPillarDate_(firstPillarDate) {
registerWith(zeroIndex_);
}

YoYInflationIndexWrapper::YoYInflationIndexWrapper(const QuantLib::ext::shared_ptr<ZeroInflationIndex> zeroIndex,
const bool interpolated, const Handle<YoYInflationTermStructure>& ts)
: YoYInflationIndex(zeroIndex, interpolated, ts),
zeroIndex_(zeroIndex) {
zeroIndex_(zeroIndex), firstPillarDate_(Date()) {
registerWith(zeroIndex_);
}

Expand All @@ -66,4 +66,45 @@ Real YoYInflationIndexWrapper::forecastFixing(const Date& fixingDate) const {
return (f1 - f0) / f0;
}

Real YoYInflationIndexWrapper::forwardCpi(const Date& fixingDate, bool removeSeasonality) const {
Handle<YoYInflationTermStructure> ts = yoyInflationTermStructure();
if (ts->baseDate() < fixingDate && firstPillarDate_ > fixingDate) {
//need to mimic flat extrapolation in zero rate here
Real firstPillarCpi = forwardCpi(firstPillarDate_, true);
Real baseCpi = CPI::laggedFixing(zeroIndex_, ts->baseDate(), 0 * Days, CPI::Flat);
Time timeBaseToFirstPillar = ts->dayCounter().yearFraction(ts->baseDate(), firstPillarDate_);
Rate zr = std::pow(firstPillarCpi / baseCpi, 1.0 / timeBaseToFirstPillar) - 1;
Rate zrDeseasonalized = ts->seasonality()->correctZeroRate(fixingDate, zr, *ts.currentLink());
Time timeBaseToFixingDate = ts->dayCounter().yearFraction(ts->baseDate(), fixingDate);
return baseCpi * std::pow(1 + zrDeseasonalized, timeBaseToFixingDate);
}
Date fixingDateMinusOneYear = fixingDate - 1 * Years;
Real pastFixing;
if (needsForecast(fixingDateMinusOneYear)) {
// recursive call if the fixingDateMinusOneYear is a point on the curve
// do not adjust for seasonality when retrieving fixings from the curve
pastFixing = forwardCpi(fixingDateMinusOneYear, false);
}
else {
pastFixing = CPI::laggedFixing(zeroIndex_, fixingDateMinusOneYear, 0 * Days, CPI::Flat);
}

Rate yoyRate = fixing(fixingDate);
if (!removeSeasonality) {
// check if date is after baseDate but before any helpers
return pastFixing * (1 + yoyRate);
}
Rate yoyRateDeseasonalized = ts->seasonality()->deseasonalisedYoYRate(fixingDate, yoyRate, *ts.currentLink());
return pastFixing * (1 + yoyRateDeseasonalized);
}

Rate YoYInflationIndexWrapper::impliedZeroRate(const Date& to, const DayCounter& dc) const {
Date baseDate = yoyInflationTermStructure()->baseDate();
Real baseFixing = CPI::laggedFixing(zeroIndex_, baseDate, 0 * Days, CPI::Flat);
Real toCpi = forwardCpi(to, false);
Time t = dc.yearFraction(baseDate, to);
t = t == 0 ? 1 : t;
return std::pow(toCpi / baseFixing, 1.0 / t) - 1;
}

} // namespace QuantExt
6 changes: 5 additions & 1 deletion QuantExt/qle/indexes/inflationindexwrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ class ZeroInflationIndexWrapper : public ZeroInflationIndex {
class YoYInflationIndexWrapper : public YoYInflationIndex {
public:
YoYInflationIndexWrapper(const QuantLib::ext::shared_ptr<ZeroInflationIndex> zeroIndex,
const Handle<YoYInflationTermStructure>& ts = Handle<YoYInflationTermStructure>());
const Handle<YoYInflationTermStructure>& ts = Handle<YoYInflationTermStructure>(),
const Date& firstPillarDate = Date());

[[deprecated]]
YoYInflationIndexWrapper(const QuantLib::ext::shared_ptr<ZeroInflationIndex> zeroIndex, const bool interpolated,
Expand All @@ -73,10 +74,13 @@ class YoYInflationIndexWrapper : public YoYInflationIndex {
/*! \warning the forecastTodaysFixing parameter (required by the Index interface) is currently ignored. */
Rate fixing(const Date& fixingDate, bool forecastTodaysFixing = false) const override;
const QuantLib::ext::shared_ptr<ZeroInflationIndex> zeroIndex() const { return zeroIndex_; }
Real forwardCpi(const Date& fixingDate, bool removeSeasonality) const;
Rate impliedZeroRate(const Date& to, const DayCounter& dc) const;

private:
Rate forecastFixing(const Date& fixingDate) const;
const QuantLib::ext::shared_ptr<ZeroInflationIndex> zeroIndex_;
const Date& firstPillarDate_;
};

} // namespace QuantExt
Expand Down