Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
riskparticipationagreement.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
20
25
31
32#include <ql/cashflows/fixedratecoupon.hpp>
33#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
34
35namespace ore {
36namespace data {
37
38std::map<std::string, Handle<YieldTermStructure>>
40 std::map<std::string, Handle<YieldTermStructure>> curves;
41 for (auto const& ccy : rpa->legCurrencies()) {
42 curves[ccy] = market_->discountCurve(ccy, configuration(MarketContext::pricing));
43 }
44 return curves;
45}
46
47std::map<std::string, Handle<Quote>>
49 std::map<std::string, Handle<Quote>> fxSpots;
50 for (auto const& ccy : rpa->legCurrencies()) {
51 fxSpots[ccy] = market_->fxRate(ccy + rpa->npvCurrency(), configuration(MarketContext::pricing));
52 }
53 return fxSpots;
54}
55
56QuantLib::ext::shared_ptr<QuantLib::PricingEngine>
58 Size maxDiscretisationPoints = parseInteger(engineParameter("MaxDiscretisationPoints"));
59 if (maxDiscretisationPoints == 0)
60 maxDiscretisationPoints = Null<Size>();
62 // the first ibor / ois index found
63 QuantLib::ext::shared_ptr<IborIndex> index;
64 auto qlInstr = QuantLib::ext::dynamic_pointer_cast<QuantExt::RiskParticipationAgreement>(rpa->instrument()->qlInstrument());
65 QL_REQUIRE(qlInstr != nullptr, "RiskParticipationAgreementBlackEngineBuilder: internal error, could not "
66 "cast to RiskParticipationAgreement");
67 for (auto const& l : qlInstr->underlying()) {
68 for (auto const& c : l) {
69 if (auto floatingCpn = QuantLib::ext::dynamic_pointer_cast<QuantLib::FloatingRateCoupon>(c)) {
70 if (index == nullptr)
71 index = QuantLib::ext::dynamic_pointer_cast<IborIndex>(floatingCpn->index());
72 }
73 }
74 }
75
76 auto key = index == nullptr ? rpa->npvCurrency() : IndexNameTranslator::instance().oreName(index->name());
77 return QuantLib::ext::make_shared<AnalyticBlackRiskParticipationAgreementEngine>(
78 rpa->npvCurrency(), getDiscountCurves(rpa), getFxSpots(rpa),
79 market_->defaultCurve(rpa->creditCurveId(), config)->curve(),
80 market_->recoveryRate(rpa->creditCurveId(), config), market_->swaptionVol(key, config),
81 *market_->swapIndex(market_->swapIndexBase(key, config)),
82 parseBool(modelParameter("MatchUnderlyingTenor", {}, false, "false")),
84 modelParameter("Reversion", {IndexNameTranslator::instance().oreName(index->name()), rpa->npvCurrency()})),
85 parseBool(engineParameter("AlwaysRecomputeOptionRepresentation")), parseInteger(engineParameter("MaxGapDays")),
86 maxDiscretisationPoints);
87}
88
89QuantLib::ext::shared_ptr<QuantLib::PricingEngine>
91 Size maxDiscretisationPoints = parseInteger(engineParameter("MaxDiscretisationPoints"));
92 if (maxDiscretisationPoints == 0)
93 maxDiscretisationPoints = Null<Size>();
95 string ccyPair;
96 for (auto const& c : rpa->legCurrencies()) {
97 // the engine will check that there are exactly two underlying leg currencies, so here we can just look
98 // for the first leg currency != npvCurrency and still be sure that the correct FX Vol will be applied
99 if (c != rpa->npvCurrency()) {
100 ccyPair = c + rpa->npvCurrency();
101 break;
102 }
103 }
104 QL_REQUIRE(!ccyPair.empty(),
105 "RiskParticipationAgreementXCcyBlackEngineBuilder: no foreign currency found, this is unexpected");
106 return QuantLib::ext::make_shared<AnalyticXCcyBlackRiskParticipationAgreementEngine>(
107 rpa->npvCurrency(), getDiscountCurves(rpa), getFxSpots(rpa),
108 market_->defaultCurve(rpa->creditCurveId(), config)->curve(),
109 market_->recoveryRate(rpa->creditCurveId(), config), market_->fxVol(ccyPair, config),
110 parseBool(engineParameter("AlwaysRecomputeOptionRepresentation")), parseInteger(engineParameter("MaxGapDays")),
111 maxDiscretisationPoints);
112}
113
114QuantLib::ext::shared_ptr<QuantExt::LGM>
116 const std::vector<Date>& expiries, const Date& maturity,
117 const std::vector<Real>& strikes) {
118
119 // TODO this is the same as in LGMBermudanSwaptionEngineBuilder::model(), factor the model building out
120
121 DLOG("Get model data");
122 auto calibration = parseCalibrationType(modelParameter("Calibration"));
123 auto calibrationStrategy = parseCalibrationStrategy(modelParameter("CalibrationStrategy"));
124 std::string referenceCalibrationGrid = modelParameter("ReferenceCalibrationGrid", {}, false, "");
125 Real lambda = parseReal(modelParameter("Reversion"));
126 vector<Real> sigma = parseListOfValues<Real>(modelParameter("Volatility"), &parseReal);
127 vector<Real> sigmaTimes = parseListOfValues<Real>(modelParameter("VolatilityTimes", {}, false), &parseReal);
128 QL_REQUIRE(sigma.size() == sigmaTimes.size() + 1, "there must be n+1 volatilities (" << sigma.size()
129 << ") for n volatility times ("
130 << sigmaTimes.size() << ")");
131 Real tolerance = parseReal(modelParameter("Tolerance"));
132 auto reversionType = parseReversionType(modelParameter("ReversionType"));
133 auto volatilityType = parseVolatilityType(modelParameter("VolatilityType"));
134 bool continueOnCalibrationError = globalParameters_.count("ContinueOnCalibrationError") > 0 &&
135 parseBool(globalParameters_.at("ContinueOnCalibrationError"));
136
137 auto data = QuantLib::ext::make_shared<IrLgmData>();
138
139 // check for allowed calibration / bermudan strategy settings
140 std::vector<std::pair<CalibrationType, CalibrationStrategy>> validCalPairs = {
146
147 QL_REQUIRE(std::find(validCalPairs.begin(), validCalPairs.end(),
148 std::make_pair(calibration, calibrationStrategy)) != validCalPairs.end(),
149 "Calibration (" << calibration << ") and CalibrationStrategy (" << calibrationStrategy
150 << ") are not allowed in this combination");
151
152 // compute horizon shift
153 Real shiftHorizon = parseReal(modelParameter("ShiftHorizon", {}, false, "0.5"));
154 Date today = Settings::instance().evaluationDate();
155 shiftHorizon = ActualActual(ActualActual::ISDA).yearFraction(today, maturity) * shiftHorizon;
156
157 // Default: no calibration, constant lambda and sigma from engine configuration
158 data->reset();
159 data->qualifier() = key;
160 data->calibrateH() = false;
161 data->hParamType() = ParamType::Constant;
162 data->hValues() = {lambda};
163 data->reversionType() = reversionType;
164 data->calibrateA() = false;
165 data->aParamType() = ParamType::Piecewise;
166 data->aValues() = sigma;
167 data->aTimes() = sigmaTimes;
168 data->volatilityType() = volatilityType;
169 data->calibrationType() = calibration;
170 data->shiftHorizon() = shiftHorizon;
171
172 // calibration expiries might be empty, in this case do not calibrate
173 if (!expiries.empty() && (calibrationStrategy == CalibrationStrategy::CoterminalATM ||
174 calibrationStrategy == CalibrationStrategy::CoterminalDealStrike)) {
175 DLOG("Build LgmData for co-terminal specification");
176 vector<string> expiryDates, termDates;
177 for (Size i = 0; i < expiries.size(); ++i) {
178 expiryDates.push_back(to_string(expiries[i]));
179 termDates.push_back(to_string(maturity));
180 }
181 data->optionExpiries() = expiryDates;
182 data->optionTerms() = termDates;
183 data->optionStrikes().resize(expiryDates.size(), "ATM");
184 if (calibrationStrategy == CalibrationStrategy::CoterminalDealStrike) {
185 for (Size i = 0; i < expiryDates.size(); ++i) {
186 if (strikes[i] != Null<Real>())
187 data->optionStrikes()[i] = std::to_string(strikes[i]);
188 }
189 }
190 if (calibration == CalibrationType::Bootstrap) {
191 DLOG("Calibrate piecewise alpha");
192 data->calibrationType() = CalibrationType::Bootstrap;
193 data->calibrateH() = false;
194 data->hParamType() = ParamType::Constant;
195 data->hValues() = {lambda};
196 data->calibrateA() = true;
197 data->aParamType() = ParamType::Piecewise;
198 data->aValues() = {sigma};
199 } else if (calibration == CalibrationType::BestFit) {
200 DLOG("Calibrate constant sigma");
201 data->calibrationType() = CalibrationType::BestFit;
202 data->calibrateH() = false;
203 data->hParamType() = ParamType::Constant;
204 data->hValues() = {lambda};
205 data->calibrateA() = true;
206 data->aParamType() = ParamType::Constant;
207 data->aValues() = {sigma};
208 } else
209 QL_FAIL("choice of calibration type invalid");
210 }
211
212 bool generateAdditionalResults = false;
213 auto p = globalParameters_.find("GenerateAdditionalResults");
214 if (p != globalParameters_.end()) {
215 generateAdditionalResults = parseBool(p->second);
216 }
217
218 // Build model
219 DLOG("Build LGM model");
220 QuantLib::ext::shared_ptr<LgmBuilder> calib = QuantLib::ext::make_shared<LgmBuilder>(
221 market_, data, configuration(MarketContext::irCalibration), tolerance, continueOnCalibrationError,
222 referenceCalibrationGrid, generateAdditionalResults, id);
223
224 // In some cases, we do not want to calibrate the model
225 QuantLib::ext::shared_ptr<QuantExt::LGM> model;
226 if (globalParameters_.count("Calibrate") == 0 || parseBool(globalParameters_.at("Calibrate"))) {
227 DLOG("Calibrate model (configuration " << configuration(MarketContext::irCalibration) << ")");
228 model = calib->model();
229 } else {
230 DLOG("Skip calibration of model based on global parameters");
231 calib->freeze();
232 model = calib->model();
233 calib->unfreeze();
234 }
235 modelBuilders_.insert(std::make_pair(id, calib));
236
237 return model;
238}
239
240QuantLib::ext::shared_ptr<QuantLib::PricingEngine>
242
243 DLOG("Get engine data");
244 Real sy = parseReal(engineParameter("sy"));
245 Size ny = parseInteger(engineParameter("ny"));
246 Real sx = parseReal(engineParameter("sx"));
247 Size nx = parseInteger(engineParameter("nx"));
248 Size maxGapDays = parseInteger(engineParameter("MaxGapDays"));
249 Size maxDiscretisationPoints = parseInteger(engineParameter("MaxDiscretisationPoints"));
250 if (maxDiscretisationPoints == 0)
251 maxDiscretisationPoints = Null<Size>();
252
253 // determine expiries and strikes for calibration basket (simple approach, a la summit)
254
255 auto qlInstr = QuantLib::ext::dynamic_pointer_cast<QuantExt::RiskParticipationAgreement>(rpa->instrument()->qlInstrument());
256 QL_REQUIRE(qlInstr != nullptr, "RiskParticipationAgreementSwapLGMGridEngineBuilder: internal error, could not "
257 "cast to RiskParticipationAgreement");
258
259 std::vector<Date> expiries;
260 std::vector<Real> strikes;
261
262 Date today = Settings::instance().evaluationDate();
263 Date calibrationMaturity = std::max(qlInstr->underlyingMaturity(), today);
264
265 // the first ibor / ois index found
266 QuantLib::ext::shared_ptr<IborIndex> index;
267
268 // if protection end <= today there is no model dependent part to value (just fees, possibly), so
269 // we just pass a dummy calibration instruments
270 if (rpa->protectionEnd() > today) {
272 today, rpa->protectionStart(), rpa->protectionEnd(), qlInstr->underlying(), maxGapDays,
273 maxDiscretisationPoints);
274 for (Size i = 0; i < gridDates.size() - 1; ++i) {
275 Date mid = gridDates[i] + (gridDates[i + 1] - gridDates[i]) / 2;
276 // mid might be = reference date degenerate cases where the first two discretisation points
277 // are only one day apart from each other
278 if (mid > today && ((calibrationMaturity - mid) >= 90 || expiries.empty()))
279 expiries.push_back(mid);
280 }
281
282 std::vector<QuantLib::ext::shared_ptr<QuantLib::FixedRateCoupon>> fixedCpns;
283 std::vector<QuantLib::ext::shared_ptr<QuantLib::FloatingRateCoupon>> floatingCpns;
284 for (auto const& l : qlInstr->underlying()) {
285 for (auto const& c : l) {
286 if (auto fixedCpn = QuantLib::ext::dynamic_pointer_cast<QuantLib::FixedRateCoupon>(c))
287 fixedCpns.push_back(fixedCpn);
288 if (auto floatingCpn = QuantLib::ext::dynamic_pointer_cast<QuantLib::FloatingRateCoupon>(c)) {
289 floatingCpns.push_back(floatingCpn);
290 if (index == nullptr)
291 index = QuantLib::ext::dynamic_pointer_cast<IborIndex>(floatingCpn->index());
292 }
293 }
294 }
295 auto cpnLt = [](const QuantLib::ext::shared_ptr<Coupon>& x, const QuantLib::ext::shared_ptr<Coupon>& y) {
296 return x->accrualStartDate() < y->accrualStartDate();
297 };
298 std::sort(fixedCpns.begin(), fixedCpns.end(), cpnLt);
299 std::sort(floatingCpns.begin(), floatingCpns.end(), cpnLt);
300
301 auto accLt = [](const QuantLib::ext::shared_ptr<Coupon>& x, const Date& e) { return x->accrualStartDate() < e; };
302 for (auto const& expiry : expiries) {
303 // look for the first fixed and float coupon with accrual start >= expiry
304 auto firstFix = std::lower_bound(fixedCpns.begin(), fixedCpns.end(), expiry, accLt);
305 auto firstFloat = std::lower_bound(floatingCpns.begin(), floatingCpns.end(), expiry, accLt);
306 // if we find both coupons, we take the fixed rate minus the floating spread as the calibration strike
307 // otherwise we set the strike to null meaning we request an ATM strike for the calibration
308 if (firstFix != fixedCpns.end() && firstFloat != floatingCpns.end())
309 strikes.push_back((*firstFix)->rate() - (*firstFloat)->spread());
310 else
311 strikes.push_back(Null<Real>());
312 }
313 }
314
315 // build model + engine
316 DLOG("Building LGM Grid RPA engine for trade " << id);
317 QuantLib::ext::shared_ptr<QuantExt::LGM> lgm =
318 model(id, index == nullptr ? rpa->npvCurrency() : IndexNameTranslator::instance().oreName(index->name()),
319 expiries, calibrationMaturity, strikes);
320 DLOG("Build engine (configuration " << configuration(MarketContext::pricing) << ")");
321 Handle<DefaultProbabilityTermStructure> creditCurve =
322 market_->defaultCurve(rpa->creditCurveId(), configuration(MarketContext::pricing))->curve();
323 Handle<Quote> recoveryRate = market_->recoveryRate(rpa->creditCurveId(), configuration(MarketContext::pricing));
324 return QuantLib::ext::make_shared<NumericLgmRiskParticipationAgreementEngine>(
325 rpa->npvCurrency(), getDiscountCurves(rpa), getFxSpots(rpa), lgm, sy, ny, sx, nx, creditCurve, recoveryRate,
326 maxGapDays, maxDiscretisationPoints);
327}
328
329QuantLib::ext::shared_ptr<QuantLib::PricingEngine>
332
333 DLOG("Get engine data");
334
335 Real sy = parseReal(engineParameter("sy"));
336 Size ny = parseInteger(engineParameter("ny"));
337 Real sx = parseReal(engineParameter("sx"));
338 Size nx = parseInteger(engineParameter("nx"));
339
340 Size timeStepsPerYear = parseInteger(engineParameter("TimeStepsPerYear"));
341 Period spacing = parsePeriod(modelParameter("CalibrationInstrumentSpacing"));
342
343 QL_REQUIRE(spacing != 0 * Days, "RiskParticipationAgreementTLockLGMGridEngineBuilder: CalibrationInstrumentSpacing "
344 "is 0D, this is not allowed.");
345
346 // determine expiries and strikes for calibration basket (coterminal ATM until termination date, spacing as
347 // specified in config)
348
349 auto qlInstr =
350 QuantLib::ext::dynamic_pointer_cast<QuantExt::RiskParticipationAgreementTLock>(rpa->instrument()->qlInstrument());
351 QL_REQUIRE(qlInstr != nullptr, "RiskParticipationAgreementTLockLGMGridEngineBuilder: internal error, could not "
352 "cast to RiskParticipationAgreementTLock");
353
354 QL_REQUIRE(qlInstr->bond() != nullptr,
355 "RiskParticipationAgreementTLockLGMGridEngineBuilder: internal error, bond is null");
356
357 std::vector<Date> expiries;
358 std::vector<Real> strikes;
359
360 Date today = Settings::instance().evaluationDate();
361 Date calibrationMaturity = std::max(qlInstr->bond()->maturityDate(), today);
362
363 // do not need calibration instruments, if the instrument price is not sensitive to the model
364
365 if (rpa->protectionEnd() > today && qlInstr->terminationDate() > today) {
366 Date calibrationDate = today + spacing;
367 while (calibrationDate < qlInstr->terminationDate()) {
368 if (expiries.empty() || (calibrationMaturity - calibrationDate) >= 90)
369 expiries.push_back(calibrationDate);
370 calibrationDate += spacing;
371 }
372 expiries.push_back(qlInstr->terminationDate());
373 strikes.resize(expiries.size(), Null<Real>());
374 }
375
376 // build model + engine
377
378 DLOG("Building LGM Grid RPA engine (tlock) for trade " << id);
379 QuantLib::ext::shared_ptr<QuantExt::LGM> lgm = model(id, rpa->npvCurrency(), expiries, calibrationMaturity, strikes);
380 DLOG("Build engine (configuration " << configuration(MarketContext::pricing) << ")");
381 Handle<DefaultProbabilityTermStructure> creditCurve =
382 market_->defaultCurve(rpa->creditCurveId(), configuration(MarketContext::pricing))->curve();
383 Handle<Quote> recoveryRate = market_->recoveryRate(rpa->creditCurveId(), configuration(MarketContext::pricing));
384 Handle<YieldTermStructure> treasuryCurve =
385 market_->yieldCurve(rpa->tlockData().bondData().referenceCurveId(), configuration(MarketContext::pricing));
386 try {
387 // spread is optional, add it to the treasury curve here, if given
388 auto spread =
389 market_->securitySpread(rpa->tlockData().bondData().securityId(), configuration(MarketContext::pricing));
390 treasuryCurve =
391 Handle<YieldTermStructure>(QuantLib::ext::make_shared<ZeroSpreadedTermStructure>(treasuryCurve, spread));
392 } catch (...) {
393 }
394
395 return QuantLib::ext::make_shared<NumericLgmRiskParticipationAgreementEngineTLock>(
396 rpa->npvCurrency(), getDiscountCurves(rpa), getFxSpots(rpa), lgm, sy, ny, sx, nx, treasuryCurve, creditCurve,
397 recoveryRate, timeStepsPerYear);
398}
399
400} // namespace data
401} // namespace ore
const Date & protectionEnd() const
const Date & protectionStart() const
QuantLib::ext::shared_ptr< Market > market_
const string & model() const
Return the model name.
std::string modelParameter(const std::string &p, const std::vector< std::string > &qualifiers={}, const bool mandatory=true, const std::string &defaultValue="") const
std::string engineParameter(const std::string &p, const std::vector< std::string > &qualifiers={}, const bool mandatory=true, const std::string &defaultValue="") const
const string & configuration(const MarketContext &key)
Return a configuration (or the default one if key not found)
set< std::pair< string, QuantLib::ext::shared_ptr< QuantExt::ModelBuilder > > > modelBuilders_
std::map< std::string, std::string > globalParameters_
static std::vector< Date > buildDiscretisationGrid(const Date &referenceDate, const Date &protectionStart, const Date &protectionEnd, const std::vector< Leg > &underlying, const Size maxGapDays=Null< Size >(), const Size maxDiscretisationPoints=Null< Size >())
QuantLib::ext::shared_ptr< QuantLib::PricingEngine > engineImpl(const std::string &id, RiskParticipationAgreement *rpa) override
std::map< std::string, Handle< Quote > > getFxSpots(RiskParticipationAgreement *rpa)
std::map< std::string, Handle< YieldTermStructure > > getDiscountCurves(RiskParticipationAgreement *rpa)
QuantLib::ext::shared_ptr< QuantLib::PricingEngine > engineImpl(const std::string &id, RiskParticipationAgreement *rpa) override
QuantLib::ext::shared_ptr< QuantLib::PricingEngine > engineImpl(const std::string &id, RiskParticipationAgreement *rpa) override
QuantLib::ext::shared_ptr< QuantLib::PricingEngine > engineImpl(const std::string &id, RiskParticipationAgreement *rpa) override
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
Definition: parsers.cpp:171
bool parseBool(const string &s)
Convert text to bool.
Definition: parsers.cpp:144
Real parseReal(const string &s)
Convert text to Real.
Definition: parsers.cpp:112
Integer parseInteger(const string &s)
Convert text to QuantLib::Integer.
Definition: parsers.cpp:136
translates between QuantLib::Index::name() and ORE names
Build an lgm model.
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
Time maturity
Definition: utilities.cpp:66
CalibrationType parseCalibrationType(const string &s)
Convert calibration type string into enumerated class value.
Definition: irmodeldata.cpp:47
CalibrationStrategy parseCalibrationStrategy(const string &s)
Convert calibration strategy string into enumerated class value.
Definition: irmodeldata.cpp:70
VolatilityType volatilityType(CapFloorVolatilityCurveConfig::VolatilityType type)
Imply QuantLib::VolatilityType from CapFloorVolatilityCurveConfig::VolatilityType.
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
LgmData::ReversionType parseReversionType(const string &s)
Enum parsers.
Definition: lgmdata.cpp:62
LgmData::VolatilityType parseVolatilityType(const string &s)
Definition: lgmdata.cpp:81
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.
vector< Real > strikes
string conversion utilities