Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
blackvolsurfacedelta.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 Copyright (C) 2022 Skandinaviska Enskilda Banken AB (publ)
4 All rights reserved.
5
6 This file is part of ORE, a free-software/open-source library
7 for transparent pricing and risk analysis - http://opensourcerisk.org
8
9 ORE is free software: you can redistribute it and/or modify it
10 under the terms of the Modified BSD License. You should have received a
11 copy of the license along with this program.
12 The license is also available online at <http://opensourcerisk.org>
13
14 This program is distributed on the basis that it will form a useful
15 contribution to risk analytics and model standardisation, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include <ql/errors.hpp>
21#include <ql/experimental/fx/blackdeltacalculator.hpp>
22#include <ql/math/interpolations/cubicinterpolation.hpp>
23#include <ql/math/interpolations/linearinterpolation.hpp>
25
26using namespace std;
27using namespace QuantLib;
28
29namespace QuantExt {
30
31InterpolatedSmileSection::InterpolatedSmileSection(Real spot, Real rd, Real rf, Time t,
32 const std::vector<Real>& strikes,
33 const std::vector<Volatility>& vols, InterpolationMethod method,
34 bool flatExtrapolation)
35 : FxSmileSection(spot, rd, rf, t), strikes_(strikes), vols_(vols), flatExtrapolation_(flatExtrapolation) {
36
37 if (method == InterpolationMethod::Linear)
38 interpolator_ = Linear().interpolate(strikes_.begin(), strikes_.end(), vols_.begin());
39 else if (method == InterpolationMethod::NaturalCubic)
41 Cubic(CubicInterpolation::Kruger, true).interpolate(strikes_.begin(), strikes_.end(), vols_.begin());
42 else if (method == InterpolationMethod::FinancialCubic)
43 interpolator_ = Cubic(CubicInterpolation::Kruger, true, CubicInterpolation::SecondDerivative, 0.0,
44 CubicInterpolation::FirstDerivative)
45 .interpolate(strikes_.begin(), strikes_.end(), vols_.begin());
46 else if (method == InterpolationMethod::CubicSpline)
47 interpolator_ = CubicNaturalSpline(strikes_.begin(), strikes_.end(), vols_.begin());
48 else {
49 QL_FAIL("Invalid method " << (int)method);
50 }
51}
52
53Volatility InterpolatedSmileSection::volatility(Real strike) const {
55 if (strike < strikes_.front())
56 return vols_.front();
57 else if (strike > strikes_.back())
58 return vols_.back();
59 }
60 // and then call the interpolator with extrapolation on
61 return interpolator_(strike, true);
62}
63
65 Date referenceDate, const std::vector<Date>& dates, const std::vector<Real>& putDeltas,
66 const std::vector<Real>& callDeltas, bool hasAtm, const Matrix& blackVolMatrix, const DayCounter& dayCounter,
67 const Calendar& cal, const Handle<Quote>& spot, const Handle<YieldTermStructure>& domesticTS,
68 const Handle<YieldTermStructure>& foreignTS, DeltaVolQuote::DeltaType dt, DeltaVolQuote::AtmType at,
69 boost::optional<DeltaVolQuote::DeltaType> atmDeltaType, const Period& switchTenor, DeltaVolQuote::DeltaType ltdt,
70 DeltaVolQuote::AtmType ltat, boost::optional<QuantLib::DeltaVolQuote::DeltaType> longTermAtmDeltaType,
71 InterpolatedSmileSection::InterpolationMethod im, bool flatExtrapolation)
72 : BlackVolatilityTermStructure(referenceDate, cal, Following, dayCounter), dates_(dates), times_(dates.size(), 0),
73 putDeltas_(putDeltas), callDeltas_(callDeltas), hasAtm_(hasAtm), spot_(spot), domesticTS_(domesticTS),
74 foreignTS_(foreignTS), dt_(dt), at_(at), atmDeltaType_(atmDeltaType), switchTenor_(switchTenor), ltdt_(ltdt),
75 ltat_(ltat), longTermAtmDeltaType_(longTermAtmDeltaType), interpolationMethod_(im),
76 flatExtrapolation_(flatExtrapolation) {
77
78 // If ATM delta type is not given, set it to dt
79 if (!atmDeltaType_)
83
84 // set switch time
85 switchTime_ = switchTenor_ == 0 * Days ? QL_MAX_REAL : timeFromReference(optionDateFromTenor(switchTenor));
86
87 QL_REQUIRE(dates.size() > 1, "at least 1 date required");
88 // this has already been done for dates
89 for (Size i = 0; i < dates.size(); i++) {
90 QL_REQUIRE(referenceDate < dates[i], "Dates must be greater than reference date");
91 times_[i] = timeFromReference(dates[i]);
92 if (i > 0) {
93 QL_REQUIRE(times_[i] > times_[i - 1], "dates must be sorted unique!");
94 }
95 }
96
97 // check size of matrix
98 Size n = putDeltas.size() + (hasAtm ? 1 : 0) + callDeltas.size();
99 QL_REQUIRE(n > 0, "Need at least one delta");
100 QL_REQUIRE(blackVolMatrix.columns() == n, "Invalid number of columns in blackVolMatrix, got "
101 << blackVolMatrix.columns() << " but have " << n << " deltas");
102 QL_REQUIRE(blackVolMatrix.rows() == dates.size(), "Invalid number of rows in blackVolMatrix, got "
103 << blackVolMatrix.rows() << " but have " << dates.size()
104 << " dates");
105
106 // build interpolators for each delta
107 // TODO: template this so it can be changed
108 bool forceMonotoneVariance = false;
109 for (Size i = 0; i < n; i++) {
110 vector<Volatility> vols(dates.size());
111 for (Size j = 0; j < dates.size(); j++) {
112 vols[j] = blackVolMatrix[j][i];
113 }
114
115 // BlackVarianceCurve will make a local copy of vols and dates
116 interpolators_.push_back(
117 QuantLib::ext::make_shared<BlackVarianceCurve>(referenceDate, dates, vols, dayCounter, forceMonotoneVariance));
118 }
119
120 // register
121 registerWith(spot_);
122 registerWith(domesticTS_);
123 registerWith(foreignTS_);
124}
125
126QuantLib::ext::shared_ptr<FxSmileSection> BlackVolatilitySurfaceDelta::blackVolSmile(Time t) const {
127
128 Real spot = spot_->value();
129 DiscountFactor dDiscount = domesticTS_->discount(t);
130 DiscountFactor fDiscount = foreignTS_->discount(t);
131
132 DeltaVolQuote::AtmType at;
133 DeltaVolQuote::DeltaType dt;
134 DeltaVolQuote::DeltaType atmDt;
135 if (t < switchTime_ && !close_enough(t, switchTime_)) {
136 at = at_;
137 dt = dt_;
138 atmDt = *atmDeltaType_;
139 } else {
140 at = ltat_;
141 dt = ltdt_;
142 atmDt = *longTermAtmDeltaType_;
143 }
144
145 // Store smile section in map. Use strikes as key and vols as values for automatic sorting by strike.
146 // If we have already have a strike from a previous delta, we do not overwrite it.
147 auto comp = [](Real a, Real b) { return !close(a, b) && a < b; };
148 map<Real, Real, decltype(comp)> smileSection(comp);
149 Size i = 0;
150 for (Real delta : putDeltas_) {
151 Real vol = interpolators_.at(i)->blackVol(t, 1, true);
152 try {
153 BlackDeltaCalculator bdc(Option::Put, dt, spot, dDiscount, fDiscount, vol * sqrt(t));
154 Real strike = bdc.strikeFromDelta(delta);
155 if (smileSection.count(strike) == 0)
156 smileSection[strike] = vol;
157 } catch (const std::exception& e) {
158 QL_FAIL("BlackVolatilitySurfaceDelta: Error during calculating put strike at delta " << delta << ": "
159 << e.what());
160 }
161 i++;
162 }
163 if (hasAtm_) {
164 Real vol = interpolators_.at(i)->blackVol(t, 1, true);
165 try {
166 BlackDeltaCalculator bdc(Option::Put, atmDt, spot, dDiscount, fDiscount, vol * sqrt(t));
167 Real strike = bdc.atmStrike(at);
168 if (smileSection.count(strike) == 0)
169 smileSection[strike] = vol;
170 } catch (const std::exception& e) {
171 QL_FAIL("BlackVolatilitySurfaceDelta: Error during calculating atm strike: " << e.what());
172 }
173 i++;
174 }
175 for (Real delta : callDeltas_) {
176 Real vol = interpolators_.at(i)->blackVol(t, 1, true);
177 try {
178 BlackDeltaCalculator bdc(Option::Call, dt, spot, dDiscount, fDiscount, vol * sqrt(t));
179 Real strike = bdc.strikeFromDelta(delta);
180 if (smileSection.count(strike) == 0)
181 smileSection[strike] = vol;
182 } catch (const std::exception& e) {
183 QL_FAIL("BlackVolatilitySurfaceDelta: Error during calculating call strike at delta " << delta << ": "
184 << e.what());
185 }
186 i++;
187 }
188
189 // sort and extract to vectors
190 vector<Real> strikes;
191 strikes.reserve(smileSection.size());
192 vector<Real> vols;
193 vols.reserve(smileSection.size());
194 for (const auto& kv : smileSection) {
195 strikes.push_back(kv.first);
196 vols.push_back(kv.second);
197 }
198
199 // now build smile from strikes and vols
200 QL_REQUIRE(!vols.empty(),
201 "BlackVolatilitySurfaceDelta::blackVolSmile(" << t << "): no strikes given, this is unexpected.");
202 if (vols.size() == 1) {
203 // handle the situation that we only have one strike (might occur for e.g. t=0)
204 return QuantLib::ext::make_shared<ConstantSmileSection>(vols.front());
205 } else {
206 // we have at least two strikes
207 return QuantLib::ext::make_shared<InterpolatedSmileSection>(spot, dDiscount, fDiscount, t, strikes, vols,
209 }
210}
211
212QuantLib::ext::shared_ptr<FxSmileSection> BlackVolatilitySurfaceDelta::blackVolSmile(const Date& d) const {
213 return blackVolSmile(timeFromReference(d));
214}
215
217 return spot_->value() * foreignTS_->discount(t) / domesticTS_->discount(t); // TODO
218}
219
220Volatility BlackVolatilitySurfaceDelta::blackVolImpl(Time t, Real strike) const {
221 // Only flat extrapolation allowed
222 Time tme = t <= times_.back() ? t : times_.back();
223 // If asked for strike == 0, just return the ATM value.
224 if (strike == 0 || strike == Null<Real>()) {
225 if (hasAtm_) {
226 // ask the ATM interpolator directly
227 return interpolators_[putDeltas_.size()]->blackVol(tme, Null<Real>(), true);
228 } else {
229 // set strike to be fwd and we will return ATMF
230 strike = forward(tme);
231 }
232 }
233 return blackVolSmile(tme)->volatility(strike);
234}
235
236} // namespace QuantExt
Black volatility surface based on delta.
boost::optional< QuantLib::DeltaVolQuote::DeltaType > atmDeltaType_
BlackVolatilitySurfaceDelta(Date referenceDate, const std::vector< Date > &dates, const std::vector< Real > &putDeltas, const std::vector< Real > &callDeltas, bool hasAtm, const Matrix &blackVolMatrix, const DayCounter &dayCounter, const Calendar &cal, const Handle< Quote > &spot, const Handle< YieldTermStructure > &domesticTS, const Handle< YieldTermStructure > &foreignTS, DeltaVolQuote::DeltaType dt=DeltaVolQuote::DeltaType::Spot, DeltaVolQuote::AtmType at=DeltaVolQuote::AtmType::AtmDeltaNeutral, boost::optional< QuantLib::DeltaVolQuote::DeltaType > atmDeltaType=boost::none, const Period &switchTenor=0 *Days, DeltaVolQuote::DeltaType ltdt=DeltaVolQuote::DeltaType::Fwd, DeltaVolQuote::AtmType ltat=DeltaVolQuote::AtmType::AtmDeltaNeutral, boost::optional< QuantLib::DeltaVolQuote::DeltaType > longTermAtmDeltaType=boost::none, InterpolatedSmileSection::InterpolationMethod interpolationMethod=InterpolatedSmileSection::InterpolationMethod::Linear, bool flatExtrapolation=true)
std::vector< QuantLib::ext::shared_ptr< BlackVarianceCurve > > interpolators_
Handle< YieldTermStructure > domesticTS_
QuantLib::ext::shared_ptr< FxSmileSection > blackVolSmile(Time t) const
Return an FxSmile for the time t.
boost::optional< QuantLib::DeltaVolQuote::DeltaType > longTermAtmDeltaType_
InterpolatedSmileSection::InterpolationMethod interpolationMethod_
virtual Volatility blackVolImpl(Time t, Real strike) const override
const std::vector< QuantLib::Date > & dates() const
Volatility volatility(Real strike) const override
InterpolationMethod
Supported interpolation methods.
InterpolatedSmileSection(Real spot, Real rd, Real rf, Time t, const std::vector< Real > &strikes, const std::vector< Volatility > &vols, InterpolationMethod method, bool flatExtrapolation=false)
ctor
RandomVariable sqrt(RandomVariable x)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
vector< Real > strikes