Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
spreadedblackvolatilitysurfacemoneyness.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2020 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
21#include <ql/math/interpolations/bilinearinterpolation.hpp>
22#include <ql/math/interpolations/flatextrapolation2d.hpp>
23#include <ql/math/interpolations/linearinterpolation.hpp>
24#include <ql/quotes/simplequote.hpp>
25#include <ql/termstructures/yield/forwardcurve.hpp>
26#include <ql/utilities/dataformatters.hpp>
27
28#include <boost/make_shared.hpp>
29
30using namespace std;
31
32namespace QuantExt {
33
35 const Handle<BlackVolTermStructure>& referenceVol, const Handle<Quote>& movingSpot, const std::vector<Time>& times,
36 const std::vector<Real>& moneyness, const std::vector<std::vector<Handle<Quote>>>& volSpreads,
37 const Handle<Quote>& stickySpot, const Handle<YieldTermStructure>& stickyDividendTs,
38 const Handle<YieldTermStructure>& stickyRiskFreeTs, const Handle<YieldTermStructure>& movingDividendTs,
39 const Handle<YieldTermStructure>& movingRiskFreeTs, bool stickyStrike)
40 : BlackVolatilityTermStructure(referenceVol->businessDayConvention(), referenceVol->dayCounter()),
41 referenceVol_(referenceVol), movingSpot_(movingSpot), times_(times), moneyness_(moneyness),
42 volSpreads_(volSpreads), stickySpot_(stickySpot), stickyDividendTs_(stickyDividendTs),
43 stickyRiskFreeTs_(stickyRiskFreeTs), movingDividendTs_(movingDividendTs), movingRiskFreeTs_(movingRiskFreeTs),
44 stickyStrike_(stickyStrike) {
45
46 // register with observables
47
48 registerWith(referenceVol_);
49 registerWith(movingSpot_);
50 registerWith(stickySpot_);
51
52 for (auto const& v : volSpreads_)
53 for (auto const& s : v)
54 registerWith(s);
55
56 registerWith(stickyDividendTs_);
57 registerWith(stickyRiskFreeTs_);
58 registerWith(movingDividendTs_);
59 registerWith(movingRiskFreeTs_);
60
61 // check our preconditions on the inputs
62
63 QL_REQUIRE(!times_.empty(), "no times given");
64 QL_REQUIRE(!moneyness_.empty(), "no moneyness values given");
65 QL_REQUIRE(moneyness_.size() == volSpreads_.size(), "mismatch between moneyness vector and vol matrix rows");
66
67 for (auto const& v : volSpreads_) {
68 QL_REQUIRE(times_.size() == v.size(), "mismatch between times vector and vol matrix columns");
69 }
70
71 for (Size j = 1; j < times_.size(); ++j) {
72 QL_REQUIRE(times_[j] > times_[j - 1], "Times must be sorted and unique but found that the "
73 << io::ordinal(j) << " time, " << times_[j]
74 << ", is not greater than the " << io::ordinal(j - 1) << " time, "
75 << times_[j - 1] << ".");
76 }
77
78 // add an artificial time if there is only one to make the interpolation work
79
80 if (times_.size() == 1) {
81 times_.push_back(times_.back() + 1.0);
82 for (auto& v : volSpreads_)
83 v.push_back(v.back());
84 }
85
86 // add an artificial moneyness if there is only one to make the interpolation work
87
88 if (moneyness_.size() == 1) {
89 moneyness_.push_back(moneyness_.back() + 1.0);
90 volSpreads_.push_back(volSpreads_.back());
91 }
92
93 // create data matrix used for interpolation and the interpolation object
94
95 data_ = Matrix(moneyness_.size(), times_.size(), 0.0);
96 volSpreadSurface_ = FlatExtrapolator2D(QuantLib::ext::make_shared<BilinearInterpolation>(
97 times_.begin(), times_.end(), moneyness_.begin(), moneyness_.end(), data_));
98 volSpreadSurface_.enableExtrapolation();
99}
100
102const Date& SpreadedBlackVolatilitySurfaceMoneyness::referenceDate() const { return referenceVol_->referenceDate(); }
107
109 LazyObject::update();
110 BlackVolatilityTermStructure::update();
111}
112
113const std::vector<QuantLib::Real>& SpreadedBlackVolatilitySurfaceMoneyness::moneyness() const { return moneyness_; }
114
116 for (Size j = 0; j < data_.columns(); ++j) {
117 for (Size i = 0; i < data_.rows(); ++i) {
118 data_(i, j) = volSpreads_[i][j]->value();
119 }
120 }
121 volSpreadSurface_.update();
122}
123
125 calculate();
126 QL_REQUIRE(!referenceVol_.empty(), "SpreadedBlackVolatilitySurfaceMoneyness: reference vol is empty");
127 Real m = moneynessFromStrike(t, strike, false);
128 QL_REQUIRE(std::isfinite(m),
129 "SpreadedBlackVolatilitySurfaceMoneyness: got invalid moneyness (dynamic reference) at t = "
130 << t << ", strike = " << strike << ": " << m);
131 Real effStrike;
132 if (stickyStrike_)
133 effStrike = strike;
134 else {
135 effStrike = strikeFromMoneyness(t, m, true);
136 QL_REQUIRE(std::isfinite(effStrike),
137 "SpreadedBlackVolatilitySurfaceMoneyness: got invalid strike from moneyness at t = "
138 << t << ", input strike = " << strike << ", moneyness = " << m);
139 }
140 Real m2 = moneynessFromStrike(t, strike, false);
141 QL_REQUIRE(std::isfinite(m2),
142 "SpreadedBlackVolatilitySurfaceMoneyness: got invalid moneyness (sticky reference) at t = "
143 << t << ", strike = " << strike << ": " << m2);
144 return referenceVol_->blackVol(t, effStrike) + volSpreadSurface_(t, m2);
145}
146
148 const bool stickyReference) const {
149 if (strike == Null<Real>() || close_enough(strike, 0.0)) {
150 return 1.0;
151 } else {
152 QL_REQUIRE(!stickyReference || !stickySpot_.empty(),
153 "SpreadedBlackVolatilitySurfaceMoneynessSpot: stickySpot is empty");
154 QL_REQUIRE(stickyReference || !movingSpot_.empty(),
155 "SpreadedBlackVolatilitySurfaceMoneynessSpot: movingSpot is empty");
156 return strike / (stickyReference ? stickySpot_->value() : movingSpot_->value());
157 }
158}
159
161 const bool stickyReference) const {
162 QL_REQUIRE(!stickyReference || !stickySpot_.empty(),
163 "SpreadedBlackVolatilitySurfaceMoneynessSpot: stickySpot is empty");
164 QL_REQUIRE(stickyReference || !movingSpot_.empty(),
165 "SpreadedBlackVolatilitySurfaceMoneynessSpot: movingSpot is empty");
166 return moneyness * (stickyReference ? stickySpot_->value() : movingSpot_->value());
167}
168
170 const bool stickyReference) const {
171 if (strike == Null<Real>() || close_enough(strike, 0.0)) {
172 return 0.0;
173 } else {
174 QL_REQUIRE(!stickyReference || !stickySpot_.empty(),
175 "SpreadedBlackVolatilitySurfaceLogMoneynessSpot: stickySpot is empty");
176 QL_REQUIRE(stickyReference || !movingSpot_.empty(),
177 "SpreadedBlackVolatilitySurfaceLogMoneynessSpot: movingSpot is empty");
178 return std::log(strike / (stickyReference ? stickySpot_->value() : movingSpot_->value()));
179 }
180}
181
183 const bool stickyReference) const {
184 QL_REQUIRE(!stickyReference || !stickySpot_.empty(),
185 "SpreadedBlackVolatilitySurfaceLogMoneynessSpot: stickySpot is empty");
186 QL_REQUIRE(stickyReference || !movingSpot_.empty(),
187 "SpreadedBlackVolatilitySurfaceLogMoneynessSpot: movingSpot is empty");
188 return std::exp(moneyness) * (stickyReference ? stickySpot_->value() : movingSpot_->value());
189}
190
192 const bool stickyReference) const {
193 if (strike == Null<Real>() || close_enough(strike, 0.0))
194 return 1.0;
195 else {
196 Real forward;
197 if (stickyReference) {
198 QL_REQUIRE(!stickySpot_.empty(), "SpreadedBlackVolatilitySurfaceMoneynessForward: stickySpot is empty");
199 QL_REQUIRE(!stickyDividendTs_.empty(),
200 "SpreadedBlackVolatilitySurfaceMoneynessForward: stickyDividendTs is empty");
201 QL_REQUIRE(!stickyRiskFreeTs_.empty(),
202 "SpreadedBlackVolatilitySurfaceMoneynessForward: stickyRiskFreeTs is empty");
203 forward = stickySpot_->value() * stickyDividendTs_->discount(t) / stickyRiskFreeTs_->discount(t);
204 } else {
205 QL_REQUIRE(!movingSpot_.empty(), "SpreadedBlackVolatilitySurfaceMoneynessForward: movingSpot is empty");
206 QL_REQUIRE(!movingDividendTs_.empty(),
207 "SpreadedBlackVolatilitySurfaceMoneynessForward: movingDividendTs is empty");
208 QL_REQUIRE(!movingRiskFreeTs_.empty(),
209 "SpreadedBlackVolatilitySurfaceMoneynessForward: mocingRiskFreeTs is empty");
210 forward = movingSpot_->value() * movingDividendTs_->discount(t) / movingRiskFreeTs_->discount(t);
211 }
212 return strike / forward;
213 }
214}
215
217 const bool stickyReference) const {
218 Real forward;
219 if (stickyReference) {
220 QL_REQUIRE(!stickySpot_.empty(), "SpreadedBlackVolatilitySurfaceMoneynessForward: stickySpot is empty");
221 QL_REQUIRE(!stickyDividendTs_.empty(),
222 "SpreadedBlackVolatilitySurfaceMoneynessForward: stickyDividendTs is empty");
223 QL_REQUIRE(!stickyRiskFreeTs_.empty(),
224 "SpreadedBlackVolatilitySurfaceMoneynessForward: stickyRiskFreeTs is empty");
225 forward = stickySpot_->value() * stickyDividendTs_->discount(t) / stickyRiskFreeTs_->discount(t);
226 } else {
227 QL_REQUIRE(!movingSpot_.empty(), "SpreadedBlackVolatilitySurfaceMoneynessForward: movingSpot is empty");
228 QL_REQUIRE(!movingDividendTs_.empty(),
229 "SpreadedBlackVolatilitySurfaceMoneynessForward: movingDividendTs is empty");
230 QL_REQUIRE(!movingRiskFreeTs_.empty(),
231 "SpreadedBlackVolatilitySurfaceMoneynessForward: mocingRiskFreeTs is empty");
232 forward = movingSpot_->value() * movingDividendTs_->discount(t) / movingRiskFreeTs_->discount(t);
233 }
234 return moneyness * forward;
235}
236
238 const bool stickyReference) const {
239 if (strike == Null<Real>() || close_enough(strike, 0.0))
240 return 0.0;
241 else {
242 Real forward;
243 if (stickyReference) {
244 QL_REQUIRE(!stickySpot_.empty(), "SpreadedBlackVolatilitySurfaceLogMoneynessForward: stickySpot is empty");
245 QL_REQUIRE(!stickyDividendTs_.empty(),
246 "SpreadedBlackVolatilitySurfaceLogMoneynessForward: stickyDividendTs is empty");
247 QL_REQUIRE(!stickyRiskFreeTs_.empty(),
248 "SpreadedBlackVolatilitySurfaceLogMoneynessForward: stickyRiskFreeTs is empty");
249 forward = stickySpot_->value() * stickyDividendTs_->discount(t) / stickyRiskFreeTs_->discount(t);
250 } else {
251 QL_REQUIRE(!movingSpot_.empty(), "SpreadedBlackVolatilitySurfaceLogMoneynessForward: movingSpot is empty");
252 QL_REQUIRE(!movingDividendTs_.empty(),
253 "SpreadedBlackVolatilitySurfaceLogMoneynessForward: movingDividendTs is empty");
254 QL_REQUIRE(!movingRiskFreeTs_.empty(),
255 "SpreadedBlackVolatilitySurfaceLogMoneynessForward: mocingRiskFreeTs is empty");
256 forward = movingSpot_->value() * movingDividendTs_->discount(t) / movingRiskFreeTs_->discount(t);
257 }
258 return std::log(strike / forward);
259 }
260}
261
263 const bool stickyReference) const {
264 Real forward;
265 if (stickyReference) {
266 QL_REQUIRE(!stickySpot_.empty(), "SpreadedBlackVolatilitySurfaceLogMoneynessForward: stickySpot is empty");
267 QL_REQUIRE(!stickyDividendTs_.empty(),
268 "SpreadedBlackVolatilitySurfaceLogMoneynessForward: stickyDividendTs is empty");
269 QL_REQUIRE(!stickyRiskFreeTs_.empty(),
270 "SpreadedBlackVolatilitySurfaceLogMoneynessForward: stickyRiskFreeTs is empty");
271 forward = stickySpot_->value() * stickyDividendTs_->discount(t) / stickyRiskFreeTs_->discount(t);
272 } else {
273 QL_REQUIRE(!movingSpot_.empty(), "SpreadedBlackVolatilitySurfaceLogMoneynessForward: movingSpot is empty");
274 QL_REQUIRE(!movingDividendTs_.empty(),
275 "SpreadedBlackVolatilitySurfaceLogMoneynessForward: movingDividendTs is empty");
276 QL_REQUIRE(!movingRiskFreeTs_.empty(),
277 "SpreadedBlackVolatilitySurfaceLogMoneynessForward: mocingRiskFreeTs is empty");
278 forward = movingSpot_->value() * movingDividendTs_->discount(t) / movingRiskFreeTs_->discount(t);
279 }
280 return std::exp(moneyness) * forward;
281}
282
283Real SpreadedBlackVolatilitySurfaceStdDevs::moneynessFromStrike(Time t, Real strike, const bool stickyReference) const {
284 if (strike == Null<Real>() || close_enough(strike, 0.0) || close_enough(t, 0.0))
285 return 0.0;
286 else {
287 QL_REQUIRE(!stickySpot_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: stickySpot is empty");
288 QL_REQUIRE(!stickyDividendTs_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: stickyDividendTs is empty");
289 QL_REQUIRE(!stickyRiskFreeTs_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: stickyRiskFreeTs is empty");
290 Real stickyForward = stickySpot_->value() * stickyDividendTs_->discount(t) / stickyRiskFreeTs_->discount(t);
291 Real forward;
292 if (stickyReference) {
293 forward = stickyForward;
294 } else {
295 QL_REQUIRE(!movingSpot_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: movingSpot is empty");
296 QL_REQUIRE(!movingDividendTs_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: movingDividendTs is empty");
297 QL_REQUIRE(!movingRiskFreeTs_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: mocingRiskFreeTs is empty");
298 forward = movingSpot_->value() * movingDividendTs_->discount(t) / movingRiskFreeTs_->discount(t);
299 }
300 // We use the sticky forward to read the vol for the definition of the standardised moneyness to
301 // avoid that this changes under forward curve changes. In the end this is a matter of definition and
302 // we might want to revise this later.
303 Real vol = referenceVol_->blackVol(t, stickyForward);
304 return std::log(strike / forward) / (vol * std::sqrt(t));
305 }
306}
307
309 const bool stickyReference) const {
310 Real stickyForward = stickySpot_->value() * stickyDividendTs_->discount(t) / stickyRiskFreeTs_->discount(t);
311 Real forward;
312 if (stickyReference) {
313 forward = stickyForward;
314 } else {
315 QL_REQUIRE(!movingSpot_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: movingSpot is empty");
316 QL_REQUIRE(!movingDividendTs_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: movingDividendTs is empty");
317 QL_REQUIRE(!movingRiskFreeTs_.empty(), "SpreadedBlackVolatilitySurfaceStdDevs: mocingRiskFreeTs is empty");
318 forward = movingSpot_->value() * movingDividendTs_->discount(t) / movingRiskFreeTs_->discount(t);
319 }
320 // We use the sticky forward to read the vol for the definition of the standardised moneyness to
321 // avoid that this changes under forward curve changes. In the end this is a matter of definition and
322 // we might want to revise this later.
323 Real vol = referenceVol_->blackVol(t, stickyForward);
324 return std::exp(moneyness * vol * std::sqrt(t)) * forward;
325}
326
328 const bool stickyReference) const {
329 if (strike == Null<Real>() || close_enough(strike, 0.0)) {
330 return 0.0;
331 } else {
332 QL_REQUIRE(!stickyReference || !stickySpot_.empty(),
333 "SpreadedBlackVolatilitySurfaceMoneynessSpot: stickySpot is empty");
334 QL_REQUIRE(stickyReference || !movingSpot_.empty(),
335 "SpreadedBlackVolatilitySurfaceMoneynessSpot: movingSpot is empty");
336 return strike - (stickyReference ? stickySpot_->value() : movingSpot_->value());
337 }
338}
339
341 const bool stickyReference) const {
342 QL_REQUIRE(!stickyReference || !stickySpot_.empty(),
343 "SpreadedBlackVolatilitySurfaceMoneynessSpot: stickySpot is empty");
344 QL_REQUIRE(stickyReference || !movingSpot_.empty(),
345 "SpreadedBlackVolatilitySurfaceMoneynessSpot: movingSpot is empty");
346 return moneyness + (stickyReference ? stickySpot_->value() : movingSpot_->value());
347}
348
350 const bool stickyReference) const {
351 if (strike == Null<Real>() || close_enough(strike, 0.0))
352 return 0.0;
353 else {
354 Real forward;
355 if (stickyReference) {
356 QL_REQUIRE(!stickySpot_.empty(), "SpreadedBlackVolatilitySurfaceMoneynessForward: stickySpot is empty");
357 QL_REQUIRE(!stickyDividendTs_.empty(),
358 "SpreadedBlackVolatilitySurfaceMoneynessForward: stickyDividendTs is empty");
359 QL_REQUIRE(!stickyRiskFreeTs_.empty(),
360 "SpreadedBlackVolatilitySurfaceMoneynessForward: stickyRiskFreeTs is empty");
361 forward = stickySpot_->value() * stickyDividendTs_->discount(t) / stickyRiskFreeTs_->discount(t);
362 } else {
363 QL_REQUIRE(!movingSpot_.empty(), "SpreadedBlackVolatilitySurfaceMoneynessForward: movingSpot is empty");
364 QL_REQUIRE(!movingDividendTs_.empty(),
365 "SpreadedBlackVolatilitySurfaceMoneynessForward: movingDividendTs is empty");
366 QL_REQUIRE(!movingRiskFreeTs_.empty(),
367 "SpreadedBlackVolatilitySurfaceMoneynessForward: mocingRiskFreeTs is empty");
368 forward = movingSpot_->value() * movingDividendTs_->discount(t) / movingRiskFreeTs_->discount(t);
369 }
370 return strike - forward;
371 }
372}
373
375 const bool stickyReference) const {
376 Real forward;
377 if (stickyReference) {
378 QL_REQUIRE(!stickySpot_.empty(), "SpreadedBlackVolatilitySurfaceMoneynessForward: stickySpot is empty");
379 QL_REQUIRE(!stickyDividendTs_.empty(),
380 "SpreadedBlackVolatilitySurfaceMoneynessForward: stickyDividendTs is empty");
381 QL_REQUIRE(!stickyRiskFreeTs_.empty(),
382 "SpreadedBlackVolatilitySurfaceMoneynessForward: stickyRiskFreeTs is empty");
383 forward = stickySpot_->value() * stickyDividendTs_->discount(t) / stickyRiskFreeTs_->discount(t);
384 } else {
385 QL_REQUIRE(!movingSpot_.empty(), "SpreadedBlackVolatilitySurfaceMoneynessForward: movingSpot is empty");
386 QL_REQUIRE(!movingDividendTs_.empty(),
387 "SpreadedBlackVolatilitySurfaceMoneynessForward: movingDividendTs is empty");
388 QL_REQUIRE(!movingRiskFreeTs_.empty(),
389 "SpreadedBlackVolatilitySurfaceMoneynessForward: mocingRiskFreeTs is empty");
390 forward = movingSpot_->value() * movingDividendTs_->discount(t) / movingRiskFreeTs_->discount(t);
391 }
392 return moneyness + forward;
393}
394
395} // namespace QuantExt
Real strikeFromMoneyness(Time t, Real moneyness, const bool stickyReference) const override
Real moneynessFromStrike(Time t, Real strike, const bool stickyReference) const override
Real strikeFromMoneyness(Time t, Real moneyness, const bool stickyReference) const override
Real moneynessFromStrike(Time t, Real strike, const bool stickyReference) const override
Real strikeFromMoneyness(Time t, Real moneyness, const bool stickyReference) const override
Real moneynessFromStrike(Time t, Real strike, const bool stickyReference) const override
Real strikeFromMoneyness(Time t, Real moneyness, const bool stickyReference) const override
Real moneynessFromStrike(Time t, Real strike, const bool stickyReference) const override
virtual Real strikeFromMoneyness(Time t, Real moneyness, const bool stickyReference) const =0
virtual Real moneynessFromStrike(Time t, Real strike, const bool stickyReference) const =0
SpreadedBlackVolatilitySurfaceMoneyness(const Handle< BlackVolTermStructure > &referenceVol, const Handle< Quote > &movingSpot, const std::vector< Time > &times, const std::vector< Real > &moneyness, const std::vector< std::vector< Handle< Quote > > > &volSpreads, const Handle< Quote > &stickySpot, const Handle< YieldTermStructure > &stickyDividendTs, const Handle< YieldTermStructure > &stickyRiskFreeTs, const Handle< YieldTermStructure > &movingDividendTs, const Handle< YieldTermStructure > &movingRiskFreeTs, bool stickyStrike)
Real strikeFromMoneyness(Time t, Real moneyness, const bool stickyReference) const override
Real moneynessFromStrike(Time t, Real strike, const bool stickyReference) const override
Real strikeFromMoneyness(Time t, Real moneyness, const bool stickyReference) const override
Real moneynessFromStrike(Time t, Real strike, const bool stickyReference) const override
Real strikeFromMoneyness(Time t, Real moneyness, const bool stickyReference) const override
Real moneynessFromStrike(Time t, Real strike, const bool stickyReference) const override
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Spreaded Black volatility surface based on moneyness.