23#include <ql/experimental/math/laplaceinterpolation.hpp>
24#include <ql/math/comparison.hpp>
25#include <ql/math/interpolations/bilinearinterpolation.hpp>
26#include <ql/math/interpolations/flatextrapolation2d.hpp>
27#include <ql/math/optimization/costfunction.hpp>
28#include <ql/math/optimization/levenbergmarquardt.hpp>
29#include <ql/math/randomnumbers/haltonrsg.hpp>
30#include <ql/termstructures/volatility/sabr.hpp>
38 const MarketQuoteType inputMarketQuoteType,
const Handle<YieldTermStructure> discountCurve,
39 const std::map<std::pair<QuantLib::Real, QuantLib::Real>, std::vector<std::pair<Real, bool>>> modelParameters,
40 const Size maxCalibrationAttempts,
const Real exitEarlyErrorThreshold,
const Real maxAcceptableError)
42 modelVariant_(modelVariant), modelParameters_(std::move(modelParameters)),
43 maxCalibrationAttempts_(maxCalibrationAttempts), exitEarlyErrorThreshold_(exitEarlyErrorThreshold),
44 maxAcceptableError_(maxAcceptableError) {
63 QL_FAIL(
"SabrParametricVolatility::preferredOutputQuoteType(): model variant ("
69 const std::vector<Real>& randomSeq,
const Real forward,
70 const Real lognormalShift)
const {
71 std::vector<Real> result(4);
72 for (Size i = 0, j = 0; i < 4; ++i) {
73 if (params[i].second) {
74 result[i] = params[i].first;
79 result[0] = (
eps1 + randomSeq[j] * 0.01) / fbeta;
83 result[1] =
eps1 + randomSeq[j] *
eps2;
87 result[2] =
eps1 + randomSeq[j] * 5.0;
91 result[3] = (randomSeq[j] * 2.0 - 1.0) *
eps2;
106 return {{0.0050,
false}, {0.8,
false}, {0.30,
false}, {0.0,
false}};
108 return {{0.0050,
false}, {0.8,
false}, {0.30,
false}, {0.0,
false}};
110 return {{0.0050,
false}, {0.0,
true}, {0.30,
false}, {0.0,
false}};
112 return {{0.0050,
false}, {0.0,
true}, {0.30,
false}, {0.0,
false}};
114 return {{0.0050,
false}, {0.8,
false}, {0.30,
false}, {0.0,
false}};
116 return {{0.0050,
false}, {0.8,
false}, {0.30,
false}, {0.0,
false}};
118 QL_FAIL(
"SabrParametricVolatility::defaultModelParameters(): model variant (" <<
static_cast<int>(
modelVariant_)
119 <<
") not handled.");
124 const Real lognormalShift)
const {
125 std::vector<Real> y(4);
126 y[1] = std::max(
eps1, std::min(1.0 -
eps1, std::exp(-(x[1] * x[1]))));
129 y[2] = std::max(
eps1, std::exp(-(x[2] * x[2])) *
max_nu);
130 y[3] = std::fabs(x[3]) < 2.5 * M_PI ?
eps2 * std::sin(x[3]) :
eps2 * (x[3] > 0.0 ? 1.0 : (-1.0));
135 const Real lognormalShift)
const {
136 std::vector<Real> x(4);
137 x[1] = std::sqrt(-std::log(std::min(1.0 -
eps1, std::max(
eps1, y[1]))));
140 x[2] = std::sqrt(-std::log(std::min(1.0 -
eps1, std::max(
eps1, y[2] /
max_nu))));
141 x[3] = std::asin(std::max(-
eps2, std::min(
eps2, y[3])));
146 const Real timeToExpiry,
const Real lognormalShift,
147 const std::vector<Real>&
strikes)
const {
148 std::vector<Real> result;
152 for (Size i = 0; i <
strikes.size(); ++i) {
158 timeToExpiry, params[0], params[1], params[2], params[3]);
167 for (Size i = 0; i <
strikes.size(); ++i) {
173 timeToExpiry, params[0], params[1], params[2], params[3]);
182 for (Size i = 0; i <
strikes.size(); ++i) {
194 for (Size i = 0; i <
strikes.size(); ++i) {
197 params[2], params[3]) *
200 result[i] = result[i] - forward +
strikes[i];
211 std::max<Size>(5, std::lround(24.0 * timeToExpiry + 0.5)), 5.0);
213 for (Size i = 0; i <
strikes.size(); ++i) {
215 result[i] = result[i] - forward +
strikes[i];
219 result = std::vector<Real>(
strikes.size(), 0.0);
225 for (Size i = 0; i <
strikes.size(); ++i) {
231 timeToExpiry, params[0], params[1], params[2], params[3]);
239 QL_FAIL(
"SabrParametricVolatility::preferredOutputQuoteType(): model variant ("
245 for (
auto& v : result)
246 if (!std::isfinite(v))
252std::tuple<std::vector<Real>, Real, Size>
254 const std::vector<std::pair<Real, bool>>& params)
const {
258 Size noFreeParams = 0;
259 for (
auto const& p : params)
265 if (noFreeParams == 0) {
266 std::vector<Real> resultParams;
267 for (
auto const& p : params)
268 resultParams.push_back(p.first);
269 return std::make_tuple(resultParams, 0.0, 0);
274 QL_REQUIRE(noFreeParams <= marketSmile.
strikes.size(),
"internal: less data points than free parameters");
278 struct TargetFunction :
public QuantLib::CostFunction {
281 Real lognormalShift_;
282 std::vector<Real> strikes_;
283 std::vector<Real> marketQuotes_;
285 std::function<std::vector<Real>(
const std::vector<Real>&,
const Real,
const Real,
const Real,
286 const std::vector<Real>&)>
288 std::vector<std::pair<Real, bool>> params_;
289 std::vector<Real> invParams_;
290 std::function<std::vector<Real>(
const std::vector<Real>&)> direct_;
291 std::function<std::vector<Real>(
const std::vector<Real>&)> inverse_;
293 std::vector<Real> evalSabr(
const Array& x)
const {
294 std::vector<Real> params(4);
295 for (Size i = 0, j = 0; i < params_.size(); ++i) {
296 if (params_[i].second)
297 params[i] = invParams_[i];
301 params = direct_(params);
302 return evalSabr_(params, forward_, timeToExpiry_, lognormalShift_, strikes_);
305 Array values(
const Array& x)
const override {
306 Array result(strikes_.size());
307 auto sabr = evalSabr(x);
308 for (Size i = 0; i < strikes_.size(); ++i) {
309 result[i] = (marketQuotes_[i] - sabr[i]) / refQuote_;
321 t.forward_ = marketSmile.
forward;
325 t.evalSabr_ = [
this](
const std::vector<Real>& params,
const Real forward,
const Real timeToExpiry,
331 for (Size i = 0; i < params.size(); ++i)
332 t.invParams_.push_back(params[i].first);
335 t.inverse_ = [
this, &marketSmile](
const std::vector<Real>& y) {
338 t.direct_ = [
this, &marketSmile](
const std::vector<Real>& x) {
342 t.strikes_ = marketSmile.
strikes;
343 for (Size i = 0; i < marketSmile.
marketQuotes.size(); ++i) {
344 t.marketQuotes_.push_back(
convert(
346 marketSmile.
optionTypes.empty() ? boost::none : boost::optional<Option::Type>(marketSmile.
optionTypes[i]),
351 t.refQuote_ = *std::max_element(t.marketQuotes_.begin(), t.marketQuotes_.end());
355 NoConstraint noConstraint;
356 LevenbergMarquardt lm;
357 EndCriteria endCriteria(100, 10, 1E-8, 1E-8, 1E-8);
359 std::vector<Real> bestResult(params.size());
360 Real bestError = QL_MAX_REAL;
362 HaltonRsg haltonRsg(noFreeParams, 42);
364 Array guess(noFreeParams);
371 for (Size i = 0, j = 0; i < t.invParams_.size(); ++i) {
372 if (!params[i].second) {
373 guess[j++] = t.invParams_[i];
378 auto g =
inverse(
getGuess(params, haltonRsg.nextSequence().value, t.forward_, t.lognormalShift_),
379 t.forward_, t.lognormalShift_);
380 for (Size i = 0, j = 0; i < g.size(); ++i) {
381 if (!params[i].second) {
387 Problem problem(t, noConstraint, guess);
389 lm.minimize(problem, endCriteria);
394 Real thisError = problem.functionValue();
395 if (thisError < bestError) {
396 bestError = thisError;
397 for (Size i = 0, j = 0; i < bestResult.size(); ++i) {
398 if (params[i].second)
399 bestResult[i] = t.invParams_[i];
401 bestResult[i] = problem.currentValue()[j++];
403 bestResult =
direct(bestResult, t.forward_, t.lognormalShift_);
410 QL_REQUIRE(bestError < QL_MAX_REAL,
"internal: all calibrations failed");
412 return std::make_tuple(bestResult, bestError, ++attempt);
416void laplaceInterpolationWithErrorHandling(Matrix& m,
const std::vector<Real>& x,
const std::vector<Real>& y) {
418 laplaceInterpolation(m, x, y, 1E-6, 100);
419 }
catch (
const std::exception& e) {
420 QL_FAIL(
"Error during laplaceInterpolation() in SabrParametricVolatility: "
421 << e.what() <<
", this might be related to the numerical parameters relTol, maxIterMult. Contact dev.");
445 auto key = std::make_pair(s.timeToExpiry, s.underlyingLength);
448 "SabrParametricVolatility::performCalculations(): no model parameter given for ("
449 << s.timeToExpiry <<
", " << s.underlyingLength
450 <<
"). All (timeToExpiry, underlyingLength) pairs that are given as market points must be "
451 "covered by the given model parameters.");
458 }
catch (
const std::exception& e) {
466 std::set<Real> tmpTimeToExpiries, tmpUnderlyingLengths;
469 tmpTimeToExpiries.insert(s.timeToExpiry);
470 tmpUnderlyingLengths.insert(s.underlyingLength);
473 timeToExpiries_ = std::vector<Real>(tmpTimeToExpiries.begin(), tmpTimeToExpiries.end());
474 underlyingLengths_ = std::vector<Real>(tmpUnderlyingLengths.begin(), tmpUnderlyingLengths.end());
481 alpha_ = Matrix(m, n, Null<Real>());
482 beta_ = Matrix(m, n, Null<Real>());
483 nu_ = Matrix(m, n, Null<Real>());
484 rho_ = Matrix(m, n, Null<Real>());
490 for (Size i = 0; i < m; ++i) {
491 for (Size j = 0; j < n; ++j) {
494 alpha_(i, j) = p->second[0];
495 beta_(i, j) = p->second[1];
496 nu_(i, j) = p->second[2];
497 rho_(i, j) = p->second[3];
521 for (Size i = 0; i < m; ++i) {
522 for (Size j = 0; j < n; ++j) {
525 nu_(i, j) = std::max(
nu_(i, j), 0.0);
526 rho_(i, j) = std::max(std::min(
rho_(i, j), 1.0), -1.0);
535 if (m == 1 || n == 1) {
537 auto mNew = m == 1 ? m + 1 : m;
538 auto nNew = n == 1 ? n + 1 : n;
541 auto betaTmp =
beta_;
546 alpha_ = Matrix(mNew, nNew, Null<Real>());
547 beta_ = Matrix(mNew, nNew, Null<Real>());
548 nu_ = Matrix(mNew, nNew, Null<Real>());
549 rho_ = Matrix(mNew, nNew, Null<Real>());
552 for (Size i = 0; i < mNew; ++i) {
553 for (Size j = 0; j < nNew; ++j) {
554 Size iOld = std::min(i, m - 1);
555 Size jOld = std::min(j, n - 1);
556 alpha_(i, j) = alphaTmp(iOld, jOld);
557 beta_(i, j) = betaTmp(iOld, jOld);
558 nu_(i, j) = nuTmp(iOld, jOld);
559 rho_(i, j) = rhoTmp(iOld, jOld);
585 nuInterpolation_ = FlatExtrapolator2D(boost::make_shared<BilinearInterpolation>(
588 rhoInterpolation_ = FlatExtrapolator2D(boost::make_shared<BilinearInterpolation>(
604 const Real outputLognormalShift,
605 const boost::optional<QuantLib::Option::Type> outputOptionType)
const {
615 outputMarketQuoteType, outputLognormalShift == Null<Real>() ?
lognormalShift : outputLognormalShift,
std::vector< Real > callPrices(const std::vector< Real > &strikes) const
@ ShiftedLognormalVolatility
std::vector< MarketSmile > marketSmiles_
Real convert(const Real inputQuote, const MarketQuoteType inputMarketQuoteType, const QuantLib::Real inputLognormalShift, const boost::optional< QuantLib::Option::Type > inputOptionType, const QuantLib::Real timeToExpiry, const QuantLib::Real strike, const QuantLib::Real forward, const MarketQuoteType outputMarketQuoteType, const QuantLib::Real outputLognormalShift, const boost::optional< QuantLib::Option::Type > outputOptionType=boost::none) const
QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve_
MarketQuoteType inputMarketQuoteType_
SabrParametricVolatility(const ModelVariant modelVariant, const std::vector< MarketSmile > marketSmiles, const MarketModelType marketModelType, const MarketQuoteType inputMarketQuoteType, const QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve, const std::map< std::pair< QuantLib::Real, QuantLib::Real >, std::vector< std::pair< Real, bool > > > modelParameters={}, const QuantLib::Size maxCalibrationAttempts=10, const QuantLib::Real exitEarlyErrorThreshold=0.005, const QuantLib::Real maxAcceptableError=0.05)
static constexpr double eps1
QuantLib::Interpolation2D alphaInterpolation_
std::vector< Real > underlyingLengths_
static constexpr double eps2
const QuantLib::Matrix & rho() const
QuantLib::Matrix numberOfCalibrationAttempts_
QuantLib::Matrix calibrationError_
std::vector< Real > timeToExpiriesForInterpolation_
QuantLib::Interpolation2D lognormalShiftInterpolation_
static constexpr double max_nvol_equiv
std::vector< std::pair< Real, bool > > defaultModelParameters() const
const QuantLib::Matrix & alpha() const
static constexpr double max_nu
QuantLib::Interpolation2D nuInterpolation_
std::vector< Real > evaluateSabr(const std::vector< Real > ¶ms, const Real forward, const Real timeToExpiry, const Real lognormalShift, const std::vector< Real > &strikes) const
std::map< std::pair< Real, Real >, Real > calibrationErrors_
QuantLib::Matrix lognormalShift_
std::vector< Real > underlyingLengthsForInterpolation_
QuantLib::Real exitEarlyErrorThreshold_
const QuantLib::Matrix & beta() const
const QuantLib::Matrix & lognormalShift() const
ModelVariant modelVariant_
QuantLib::Real maxAcceptableError_
std::vector< Real > inverse(const std::vector< Real > &y, const Real forward, const Real lognormalShift) const
QuantLib::Real evaluate(const QuantLib::Real timeToExpiry, const QuantLib::Real underlyingLength, const QuantLib::Real strike, const QuantLib::Real forward, const MarketQuoteType outputMarketQuoteType, const QuantLib::Real outputLognormalShift=QuantLib::Null< QuantLib::Real >(), const boost::optional< QuantLib::Option::Type > outputOptionType=boost::none) const override
std::map< std::pair< Real, Real >, QuantLib::Size > noOfAttempts_
QuantLib::Matrix isInterpolated_
std::map< std::pair< Real, Real >, std::vector< Real > > calibratedSabrParams_
std::vector< Real > getGuess(const std::vector< std::pair< Real, bool > > ¶ms, const std::vector< Real > &randomSeq, const Real forward, const Real lognormalShift) const
@ Antonov2015FreeBoundaryNormal
@ Hagan2002NormalZeroBeta
QuantLib::Interpolation2D rhoInterpolation_
std::map< std::pair< QuantLib::Real, QuantLib::Real >, std::vector< std::pair< Real, bool > > > modelParameters_
const QuantLib::Matrix & nu() const
std::tuple< std::vector< Real >, Real, QuantLib::Size > calibrateModelParameters(const MarketSmile &marketSmile, const std::vector< std::pair< Real, bool > > ¶ms) const
QuantLib::Size maxCalibrationAttempts_
QuantLib::Interpolation2D betaInterpolation_
std::vector< Real > timeToExpiries_
std::map< std::pair< Real, Real >, Real > lognormalShifts_
std::vector< Real > direct(const std::vector< Real > &x, const Real forward, const Real lognormalShift) const
ParametricVolatility::MarketQuoteType preferredOutputQuoteType() const
Adaption of VBA code by Jörg Kienitz, 2017, to create a SABR density with PDE methods.
Real normalSabrVolatility(Rate strike, Rate forward, Time expiryTime, Real alpha, Real nu, Real rho)
Real normalFreeBoundarySabrPrice(Rate strike, Rate forward, Time expiryTime, Real alpha, Real nu, Real rho)
normal SABR model implied volatility approximation
sabr volatility structure
std::vector< QuantLib::Option::Type > optionTypes
QuantLib::Real lognormalShift
std::vector< QuantLib::Real > strikes
QuantLib::Real timeToExpiry
std::vector< QuantLib::Real > marketQuotes