Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
parsensitivityutilities.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2024 AcadiaSoft Inc.
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
22#include <ql/instruments/creditdefaultswap.hpp>
23#include <ql/instruments/forwardrateagreement.hpp>
24#include <ql/instruments/makecapfloor.hpp>
25#include <ql/instruments/makeois.hpp>
26#include <ql/instruments/makevanillaswap.hpp>
27#include <ql/instruments/yearonyearinflationswap.hpp>
28#include <ql/instruments/zerocouponinflationswap.hpp>
29#include <ql/math/solvers1d/newtonsafe.hpp>
30#include <ql/pricingengine.hpp>
31#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
32#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
33#include <ql/quotes/simplequote.hpp>
34#include <ql/termstructures/volatility/inflation/yoyinflationoptionletvolatilitystructure.hpp>
45
46using namespace QuantLib;
47using namespace QuantExt;
48using namespace std;
49
50namespace ore {
51namespace analytics {
52
53namespace {
54
55/* Helper class for implying the fair flat cap/floor volatility
56 This class is copied from QuantLib's capfloor.cpp and generalised to cover both normal and lognormal volatilities */
57class ImpliedCapFloorVolHelper {
58public:
59 ImpliedCapFloorVolHelper(
60 const QuantLib::Instrument& cap,
61 const std::function<QuantLib::ext::shared_ptr<QuantLib::PricingEngine>(const QuantLib::Handle<Quote>)>
62 engineGenerator,
63 const Real targetValue);
64 Real operator()(Volatility x) const;
65 Real derivative(Volatility x) const;
66
67private:
69 QuantLib::ext::shared_ptr<PricingEngine> engine_;
70 QuantLib::ext::shared_ptr<QuantLib::SimpleQuote> vol_;
71 const Instrument::results* results_;
72};
73
74ImpliedCapFloorVolHelper::ImpliedCapFloorVolHelper(
75 const QuantLib::Instrument& cap,
76 const std::function<QuantLib::ext::shared_ptr<PricingEngine>(const Handle<Quote>)> engineGenerator,
77 const Real targetValue)
78 : targetValue_(targetValue) {
79 // set an implausible value, so that calculation is forced
80 // at first ImpliedCapFloorVolHelper::operator()(Volatility x) call
81 vol_ = QuantLib::ext::shared_ptr<SimpleQuote>(new SimpleQuote(-1));
82 engine_ = engineGenerator(Handle<Quote>(vol_));
83 cap.setupArguments(engine_->getArguments());
84 results_ = dynamic_cast<const Instrument::results*>(engine_->getResults());
85}
86
87Real ImpliedCapFloorVolHelper::operator()(Volatility x) const {
88 if (x != vol_->value()) {
89 vol_->setValue(x);
90 engine_->calculate();
91 }
92 return results_->value - targetValue_;
93}
94
95Real ImpliedCapFloorVolHelper::derivative(Volatility x) const {
96 if (x != vol_->value()) {
97 vol_->setValue(x);
98 engine_->calculate();
99 }
100 std::map<std::string, boost::any>::const_iterator vega_ = results_->additionalResults.find("vega");
101 QL_REQUIRE(vega_ != results_->additionalResults.end(), "vega not provided");
102 return boost::any_cast<Real>(vega_->second);
103}
104
105std::function<QuantLib::ext::shared_ptr<PricingEngine>(const Handle<Quote>&)>
106pricingEngineGeneratorFactory(const CapFloor& cap, const Handle<YieldTermStructure>& d, VolatilityType type,
107 Real displacement, const Handle<Index>& index) {
108 std::function<QuantLib::ext::shared_ptr<PricingEngine>(const Handle<Quote>)> engineGenerator;
109 if (type == ShiftedLognormal)
110 engineGenerator = [&d, displacement](const Handle<Quote>& h) {
111 return QuantLib::ext::make_shared<BlackCapFloorEngine>(d, h, Actual365Fixed(), displacement);
112 };
113 else if (type == Normal)
114 engineGenerator = [&d](const Handle<Quote>& h) {
115 return QuantLib::ext::make_shared<BachelierCapFloorEngine>(d, h, Actual365Fixed());
116 };
117 else
118 QL_FAIL("volatility type " << type << " not implemented");
119 return engineGenerator;
120}
121
122std::function<QuantLib::ext::shared_ptr<PricingEngine>(const Handle<Quote>&)>
123pricingEngineGeneratorFactory(const YoYInflationCapFloor& cap, const Handle<YieldTermStructure>& d, VolatilityType type,
124 Real displacement, const Handle<YoYInflationIndex>& index) {
125 std::function<QuantLib::ext::shared_ptr<PricingEngine>(const Handle<Quote>)> engineGenerator;
126 if (type == ShiftedLognormal) {
127 if (close_enough(displacement, 0.0))
128 engineGenerator = [&d, &index](const Handle<Quote>& h) {
129 // hardcode A365F as for ir caps, or should we use the dc from the original market vol ts ?
130 // calendar, bdc not needed here, settlement days should be zero so that the
131 // reference date is = evaluation date
132 auto c = Handle<QuantLib::YoYOptionletVolatilitySurface>(
133 QuantLib::ext::make_shared<QuantExt::ConstantYoYOptionletVolatility>(
134 h, 0, NullCalendar(), Unadjusted, Actual365Fixed(),
135 index->yoyInflationTermStructure()->observationLag(), index->frequency(),
136 index->interpolated()));
137 return QuantLib::ext::make_shared<QuantExt::YoYInflationBlackCapFloorEngine>(*index, c, d);
138 };
139 else
140 engineGenerator = [&d, &index](const Handle<Quote>& h) {
141 auto c = Handle<QuantLib::YoYOptionletVolatilitySurface>(
142 QuantLib::ext::make_shared<QuantExt::ConstantYoYOptionletVolatility>(
143 h, 0, NullCalendar(), Unadjusted, Actual365Fixed(),
144 index->yoyInflationTermStructure()->observationLag(), index->frequency(),
145 index->interpolated()));
146 return QuantLib::ext::make_shared<QuantExt::YoYInflationUnitDisplacedBlackCapFloorEngine>(*index, c, d);
147 };
148 } else if (type == Normal)
149 engineGenerator = [&d, &index](const Handle<Quote>& h) {
150 auto c = Handle<QuantLib::YoYOptionletVolatilitySurface>(
151 QuantLib::ext::make_shared<QuantExt::ConstantYoYOptionletVolatility>(
152 h, 0, NullCalendar(), Unadjusted, Actual365Fixed(),
153 index->yoyInflationTermStructure()->observationLag(), index->frequency(), index->interpolated()));
154 return QuantLib::ext::make_shared<QuantExt::YoYInflationBachelierCapFloorEngine>(*index, c, d);
155 };
156 else
157 QL_FAIL("volatility type " << type << " not implemented");
158 return engineGenerator;
159}
160
161template <typename CapFloorType, typename IndexType>
162Volatility impliedVolatilityImpl(const CapFloorType& cap, Real targetValue, const Handle<YieldTermStructure>& d,
163 Volatility guess, VolatilityType type, Real displacement, Real accuracy,
164 Natural maxEvaluations, Volatility minVolLognormal, Volatility maxVolLognormal,
165 Volatility minVolNormal, Volatility maxVolNormal,
166 const Handle<IndexType>& index = Handle<IndexType>()) {
167 QL_REQUIRE(!cap.isExpired(), "instrument expired");
168 auto engineGenerator = pricingEngineGeneratorFactory(cap, d, type, displacement, index);
169 ImpliedCapFloorVolHelper f(cap, engineGenerator, targetValue);
170 NewtonSafe solver;
171 solver.setMaxEvaluations(maxEvaluations);
172 Real minVol = type == Normal ? minVolNormal : minVolLognormal;
173 Real maxVol = type == Normal ? maxVolNormal : maxVolLognormal;
174 return solver.solve(f, accuracy, guess, minVol, maxVol);
175}
176
177// wrapper function, does not throw
178template <typename CapFloorType, typename IndexType>
179Volatility impliedVolatilityWrapper(const CapFloorType& cap, Real targetValue, const Handle<YieldTermStructure>& d,
180 Volatility guess, VolatilityType type, Real displacement,
181 const Handle<IndexType>& index = Handle<IndexType>()) {
182 string strikeStr = "?";
183 try {
184
185 Real accuracy = 1.0e-6;
186 Natural maxEvaluations = 100;
187 Volatility minVolLognormal = 1.0e-7;
188 Volatility maxVolLognormal = 4.0;
189 Volatility minVolNormal = 1.0e-7;
190 Volatility maxVolNormal = 0.05;
191
192 // 1. Get strike for logging
193 std::ostringstream oss;
194 if (!cap.capRates().empty()) {
195 oss << "Cap: " << cap.capRates().size() << " strikes, starting with " << cap.capRates().front()
196 << "."; // they are probably all the same here
197 }
198 if (!cap.floorRates().empty()) {
199 oss << "Floor: " << cap.floorRates().size() << " strikes, starting with " << cap.floorRates().front()
200 << "."; // they are probably all the same here
201 }
202 strikeStr = oss.str();
203
204 // 2. Try to get implied Vol with defaults
205 TLOG("Getting impliedVolatility for cap (" << cap.maturityDate() << " strike " << strikeStr << ")");
206 try {
207 double vol =
208 impliedVolatilityImpl(cap, targetValue, d, guess, type, displacement, accuracy, maxEvaluations,
209 minVolLognormal, maxVolLognormal, minVolNormal, maxVolNormal, index);
210 TLOG("Got vol " << vol << " on first attempt");
211 return vol;
212 } catch (std::exception& e) {
213 ALOG("Exception getting implied Vol for Cap (" << cap.maturityDate() << " strike " << strikeStr << ") "
214 << e.what());
215 }
216
217 // 3. Try with bigger bounds
218 try {
219 Volatility vol = impliedVolatilityImpl(cap, targetValue, d, guess, type, displacement, accuracy,
220 maxEvaluations, minVolLognormal / 100.0, maxVolLognormal * 100.0,
221 minVolNormal / 100.0, maxVolNormal * 100.0, index);
222 TLOG("Got vol " << vol << " on second attempt");
223 return vol;
224 } catch (std::exception& e) {
225 ALOG("Exception getting implied Vol for Cap (" << cap.maturityDate() << " strike " << strikeStr << ") "
226 << e.what());
227 }
228
229 } catch (...) {
230 // pass through to below
231 }
232
233 ALOG("Cap impliedVolatility() failed for Cap (" << cap.type() << ", maturity " << cap.maturityDate() << ", strike "
234 << strikeStr << " for target " << targetValue
235 << ". Returning Initial guess " << guess << " and continuing");
236 return guess;
237}
238} // namespace
239
240Real impliedQuote(const QuantLib::ext::shared_ptr<Instrument>& i) {
241 if (QuantLib::ext::dynamic_pointer_cast<VanillaSwap>(i))
242 return QuantLib::ext::dynamic_pointer_cast<VanillaSwap>(i)->fairRate();
243 if (QuantLib::ext::dynamic_pointer_cast<Deposit>(i))
244 return QuantLib::ext::dynamic_pointer_cast<Deposit>(i)->fairRate();
245 if (QuantLib::ext::dynamic_pointer_cast<QuantLib::ForwardRateAgreement>(i))
246 return QuantLib::ext::dynamic_pointer_cast<QuantLib::ForwardRateAgreement>(i)->forwardRate();
247 if (QuantLib::ext::dynamic_pointer_cast<OvernightIndexedSwap>(i))
248 return QuantLib::ext::dynamic_pointer_cast<OvernightIndexedSwap>(i)->fairRate();
249 if (QuantLib::ext::dynamic_pointer_cast<CrossCcyBasisMtMResetSwap>(i))
250 return QuantLib::ext::dynamic_pointer_cast<CrossCcyBasisMtMResetSwap>(i)->fairSpread();
251 if (QuantLib::ext::dynamic_pointer_cast<CrossCcyBasisSwap>(i))
252 return QuantLib::ext::dynamic_pointer_cast<CrossCcyBasisSwap>(i)->fairPaySpread();
253 if (QuantLib::ext::dynamic_pointer_cast<FxForward>(i))
254 return QuantLib::ext::dynamic_pointer_cast<FxForward>(i)->fairForwardRate().rate();
255 if (QuantLib::ext::dynamic_pointer_cast<QuantExt::CreditDefaultSwap>(i))
256 return QuantLib::ext::dynamic_pointer_cast<QuantExt::CreditDefaultSwap>(i)->fairSpreadClean();
257 if (QuantLib::ext::dynamic_pointer_cast<ZeroCouponInflationSwap>(i))
258 return QuantLib::ext::dynamic_pointer_cast<ZeroCouponInflationSwap>(i)->fairRate();
259 if (QuantLib::ext::dynamic_pointer_cast<YearOnYearInflationSwap>(i))
260 return QuantLib::ext::dynamic_pointer_cast<YearOnYearInflationSwap>(i)->fairRate();
261 if (QuantLib::ext::dynamic_pointer_cast<TenorBasisSwap>(i)) {
262 if (QuantLib::ext::dynamic_pointer_cast<TenorBasisSwap>(i)->spreadOnRec())
263 return QuantLib::ext::dynamic_pointer_cast<TenorBasisSwap>(i)->fairRecLegSpread();
264 else
265 return QuantLib::ext::dynamic_pointer_cast<TenorBasisSwap>(i)->fairPayLegSpread();
266 }
267 if (QuantLib::ext::dynamic_pointer_cast<FixedBMASwap>(i))
268 return QuantLib::ext::dynamic_pointer_cast<FixedBMASwap>(i)->fairRate();
269 if (QuantLib::ext::dynamic_pointer_cast<SubPeriodsSwap>(i))
270 return QuantLib::ext::dynamic_pointer_cast<SubPeriodsSwap>(i)->fairRate();
271 QL_FAIL("SensitivityAnalysis: impliedQuote: unknown instrument (is null = " << std::boolalpha << (i == nullptr)
272 << ")");
273}
274
275Volatility impliedVolatility(const QuantLib::CapFloor& cap, Real targetValue, const Handle<YieldTermStructure>& d,
276 Volatility guess, VolatilityType type, Real displacement) {
277 return impliedVolatilityWrapper(cap, targetValue, d, guess, type, displacement, Handle<Index>());
278}
279
281 const Handle<YieldTermStructure>& d, Volatility guess, VolatilityType type,
282 Real displacement, const Handle<YoYInflationIndex>& index) {
283 return impliedVolatilityWrapper(cap, targetValue, d, guess, type, displacement, index);
284}
285
287 return x.keytype == y.keytype && x.name == y.name;
288}
289
291 if (key.keytype == RiskFactorKey::KeyType::OptionletVolatility) {
292 QL_REQUIRE(instruments.parCaps_.count(key) == 1, "Can not convert capFloor par shifts to zero Vols");
293 QL_REQUIRE(instruments.parCapsYts_.count(key) > 0,
294 "getTodaysAndTargetQuotes: no cap yts found for key " << key);
295 QL_REQUIRE(instruments.parCapsVts_.count(key) > 0,
296 "getTodaysAndTargetQuotes: no cap vts found for key " << key);
297 const auto cap = instruments.parCaps_.at(key);
298 Real price = cap->NPV();
299 Volatility parVol = impliedVolatility(*cap, price, instruments.parCapsYts_.at(key), 0.01,
300 instruments.parCapsVts_.at(key)->volatilityType(),
301 instruments.parCapsVts_.at(key)->displacement());
302 return parVol;
303 } else if (key.keytype == RiskFactorKey::KeyType::YoYInflationCapFloorVolatility) {
304 QL_REQUIRE(instruments.parYoYCaps_.count(key) == 1, "Can not convert capFloor par shifts to zero Vols");
305 QL_REQUIRE(instruments.parYoYCapsYts_.count(key) > 0,
306 "getTodaysAndTargetQuotes: no cap yts found for key " << key);
307 QL_REQUIRE(instruments.parYoYCapsVts_.count(key) > 0,
308 "getTodaysAndTargetQuotes: no cap vts found for key " << key);
309 QL_REQUIRE(instruments.parYoYCapsIndex_.count(key) > 0,
310 "getTodaysAndTargetQuotes: no cap index found for key " << key);
311 const auto& cap = instruments.parYoYCaps_.at(key);
312 Real price = cap->NPV();
314 *cap, price, instruments.parYoYCapsYts_.at(key), 0.01, instruments.parYoYCapsVts_.at(key)->volatilityType(),
315 instruments.parYoYCapsVts_.at(key)->displacement(), instruments.parYoYCapsIndex_.at(key));
316 return parVol;
317 } else {
318 QL_FAIL("impliedCapVolatility: Unsupported risk factor key "
319 << key.keytype << ". Support OptionletVolatility and YoYInflationCapFloorVolatility");
320 }
321}
322
323} // namespace analytics
324} // namespace ore
boost::shared_ptr< SimpleQuote > vol_
Real targetValue_
boost::shared_ptr< PricingEngine > engine_
const Instrument::results * results_
Data types stored in the scenario class.
Definition: scenario.hpp:48
KeyType keytype
Key type.
Definition: scenario.hpp:89
std::string name
Key name.
Definition: scenario.hpp:94
#define ALOG(text)
#define TLOG(text)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Volatility impliedVolatility(const QuantLib::CapFloor &cap, Real targetValue, const Handle< YieldTermStructure > &d, Volatility guess, VolatilityType type, Real displacement)
bool riskFactorKeysAreSimilar(const ore::analytics::RiskFactorKey &x, const ore::analytics::RiskFactorKey &y)
true if key type and name are equal, do not care about the index though
Real impliedQuote(const QuantLib::ext::shared_ptr< Instrument > &i)
QuantLib::ext::shared_ptr< QuantLib::SimpleQuote > vol_
QuantLib::ext::shared_ptr< PricingEngine > engine_
Real targetValue_
const Instrument::results * results_
std::map< ore::analytics::RiskFactorKey, QuantLib::Handle< QuantExt::YoYOptionletVolatilitySurface > > parYoYCapsVts_
std::map< ore::analytics::RiskFactorKey, QuantLib::Handle< QuantLib::YieldTermStructure > > parYoYCapsYts_
par helpers: YoY cap / floors
std::map< ore::analytics::RiskFactorKey, QuantLib::Handle< QuantLib::YieldTermStructure > > parCapsYts_
std::map< ore::analytics::RiskFactorKey, QuantLib::ext::shared_ptr< QuantLib::CapFloor > > parCaps_
par helpers: IR cap / floors
std::map< ore::analytics::RiskFactorKey, QuantLib::Handle< QuantLib::OptionletVolatilityStructure > > parCapsVts_
std::map< ore::analytics::RiskFactorKey, QuantLib::ext::shared_ptr< QuantLib::YoYInflationCapFloor > > parYoYCaps_
std::map< ore::analytics::RiskFactorKey, QuantLib::Handle< QuantLib::YoYInflationIndex > > parYoYCapsIndex_