Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
defaultableequityjumpdiffusionmodel.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2021 Quaternion Risk Management Ltd.
3
4 This file is part of ORE, a free-software/open-source library
5 for transparent pricing and risk analysis - http://opensourcerisk.org
6
7 ORE is free software: you can redistribute it and/or modify it
8 under the terms of the Modified BSD License. You should have received a
9 copy of the license along with this program.
10 The license is also available online at <http://opensourcerisk.org>
11
12 This program is distributed on the basis that it will form a useful
13 contribution to risk analytics and model standardisation, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
16*/
17
20
21#include <ql/math/distributions/normaldistribution.hpp>
22#include <ql/math/optimization/levenbergmarquardt.hpp>
23#include <ql/methods/finitedifferences/meshers/concentrating1dmesher.hpp>
24#include <ql/methods/finitedifferences/meshers/fdmmeshercomposite.hpp>
25#include <ql/methods/finitedifferences/meshers/uniform1dmesher.hpp>
26#include <ql/methods/finitedifferences/solvers/fdmbackwardsolver.hpp>
27#include <ql/pricingengines/blackformula.hpp>
28#include <ql/quotes/simplequote.hpp>
29#include <ql/time/calendars/nullcalendar.hpp>
30#include <ql/time/daycounters/actual365fixed.hpp>
31#include <ql/timegrid.hpp>
32
33namespace QuantExt {
34
35using namespace QuantLib;
36
38 const std::vector<Real>& stepTimes, const QuantLib::ext::shared_ptr<QuantExt::EquityIndex2>& equity,
39 const Handle<BlackVolTermStructure>& volatility, const Handle<DefaultProbabilityTermStructure>& creditCurve,
40 const Real p, const Real eta, const bool staticMesher, const Size timeStepsPerYear, const Size stateGridPoints,
41 const Real mesherEpsilon, const Real mesherScaling, const Real mesherConcentration,
42 const BootstrapMode bootstrapMode, const bool enforceFokkerPlanckBootstrap, const bool calibrate,
43 const bool adjustEquityVolatility, const bool adjustEquityForward)
44 : stepTimes_(stepTimes), equity_(equity), volatility_(volatility), creditCurve_(creditCurve), p_(p), eta_(eta),
45 staticMesher_(staticMesher), timeStepsPerYear_(timeStepsPerYear), stateGridPoints_(stateGridPoints),
46 mesherEpsilon_(mesherEpsilon), mesherScaling_(mesherScaling), mesherConcentration_(mesherConcentration),
47 bootstrapMode_(bootstrapMode), enforceFokkerPlanckBootstrap_(enforceFokkerPlanckBootstrap), calibrate_(calibrate),
48 adjustEquityVolatility_(adjustEquityVolatility), adjustEquityForward_(adjustEquityForward) {
49
50 // checks
51
52 QL_REQUIRE(!stepTimes.empty(), "DefaultableEquityJumpDiffusionModel: at least one step time required");
53 QL_REQUIRE(close_enough(p_, 0.0) || adjustEquityVolatility_,
54 "DefaultableEquityJumpDiffusionModel: for p != 0 (" << p_ << ") adjustEquityVolatility must be true");
55
56 // register with observables
57
58 marketObserver_ = QuantLib::ext::make_shared<MarketObserver>();
59 marketObserver_->registerWith(equity_);
60 marketObserver_->registerWith(creditCurve_);
61
62 registerWith(volatility_);
63 registerWith(marketObserver_);
64
65 // notify observers of all market data changes, not only when not calculated
66 alwaysForwardNotifications();
67}
68
69Handle<DefaultableEquityJumpDiffusionModel> DefaultableEquityJumpDiffusionModelBuilder::model() const {
70 calculate();
71 return model_;
72}
73
75 forceCalibration_ = true;
77 forceCalibration_ = false;
78}
79
81 return calibrationPointsChanged(false) || marketObserver_->hasUpdated(false) || forceCalibration_;
82}
83
85
86 // get the current foewards and vols
87
88 std::vector<Real> forwards;
89 std::vector<Real> variances;
90 for (auto const t : stepTimes_) {
91 forwards.push_back(equity_->equitySpot()->value() * equity_->equityDividendCurve()->discount(t) /
92 equity_->equityForecastCurve()->discount(t));
93 variances.push_back(volatility_->blackVariance(t, forwards.back()));
94 }
95
96 // check for differences
97
98 bool changed = false;
99 if (stepTimes_.size() != cachedForwards_.size() || stepTimes_.size() != cachedVariances_.size()) {
100 changed = true;
101 } else {
102 for (Size i = 0; i < stepTimes_.size() && !changed; ++i) {
103 // strict comparison is deliberate here!
104 if (cachedForwards_[i] != forwards[i])
105 changed = true;
106 else if (cachedVariances_[i] != variances[i])
107 changed = true;
108 }
109 }
110
111 // update cache if caller desires so
112
113 if (updateCache) {
114 cachedForwards_ = forwards;
115 cachedVariances_ = variances;
116 }
117
118 // return the result
119
120 return changed;
121}
122
124
125 if (requiresRecalibration()) {
126
128
129 // reset market observer's updated flag
130
131 marketObserver_->hasUpdated(true);
132
133 // setup model and bootstrap the model parameters
134
135 std::vector<Real> h0(stepTimes_.size(), 0.0);
136 std::vector<Real> sigma(stepTimes_.size(), 0.10);
137 model_.linkTo(QuantLib::ext::make_shared<DefaultableEquityJumpDiffusionModel>(
138 stepTimes_, h0, sigma, equity_, creditCurve_, volatility_->dayCounter(), p_, eta_, adjustEquityForward_));
139 if (calibrate_) {
143 }
144
145 // notify model observers
146 model_->notifyObservers();
147 }
148}
149
151 const std::vector<Real>& stepTimes, const std::vector<Real>& h0, const std::vector<Real>& sigma,
152 const QuantLib::ext::shared_ptr<QuantExt::EquityIndex2>& equity,
153 const Handle<QuantLib::DefaultProbabilityTermStructure>& creditCurve, const DayCounter& volDayCounter, const Real p,
154 const Real eta, const bool adjustEquityForward)
155 : stepTimes_(stepTimes), h0_(h0), sigma_(sigma), equity_(equity), creditCurve_(creditCurve),
156 volDayCounter_(volDayCounter), p_(p), eta_(eta), adjustEquityForward_(adjustEquityForward) {
157 registerWith(equity);
158 registerWith(creditCurve);
159}
160
162
163const std::vector<Real>& DefaultableEquityJumpDiffusionModel::stepTimes() const { return stepTimes_; }
164
165QuantLib::ext::shared_ptr<QuantExt::EquityIndex2> DefaultableEquityJumpDiffusionModel::equity() const { return equity_; }
166
168
170
171Handle<DefaultProbabilityTermStructure> DefaultableEquityJumpDiffusionModel::creditCurve() const {
172 return creditCurve_;
173}
174
176
178
180
182 return volDayCounter_.yearFraction(creditCurve_->referenceDate(), d);
183}
184
185const std::vector<Real>& DefaultableEquityJumpDiffusionModel::h0() const { return h0_; }
186
187const std::vector<Real>& DefaultableEquityJumpDiffusionModel::sigma() const { return sigma_; }
188
190 Size m = std::distance(stepTimes_.begin(),
191 std::lower_bound(stepTimes_.begin(), stepTimes_.end(), t,
192 [](Real x, Real y) { return x < y && !close_enough(x, y); }));
193 return std::min(m, stepTimes_.size() - 1);
194}
195
196Real DefaultableEquityJumpDiffusionModel::h(const Real t, const Real S) const {
197 return h0_[getTimeIndex(t)] * std::pow(equity_->equitySpot()->value() / S, p_);
198}
199
200Real DefaultableEquityJumpDiffusionModel::sigma(const Real t) const { return sigma_[getTimeIndex(t)]; }
201
203 if (t > fh_) {
204 return -std::log(equity_->equityForecastCurve()->discount(t + fh_) /
205 equity_->equityForecastCurve()->discount(t - fh_)) /
206 (2.0 * fh_);
207 } else {
208 return -std::log(equity_->equityForecastCurve()->discount(t + fh_) /
209 equity_->equityForecastCurve()->discount(t)) /
210 (fh_);
211 }
212}
213
215 if (t > fh_) {
216 return -std::log(equity_->equityDividendCurve()->discount(t + fh_) /
217 equity_->equityDividendCurve()->discount(t - fh_)) /
218 (2.0 * fh_);
219 } else {
220 return -std::log(equity_->equityDividendCurve()->discount(t + fh_) /
221 equity_->equityDividendCurve()->discount(t)) /
222 (fh_);
223 }
224}
225
226Real DefaultableEquityJumpDiffusionModel::dividendYield(const Real s, const Real t) const {
227 QL_REQUIRE(t > s || close_enough(s, t), "DefaultableEquityJumppDiffusionModel::dividendYield(): start time ("
228 << s << ") must be less or equal than end time (" << t << ")");
229 Real tmp = t;
230 if (close_enough(s, t)) {
231 tmp = s + fh_;
232 }
233 return -std::log(equity_->equityDividendCurve()->discount(tmp) / equity_->equityDividendCurve()->discount(s)) /
234 (tmp - s);
235}
236
238 const Handle<QuantLib::BlackVolTermStructure>& volatility, const bool staticMesher, const Size timeStepsPerYear,
239 const Size stateGridPoints, const Real mesherEpsilon, const Real mesherScaling, const Real mesherConcentration,
241 const bool enforceFokkerPlanckBootstrap, const bool adjustEquityVolatility) const {
242
243 // set total black variance
244
245 Real forward = equity_->equitySpot()->value() * equity_->equityDividendCurve()->discount(stepTimes_.back()) /
246 equity_->equityForecastCurve()->discount(stepTimes_.back());
247 totalBlackVariance_ = volatility->blackVariance(stepTimes_.back(), forward);
248
249 // check validity of credit curve, otherwise we might divide by zero below
250
251 for (Size i = 0; i < stepTimes_.size(); ++i) {
252 QL_REQUIRE(!close_enough(creditCurve_->survivalProbability(stepTimes_[i]), 0.0),
253 "DefaultableEquityJumpDiffusionModel: creditCurve implies zero survival probability at t = "
254 << stepTimes_[i]
255 << ", this can not be handled. Check the credit curve / security spread provided in the market "
256 "data. If this happens during a spread imply, the target price might not be attainable even "
257 "for high spreads.");
258 }
259
260 if (close_enough(p_, 0.0) && !enforceFokkerPlanckBootstrap) {
261
262 // 1. case p = 0:
263
264 Real accumulatedVariance = 0.0;
265
266 for (Size i = 0; i < stepTimes_.size(); ++i) {
267
268 // 1.1 the stepwise model h is just the average hazard rate over each interval
269
270 h0_[i] = -std::log(creditCurve_->survivalProbability(stepTimes_[i]) /
271 (i == 0 ? 1.0 : creditCurve_->survivalProbability(stepTimes_[i - 1]))) /
272 (stepTimes_[i] - (i == 0 ? 0.0 : stepTimes_[i - 1]));
273
274 // 1.2 determine the implied equity vol in our model
275
276 Real impliedModelVol = 0.0;
277 Real forward = equity_->equitySpot()->value() / equity_->equityForecastCurve()->discount(stepTimes_[i]) *
278 equity_->equityDividendCurve()->discount(stepTimes_[i]);
279
280 if (!adjustEquityVolatility) {
281
282 // 1.2.1 if we do not adjust the equity vol, we just read it from the market surface
283
284 impliedModelVol = volatility->blackVol(stepTimes_[i], forward);
285
286 } else {
287
288 // 1.2.2 if we adjust the equity vol, we first compute the market atm option price
289
290 Real marketPrice = blackFormula(Option::Call, forward, forward,
291 std::sqrt(volatility->blackVariance(stepTimes_[i], forward)),
292 equity_->equityForecastCurve()->discount(stepTimes_[i]));
293
294 // 1.2.2 adjust the forward and the discount factor by the credit drift / jump intensity and back out
295 // the model implied vol using these and the market price computed above
296
297 // TODO a tacit assumption here is that a defaulted equity does not contribute signficantly to the ATM
298 // call option payoff, i.e. that eta is sufficiently close to 1, review this and compare to the "full"
299 // implementation via Fokker-Planck below
300
301 Real adjustedForward = adjustEquityForward_
302 ? forward / std::pow(creditCurve_->survivalProbability(stepTimes_[i]), eta_)
303 : forward;
304 try {
305 impliedModelVol =
306 blackFormulaImpliedStdDev(Option::Call, forward, adjustedForward, marketPrice,
307 equity_->equityForecastCurve()->discount(stepTimes_[i]) *
308 creditCurve_->survivalProbability(stepTimes_[i])) /
309 std::sqrt(stepTimes_[i]);
310 } catch (...) {
311 }
312 }
313
314 impliedModelVol = std::max(1E-4, impliedModelVol);
315
316 // 1.3 bootstrap the stepwise model sigma
317
318 sigma_[i] =
319 i == 0
320 ? impliedModelVol
321 : std::sqrt(std::max(impliedModelVol * impliedModelVol * stepTimes_[i] - accumulatedVariance, 0.0) /
322 (stepTimes_[i] - stepTimes_[i - 1]));
323
324 accumulatedVariance += sigma_[i] * sigma_[i] * (stepTimes_[i] - (i == 0 ? 0.0 : stepTimes_[i - 1]));
325 }
326
327 } else {
328
329 // 2. case p != 0 (or enforced Fokker-Planck method for bootstrap)
330
331 // 2.1 set up solver for the Fokker-Planck PDE
332
333 // 2.1.1 build mesher
334
335 Real spot = equity()->equitySpot()->value();
336 Real logSpot = std::log(spot);
337
338 if (mesher_ == nullptr || !staticMesher) {
339
340 TimeGrid tmpGrid(stepTimes_.begin(), stepTimes_.end(),
341 std::max<Size>(std::lround(timeStepsPerYear * stepTimes_.back() + 0.5), 1));
342
343 Real mi = spot;
344 Real ma = spot;
345
346 Real forward = spot;
347 for (Size i = 1; i < tmpGrid.size(); ++i) {
348 forward = equity()->equitySpot()->value() * equity()->equityDividendCurve()->discount(tmpGrid[i]) /
349 equity()->equityForecastCurve()->discount(tmpGrid[i]) /
350 std::pow(creditCurve()->survivalProbability(tmpGrid[i]), eta());
351 mi = std::min(mi, forward);
352 ma = std::max(ma, forward);
353 }
354
355 Real sigmaSqrtT = std::max(1E-4, std::sqrt(volatility->blackVariance(tmpGrid.back(), forward)));
356 Real normInvEps = InverseCumulativeNormal()(1.0 - mesherEpsilon);
357 Real xMin = std::log(mi) - sigmaSqrtT * normInvEps * mesherScaling;
358 Real xMax = std::log(ma) + sigmaSqrtT * normInvEps * mesherScaling;
359
360 if (mesherConcentration != Null<Real>()) {
361 mesher_ = QuantLib::ext::make_shared<Concentrating1dMesher>(xMin, xMax, stateGridPoints,
362 std::make_pair(logSpot, mesherConcentration), true);
363 } else {
364 mesher_ = QuantLib::ext::make_shared<Uniform1dMesher>(xMin, xMax, stateGridPoints);
365 }
366 }
367
368 // 2.1.2 build operator
369
370 auto fdmOp = QuantLib::ext::make_shared<FdmDefaultableEquityJumpDiffusionFokkerPlanckOp>(
371 stepTimes_.back(), QuantLib::ext::make_shared<FdmMesherComposite>(mesher_), shared_from_this());
372
373 // 2.1.3 build solver
374
375 // TODO which schema / how many damping steps should we take? For the moment we follow the recommendation from
376 // the Andersen paper and use Crank-Nicholson after a "few" Rannacher steps (i.e. implicit Euler)
377 // TrBDF2 seems to be a specialised scheme for initial Dirac conditons though. Some research required...
378
379 auto solver = QuantLib::ext::make_shared<FdmBackwardSolver>(
380 fdmOp, std::vector<QuantLib::ext::shared_ptr<BoundaryCondition<FdmLinearOp>>>(), nullptr, FdmSchemeDesc::Douglas());
381
382 constexpr Size dampingSteps = 5;
383
384 // 2.2 boostrap h0 and sigma functions
385
386 // 2.2.1 set integration length dy for each grid point (= center of integration interval) and initial condition
387
388 Array p(mesher_->size(), 0.0), dy(mesher_->size(), 0.0);
389
390 for (Size i = 0; i < mesher_->size(); ++i) {
391 if (i > 0) {
392 dy[i] += 0.5 * mesher_->dminus(i);
393 } else {
394 dy[i] += 0.5 * mesher_->dminus(i + 1);
395 }
396 if (i < mesher_->size() - 1) {
397 dy[i] += 0.5 * mesher_->dplus(i);
398 } else {
399 dy[i] += 0.5 * mesher_->dplus(i - 1);
400 }
401 }
402
403 for (Size i = 0; i < mesher_->size(); ++i) {
404 if (i > 0) {
405 if ((logSpot > mesher_->location(i - 1) || close_enough(logSpot, mesher_->location(i - 1))) &&
406 (logSpot < mesher_->location(i) && !close_enough(logSpot, mesher_->location(i)))) {
407 Real alpha = (mesher_->location(i) - logSpot) / mesher_->dplus(i - 1);
408 p[i - 1] = alpha / dy[i - 1];
409 p[i] = (1 - alpha) / dy[i];
410 }
411 }
412 }
413
414 Array guess(2);
415
416 // bookkeeping to maange optimization guesses (see below)
417 constexpr Real thresholdSuccessfulOptimization = 1E-5;
418 Real lastOptimizationError = 0.0;
419 Array lastValidGuess;
420
421 for (Size i = 0; i < stepTimes_.size(); ++i) {
422
423 // 2.2.2 we roll from timestep i-1 (t=0 for i=0) to i and calibrate (h0, sigma) to the market
424
425 Real marketDefaultableBond = creditCurve_->survivalProbability(stepTimes_[i]) *
426 equity_->equityForecastCurve()->discount(stepTimes_[i]);
427 Real forward = equity_->equitySpot()->value() / equity_->equityForecastCurve()->discount(stepTimes_[i]) *
428 equity_->equityDividendCurve()->discount(stepTimes_[i]);
429 Real marketEquityOption = blackFormula(Option::Call, forward, forward,
430 std::sqrt(volatility->blackVariance(stepTimes_[i], forward)),
431 equity_->equityForecastCurve()->discount(stepTimes_[i]));
432
433 struct TargetFunction : public QuantLib::CostFunction {
434 static Real hToOpt(const Real x) { return std::log(std::max(x, 1E-6)); }
435 static Real sigmaToOpt(const Real x) { return std::log(std::max(x, 1E-6)); }
436 static Real hToReal(const Real x) { return std::exp(x); }
437 static Real sigmaToReal(const Real x) { return std::exp(x); }
438
439 enum class Mode { h0, sigma, both };
440 TargetFunction(const Mode mode, const Real strike, const Real marketEquityOption,
441 const Real marketDefaultableBond, Real& h0, Real& sigma, const Array& p,
442 const std::vector<Real>& locations, const Array& dy,
443 const QuantLib::ext::shared_ptr<FdmBackwardSolver>& solver, const Real t_from, const Real t_to,
444 const Size steps, const Size dampingSteps)
445 : mode_(mode), strike_(strike), marketEquityOption_(marketEquityOption),
446 marketDefaultableBond_(marketDefaultableBond), h0_(h0), sigma_(sigma), locations_(locations),
447 dy_(dy), p_(p), solver_(solver), t_from_(t_from), t_to_(t_to), steps_(steps),
448 dampingSteps_(dampingSteps) {}
449
450 Array values(const Array& x) const override {
451
452 // 2.2.2.0 set trial values from optimiser for h0 and sigma, we square the values to ensure
453 // positivity, this transformation is reverted when setting the guess and reading the solution
454
455 if (mode_ == Mode::h0) {
456 h0_ = hToReal(x[0]);
457 } else if (mode_ == Mode::sigma) {
458 sigma_ = sigmaToReal(x[0]);
459 } else {
460 h0_ = hToReal(x[0]);
461 sigma_ = sigmaToReal(x[1]);
462 }
463
464 // 2.2.2.1 roll density back (in "time to maturity", not in calendar time, which rolls forward)
465
466 Array pTmp = p_;
467 solver_->rollback(pTmp, t_from_, t_to_, steps_, dampingSteps_);
468
469 // 2.2.2.2 compute model defaultable zero bond and equity call option
470
471 Real defaultableBond = 0.0;
472 if (mode_ == Mode::h0 || mode_ == Mode::both) {
473 for (Size i = 0; i < pTmp.size(); ++i) {
474 defaultableBond += pTmp[i] * dy_[i];
475 }
476 }
477
478 Real equityOption = 0.0;
479 if (mode_ == Mode::sigma || mode_ == Mode::both) {
480 bool knockInLocation = true;
481 for (Size i = 0; i < pTmp.size(); ++i) {
482 if (locations_[i] > std::log(strike_) && !close_enough(locations_[i], std::log(strike_))) {
483 Real dy = dy_[i];
484 if (knockInLocation) {
485 // TODO there are more sophisticated corrections (reference in the Andersen paper?)
486 dy = (locations_[i] - std::log(strike_));
487 if (i < pTmp.size())
488 dy += 0.5 * (locations_[i + 1] - locations_[i]);
489 else
490 dy += 0.5 * (locations_[i] - locations_[i - 1]);
491 knockInLocation = false;
492 }
493 equityOption += pTmp[i] * dy * (std::exp(locations_[i]) - strike_);
494 }
495 }
496 }
497
498 if (mode_ == Mode::h0) {
499 Array result(1);
500 result[0] = (defaultableBond - marketDefaultableBond_) / marketDefaultableBond_;
501 return result;
502 } else if (mode_ == Mode::sigma) {
503 Array result(1);
504 result[0] = (equityOption - marketEquityOption_) / marketEquityOption_;
505 return result;
506 } else {
507 Array result(2);
508 result[0] = (defaultableBond - marketDefaultableBond_) / marketDefaultableBond_;
509 result[1] = (equityOption - marketEquityOption_) / marketEquityOption_;
510 return result;
511 }
512 }
513 const Mode mode_;
514 const Real strike_, marketEquityOption_, marketDefaultableBond_;
515 Real &h0_, &sigma_;
516 const std::vector<Real>& locations_;
517 const Array &dy_, &p_;
518 const QuantLib::ext::shared_ptr<FdmBackwardSolver> solver_;
519 const Real t_from_, t_to_;
520 const Size steps_, dampingSteps_;
521 };
522
523 /* 2.2.2.3 The first guess (i=0) are parameters computed with p = 0 as above.
524 A subsequent guess (i>0) is the last solution that resulted in an
525 error below a given threshold (or the guess for i=0 if that is the
526 only available guess). */
527
528 if (i == 0) {
529 guess[0] = -std::log(creditCurve_->survivalProbability(stepTimes_[i]) /
530 (i == 0 ? 1.0 : creditCurve_->survivalProbability(stepTimes_[i - 1]))) /
531 (stepTimes_[i] - (i == 0 ? 0.0 : stepTimes_[i - 1]));
532 guess[1] = std::sqrt(volatility->blackVariance(stepTimes_[i], forward) /
533 (stepTimes_[i] - (i == 0 ? 0.0 : stepTimes_[i - 1])));
534 lastValidGuess = guess;
535 } else {
536 if (lastOptimizationError < thresholdSuccessfulOptimization) {
537 guess[0] = h0_[i - 1];
538 guess[1] = sigma_[i - 1];
539 lastValidGuess = guess;
540 } else {
541 guess = lastValidGuess;
542 }
543 }
544
545 // 2.2.2.4 transform the guess
546
547 guess[0] = TargetFunction::hToOpt(guess[0]);
548 guess[1] = TargetFunction::sigmaToOpt(guess[1]);
549
550 // 2.2.2.5 run the optimzation for the current time step and set the model parameters to the found solution
551
552 Real t_from = stepTimes_.back() - (i == 0 ? 0.0 : stepTimes_[i - 1]);
553 Real t_to = stepTimes_.back() - stepTimes_[i];
554 Size steps = static_cast<Size>(0.5 + (t_from - t_to) * static_cast<Real>(timeStepsPerYear));
555
556 NoConstraint noConstraint;
557 LevenbergMarquardt lm;
558 EndCriteria endCriteria(100, 10, 1E-8, 1E-8, 1E-8);
560 // simultaneous optimization of (h0,sigma)
561 TargetFunction targetFunction(TargetFunction::Mode::both, forward, marketEquityOption,
562 marketDefaultableBond, h0_[i], sigma_[i], p, mesher_->locations(), dy,
563 solver, t_from, t_to, steps, i == 0 ? dampingSteps : 0);
564 Problem problem(targetFunction, noConstraint, guess);
565 lm.minimize(problem, endCriteria);
566 h0_[i] = TargetFunction::hToReal(problem.currentValue()[0]);
567 sigma_[i] = TargetFunction::sigmaToReal(problem.currentValue()[1]);
568 lastOptimizationError = problem.functionValue();
569 } else {
570 // alternating optimization of h0 and sigma
571 TargetFunction target_function_h0(TargetFunction::Mode::h0, forward, marketEquityOption,
572 marketDefaultableBond, h0_[i], sigma_[i], p, mesher_->locations(), dy,
573 solver, t_from, t_to, steps, i == 0 ? dampingSteps : 0);
574 TargetFunction target_function_sigma(TargetFunction::Mode::sigma, forward, marketEquityOption,
575 marketDefaultableBond, h0_[i], sigma_[i], p, mesher_->locations(),
576 dy, solver, t_from, t_to, steps, i == 0 ? dampingSteps : 0);
577 Real current0 = guess[0], current1 = guess[1];
578 Real delta0 = 0.0, delta1 = 0.0;
579 Real functionValue0 = 0.0, functionValue1 = 0.0;
580 Size iteration = 0;
581 constexpr Real tol_x = 1E-8;
582 do {
583 // optimize h0
584 Problem p0(target_function_h0, noConstraint, Array(1, current0));
585 lm.minimize(p0, endCriteria);
586 h0_[i] = TargetFunction::hToReal(p0.currentValue()[0]);
587 delta0 = std::abs(h0_[i] - TargetFunction::hToReal(current0));
588 current0 = p0.currentValue()[0];
589 functionValue0 = p0.functionValue();
590 // optimize sigma
591 Problem p1(target_function_sigma, noConstraint, Array(1, current1));
592 lm.minimize(p1, endCriteria);
593 sigma_[i] = TargetFunction::sigmaToReal(p1.currentValue()[0]);
594 delta1 = std::abs(sigma_[i] - TargetFunction::sigmaToReal(current1));
595 current1 = p1.currentValue()[0];
596 functionValue1 = p1.functionValue();
597 } while (iteration++ < 50 && (delta0 > tol_x || delta1 > tol_x));
598 lastOptimizationError =
599 std::sqrt(0.5 * (functionValue0 * functionValue0 + functionValue1 * functionValue1));
600 }
601
602 // 2.2.2.6 we still have to do the actual roll back with the solution we have found
603
604 solver->rollback(p, t_from, t_to, steps, i == 0 ? dampingSteps : 0);
605
606 } // bootstrap over step times
607 } // case p!= 0
608}
609
610} // namespace QuantExt
DefaultableEquityJumpDiffusionModelBuilder(const std::vector< Real > &stepTimes, const QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > &equity, const Handle< QuantLib::BlackVolTermStructure > &volatility, const Handle< QuantLib::DefaultProbabilityTermStructure > &creditCurve, const Real p=0.0, const Real eta=1.0, const bool staticMesher=false, const Size timeStepsPerYear=24, const Size stateGridPoints=100, const Real mesherEpsilon=1E-4, const Real mesherScaling=1.5, const Real mesherConcentration=Null< Real >(), const BootstrapMode mode=BootstrapMode::Alternating, const bool enforceFokkerPlanckBootstrap=false, const bool calibrate=true, const bool adjustEquityVolatility=true, const bool adjustEquityForward=true)
bool requiresRecalibration() const override
if false is returned, the model does not require a recalibration
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > equity_
Handle< DefaultableEquityJumpDiffusionModel > model() const
RelinkableHandle< DefaultableEquityJumpDiffusionModel > model_
Handle< QuantLib::DefaultProbabilityTermStructure > creditCurve() const
void bootstrap(const Handle< QuantLib::BlackVolTermStructure > &volatility, const bool staticMesher, const Size timeStepsPerYear, const Size stateGridPoints=100, const Real mesherEpsilon=1E-4, const Real mesherScaling=1.5, const Real mesherConcentration=Null< Real >(), const DefaultableEquityJumpDiffusionModelBuilder::BootstrapMode bootstrapMode=DefaultableEquityJumpDiffusionModelBuilder::BootstrapMode::Alternating, const bool enforceFokkerPlanckBootstrap=false, const bool adjustEquityVolatility=true) const
DefaultableEquityJumpDiffusionModel(const std::vector< Real > &stepTimes, const std::vector< Real > &h0, const std::vector< Real > &sigma, const QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > &equity, const Handle< QuantLib::DefaultProbabilityTermStructure > &creditCurve, const DayCounter &volDayCounter, const Real p=0.0, const Real eta=1.0, const bool adjustEquityForward=true)
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > equity_
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > equity() const
virtual void forceRecalculate()
force recalibration of model
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
std::vector< Size > steps