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>
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) {
52 QL_REQUIRE(!stepTimes.empty(),
"DefaultableEquityJumpDiffusionModel: at least one step time required");
54 "DefaultableEquityJumpDiffusionModel: for p != 0 (" <<
p_ <<
") adjustEquityVolatility must be true");
66 alwaysForwardNotifications();
88 std::vector<Real> forwards;
89 std::vector<Real> variances;
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()));
102 for (Size i = 0; i <
stepTimes_.size() && !changed; ++i) {
136 std::vector<Real> sigma(
stepTimes_.size(), 0.10);
137 model_.linkTo(QuantLib::ext::make_shared<DefaultableEquityJumpDiffusionModel>(
146 model_->notifyObservers();
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) {
192 [](Real x, Real y) { return x < y && !close_enough(x, y); }));
204 return -std::log(
equity_->equityForecastCurve()->discount(t +
fh_) /
205 equity_->equityForecastCurve()->discount(t -
fh_)) /
208 return -std::log(
equity_->equityForecastCurve()->discount(t +
fh_) /
209 equity_->equityForecastCurve()->discount(t)) /
216 return -std::log(
equity_->equityDividendCurve()->discount(t +
fh_) /
217 equity_->equityDividendCurve()->discount(t -
fh_)) /
220 return -std::log(
equity_->equityDividendCurve()->discount(t +
fh_) /
221 equity_->equityDividendCurve()->discount(t)) /
227 QL_REQUIRE(t > s ||
close_enough(s, t),
"DefaultableEquityJumppDiffusionModel::dividendYield(): start time ("
228 << s <<
") must be less or equal than end time (" << t <<
")");
233 return -std::log(
equity_->equityDividendCurve()->discount(tmp) /
equity_->equityDividendCurve()->discount(s)) /
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 {
251 for (Size i = 0; i <
stepTimes_.size(); ++i) {
253 "DefaultableEquityJumpDiffusionModel: creditCurve implies zero survival probability at t = "
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.");
264 Real accumulatedVariance = 0.0;
266 for (Size i = 0; i <
stepTimes_.size(); ++i) {
276 Real impliedModelVol = 0.0;
280 if (!adjustEquityVolatility) {
284 impliedModelVol = volatility->blackVol(
stepTimes_[i], forward);
290 Real marketPrice = blackFormula(Option::Call, forward, forward,
291 std::sqrt(volatility->blackVariance(
stepTimes_[i], forward)),
306 blackFormulaImpliedStdDev(Option::Call, forward, adjustedForward, marketPrice,
314 impliedModelVol = std::max(1E-4, impliedModelVol);
321 : std::sqrt(std::max(impliedModelVol * impliedModelVol *
stepTimes_[i] - accumulatedVariance, 0.0) /
335 Real spot =
equity()->equitySpot()->value();
336 Real logSpot = std::log(spot);
338 if (
mesher_ ==
nullptr || !staticMesher) {
341 std::max<Size>(std::lround(timeStepsPerYear *
stepTimes_.back() + 0.5), 1));
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]) /
351 mi = std::min(mi, forward);
352 ma = std::max(ma, forward);
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;
360 if (mesherConcentration != Null<Real>()) {
361 mesher_ = QuantLib::ext::make_shared<Concentrating1dMesher>(xMin, xMax, stateGridPoints,
362 std::make_pair(logSpot, mesherConcentration),
true);
364 mesher_ = QuantLib::ext::make_shared<Uniform1dMesher>(xMin, xMax, stateGridPoints);
370 auto fdmOp = QuantLib::ext::make_shared<FdmDefaultableEquityJumpDiffusionFokkerPlanckOp>(
371 stepTimes_.back(), QuantLib::ext::make_shared<FdmMesherComposite>(
mesher_), shared_from_this());
379 auto solver = QuantLib::ext::make_shared<FdmBackwardSolver>(
380 fdmOp, std::vector<QuantLib::ext::shared_ptr<BoundaryCondition<FdmLinearOp>>>(),
nullptr, FdmSchemeDesc::Douglas());
382 constexpr Size dampingSteps = 5;
390 for (Size i = 0; i <
mesher_->size(); ++i) {
392 dy[i] += 0.5 *
mesher_->dminus(i);
394 dy[i] += 0.5 *
mesher_->dminus(i + 1);
396 if (i < mesher_->size() - 1) {
397 dy[i] += 0.5 *
mesher_->dplus(i);
399 dy[i] += 0.5 *
mesher_->dplus(i - 1);
403 for (Size i = 0; i <
mesher_->size(); ++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];
417 constexpr Real thresholdSuccessfulOptimization = 1E-5;
418 Real lastOptimizationError = 0.0;
419 Array lastValidGuess;
421 for (Size i = 0; i <
stepTimes_.size(); ++i) {
429 Real marketEquityOption = blackFormula(Option::Call, forward, forward,
430 std::sqrt(volatility->blackVariance(
stepTimes_[i], forward)),
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); }
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) {}
450 Array values(
const Array& x)
const override {
455 if (mode_ == Mode::h0) {
457 }
else if (mode_ == Mode::sigma) {
458 sigma_ = sigmaToReal(x[0]);
461 sigma_ = sigmaToReal(x[1]);
467 solver_->rollback(pTmp, t_from_, t_to_, steps_, dampingSteps_);
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];
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_))) {
484 if (knockInLocation) {
486 dy = (locations_[i] - std::log(strike_));
488 dy += 0.5 * (locations_[i + 1] - locations_[i]);
490 dy += 0.5 * (locations_[i] - locations_[i - 1]);
491 knockInLocation =
false;
493 equityOption += pTmp[i] * dy * (std::exp(locations_[i]) - strike_);
498 if (mode_ == Mode::h0) {
500 result[0] = (defaultableBond - marketDefaultableBond_) / marketDefaultableBond_;
502 }
else if (mode_ == Mode::sigma) {
504 result[0] = (equityOption - marketEquityOption_) / marketEquityOption_;
508 result[0] = (defaultableBond - marketDefaultableBond_) / marketDefaultableBond_;
509 result[1] = (equityOption - marketEquityOption_) / marketEquityOption_;
514 const Real strike_, marketEquityOption_, marketDefaultableBond_;
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_;
532 guess[1] = std::sqrt(volatility->blackVariance(
stepTimes_[i], forward) /
534 lastValidGuess = guess;
536 if (lastOptimizationError < thresholdSuccessfulOptimization) {
537 guess[0] =
h0_[i - 1];
539 lastValidGuess = guess;
541 guess = lastValidGuess;
547 guess[0] = TargetFunction::hToOpt(guess[0]);
548 guess[1] = TargetFunction::sigmaToOpt(guess[1]);
554 Size
steps =
static_cast<Size
>(0.5 + (t_from - t_to) *
static_cast<Real
>(timeStepsPerYear));
556 NoConstraint noConstraint;
557 LevenbergMarquardt lm;
558 EndCriteria endCriteria(100, 10, 1E-8, 1E-8, 1E-8);
561 TargetFunction targetFunction(TargetFunction::Mode::both, forward, marketEquityOption,
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();
571 TargetFunction target_function_h0(TargetFunction::Mode::h0, forward, marketEquityOption,
573 solver, t_from, t_to,
steps, i == 0 ? dampingSteps : 0);
574 TargetFunction target_function_sigma(TargetFunction::Mode::sigma, forward, marketEquityOption,
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;
581 constexpr Real tol_x = 1E-8;
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();
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));
604 solver->rollback(
p, t_from, t_to,
steps, i == 0 ? dampingSteps : 0);
std::vector< Real > stepTimes_
void forceRecalculate() override
force recalibration of model
void performCalculations() const override
Handle< BlackVolTermStructure > volatility_
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 adjustEquityForward_
Handle< DefaultProbabilityTermStructure > creditCurve_
std::vector< Real > cachedForwards_
std::vector< Real > cachedVariances_
bool requiresRecalibration() const override
if false is returned, the model does not require a recalibration
Real mesherConcentration_
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > equity_
bool enforceFokkerPlanckBootstrap_
bool calibrationPointsChanged(const bool updateCache) const
Handle< DefaultableEquityJumpDiffusionModel > model() const
QuantLib::ext::shared_ptr< MarketObserver > marketObserver_
bool adjustEquityVolatility_
BootstrapMode bootstrapMode_
RelinkableHandle< DefaultableEquityJumpDiffusionModel > model_
std::vector< Real > stepTimes_
bool adjustEquityForward() const
Real q(const Real t) const
const std::vector< Real > & h0() const
bool adjustEquityForward_
Handle< DefaultProbabilityTermStructure > creditCurve_
const std::vector< Real > & sigma() const
Handle< QuantLib::DefaultProbabilityTermStructure > creditCurve() const
Real r(const Real t) const
const std::vector< Real > & stepTimes() const
DayCounter volDayCounter_
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
Real timeFromReference(const Date &d) 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)
std::vector< Real > sigma_
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > equity_
QuantLib::ext::shared_ptr< Fdm1dMesher > mesher_
QuantLib::Size getTimeIndex(const Real t) const
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > equity() const
const DayCounter & volDayCounter() const
Real h(const Real t, const Real S) const
static constexpr Real fh_
Real dividendYield(const Real s, const Real t) const
Real totalBlackVariance() const
virtual void forceRecalculate()
force recalibration of model
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
std::vector< Size > steps