23#include <ql/exercise.hpp>
24#include <ql/math/integrals/simpsonintegral.hpp>
25#include <ql/math/solvers1d/brent.hpp>
26#include <ql/pricingengines/blackformula.hpp>
27#include <ql/pricingengines/credit/midpointcdsengine.hpp>
28#include <ql/termstructures/credit/flathazardrate.hpp>
29#include <ql/termstructures/yield/flatforward.hpp>
30#include <ql/time/daycounters/actual360.hpp>
32#include <boost/math/constants/constants.hpp>
45 "NumericalIntegrationIndexCdsOptionEngine::doCalc(): index recovery is not given.");
49 Date exerciseDate =
arguments_.exercise->dates().front();
50 Real exerciseTime =
volatility_->timeFromReference(exerciseDate);
51 Real omega =
arguments_.swap->side() == Protection::Buyer ? 1.0 : -1.0;
59 results_.additionalResults[
"discountToExerciseTradeCollateral"] = discTradeCollToExercise;
60 results_.additionalResults[
"discountToExerciseSwapCurrency"] = discSwapCurrToExercise;
61 results_.additionalResults[
"upfront"] =
63 (
arguments_.settlementType == Settlement::Cash ? discTradeCollToExercise / discSwapCurrToExercise : 1.0);
66 results_.additionalResults[
"callPut"] =
67 arguments_.swap->side() == Protection::Buyer ? std::string(
"Call") : std::string(
"Put");
76 results_.additionalResults[
"Model"] = std::string(
"LognormalPriceVolatility");
91 results_.additionalResults[
"strikePrice"] = strikePrice;
97 Real stdDev =
volatility * std::sqrt(exerciseTime);
99 results_.additionalResults[
"standardDeviation"] = stdDev;
103 Real forwardPriceExclFep = 1.0 - underlyingNpv /
arguments_.swap->notional() /
104 (Settlement::Cash ? discSwapCurrToExercise : discTradeCollToExercise);
105 Real forwardPrice = forwardPriceExclFep -
fep() /
arguments_.swap->notional() / discTradeCollToExercise;
107 results_.additionalResults[
"fepAdjustedForwardPrice"] = forwardPrice;
108 results_.additionalResults[
"forwardPrice"] = forwardPriceExclFep;
111 QL_REQUIRE(forwardPrice > 0.0 ||
close_enough(forwardPrice, 0.0),
112 "NumericalIntegrationIndexCdsOptionEngine: FEP adjusted forward price ("
113 << forwardPrice <<
") is not positive, can not calculate a reasonable option price");
114 QL_REQUIRE(strikePrice > 0 ||
close_enough(strikePrice, 0.0),
115 "NumericalIntegrationIndexCdsOptionEngine: Effective Strike price ("
116 << strikePrice <<
") is not positive, can not calculate a reasonable option price");
119 blackFormula(
arguments_.swap->side() == Protection::Buyer ? Option::Put : Option::Call,
120 strikePrice, forwardPrice, stdDev, discTradeCollToExercise);
126 results_.additionalResults[
"Model"] = std::string(
"LognormalSpreadVolatility");
131 Real averageInterestRate =
132 -std::log(tmpDisc->discount(
arguments_.swap->maturity()) / tmpDisc->discount(exerciseDate)) /
133 (maturityTime - exerciseTime);
137 Real strikeAdjustment;
145 results_.additionalResults[
"strikeAdjustment"] = strikeAdjustment;
155 brent.setLowerBound(1.0E-8);
156 auto strikeTarget = [
this, strikeAdjustment](Real strikeSpread) {
161 strikeSpread = brent.solve(strikeTarget, 1.0E-7,
arguments_.swap->fairSpreadClean(), 0.0001);
164 }
catch (
const std::exception& e) {
165 QL_FAIL(
"NumericalIntegrationIndexCdsOptionEngine: can not compute strike spread: "
166 << e.what() <<
", strikeAdjustment=" << strikeAdjustment <<
", trade strike "
167 <<
arguments_.strike <<
", trade strike type "
175 results_.additionalResults[
"strikeSpread"] = strikeSpread;
181 Real stdDev =
volatility * std::sqrt(exerciseTime);
183 results_.additionalResults[
"standardDeviation"] = stdDev;
187 Real forwardPriceExclFep = 1.0 -
189 (Settlement::Cash ? discSwapCurrToExercise : discTradeCollToExercise);
190 Real forwardPrice = forwardPriceExclFep -
191 fep() /
arguments_.swap->notional() / discTradeCollToExercise;
193 results_.additionalResults[
"fepAdjustedForwardPrice"] = forwardPrice;
194 results_.additionalResults[
"forwardPrice"] = forwardPriceExclFep;
198 auto Vc = [](Real t, Real T, Real r, Real R, Real c, Real stdDev, Real m, Real x) {
199 Real s = m * std::exp(-0.5 * stdDev * stdDev + stdDev * x);
200 Real w = (s / (1.0 - R) + r) * (T - t);
202 if (std::abs(w) < 1.0E-6)
203 a *= 1.0 - 0.5 * w + 1.0 / 6.0 * w * w - 1.0 / 24.0 * w * w * w;
205 a *= (1.0 - std::exp(-w)) / w;
211 SimpsonIntegral simpson = SimpsonIntegral(1.0E-7, 100);
213 auto target = [
this, &Vc, &simpson, exerciseTime, maturityTime, averageInterestRate, stdDev,
214 forwardPrice](Real m) {
216 [
this, &Vc, exerciseTime, maturityTime, averageInterestRate, stdDev, m](Real x) {
217 return Vc(exerciseTime, maturityTime, averageInterestRate,
indexRecovery_,
218 arguments_.swap->runningSpread(), stdDev, m, x) *
219 std::exp(-0.5 * x * x) / boost::math::constants::root_two_pi<Real>();
222 (1.0 - forwardPrice);
225 Real fepAdjustedForwardSpread;
226 if (target(0.0) > 0.0) {
229 fepAdjustedForwardSpread = 0.0;
232 brent.setLowerBound(0.0);
234 fepAdjustedForwardSpread = brent.solve(target, 1.0E-7,
arguments_.swap->fairSpreadClean(), 0.0001);
235 }
catch (
const std::exception& e) {
236 QL_FAIL(
"NumericalIntegrationIndexCdsOptionEngine::doCalc(): failed to calibrate forward spread: "
240 results_.additionalResults[
"fepAdjustedForwardSpread"] = fepAdjustedForwardSpread;
245 auto payoff = [
this, &Vc, exerciseTime, maturityTime, averageInterestRate, stdDev, fepAdjustedForwardSpread,
246 strikeAdjustment](Real x) {
247 return (Vc(exerciseTime, maturityTime, averageInterestRate,
indexRecovery_,
248 arguments_.swap->runningSpread(), stdDev, fepAdjustedForwardSpread, x) +
250 std::exp(-0.5 * x * x) / boost::math::constants::root_two_pi<Real>();
253 Real exerciseBoundary;
256 exerciseBoundary = brent2.solve(payoff, 1.0E-7, 0.0, 0.0001);
257 }
catch (
const std::exception& e) {
259 "NumericalIntegrationIndexCdsOptionEngine::doCalc(): failed to find exercise boundary: " << e.what());
261 results_.additionalResults[
"exerciseBoundary"] =
262 fepAdjustedForwardSpread * std::exp(-0.5 * stdDev * stdDev + stdDev * exerciseBoundary);
266 Real lowerIntegrationBound = -10.0, upperIntegrationBound = 10.0;
267 if (
arguments_.swap->side() == Protection::Buyer) {
268 lowerIntegrationBound = exerciseBoundary;
270 upperIntegrationBound = exerciseBoundary;
276 [exerciseTime, maturityTime, averageInterestRate,
this, stdDev,
277 fepAdjustedForwardSpread, omega, strikeAdjustment, &Vc](Real x) {
279 (Vc(exerciseTime, maturityTime, averageInterestRate,
indexRecovery_,
280 arguments_.swap->runningSpread(), stdDev, fepAdjustedForwardSpread, x) +
282 std::exp(-0.5 * x * x) / boost::math::constants::root_two_pi<Real>();
284 lowerIntegrationBound, upperIntegrationBound);
285 }
catch (
const std::exception& e) {
287 "NumericalIntegrationIndexCdsOptionEngine::doCalc(): failed to compute option payoff: " << e.what());
304 Schedule schedule = MakeSchedule()
305 .from(cds.protectionStartDate())
307 .withCalendar(WeekendsOnly())
308 .withFrequency(Quarterly)
309 .withConvention(Following)
310 .withTerminationDateConvention(Unadjusted)
311 .withRule(DateGeneration::CDS2015);
315 Real accuracy = 1e-8;
317 auto strikeCds = QuantLib::ext::make_shared<CreditDefaultSwap>(
318 Protection::Buyer, 1 / accuracy, strike, schedule, Following, Actual360(), cds.settlesAccrual(),
319 cds.protectionPaymentTime(), cds.protectionStartDate(), QuantLib::ext::shared_ptr<Claim>(), Actual360(
true),
true,
320 cds.tradeDate(), cds.cashSettlementDays());
322 strikeCds->setPricingEngine(QuantLib::ext::make_shared<MidPointCdsEngine>(
323 Handle<DefaultProbabilityTermStructure>(
324 QuantLib::ext::make_shared<FlatHazardRate>(0, NullCalendar(), 0.0, Actual365Fixed())),
325 0.0, Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(0, NullCalendar(), 0.0, Actual365Fixed()))));
331 }
catch (
const std::exception& e) {
332 QL_FAIL(
"can not imply fair hazard rate for CDS at option strike "
333 << strike <<
". Is the strike correct? Exception: " << e.what());
336 Handle<DefaultProbabilityTermStructure> dph(
337 QuantLib::ext::make_shared<FlatHazardRate>(
discountSwapCurrency_->referenceDate(), hazardRate, Actual365Fixed()));
340 strikeCds->setPricingEngine(
342 Real rpv01_K = std::abs(strikeCds->couponLegNPV() + strikeCds->accrualRebateNPV()) /
343 (strikeCds->notional() * strikeCds->runningSpread());
344 results_.additionalResults[
"riskyAnnuityStrike"] = rpv01_K;
345 QL_REQUIRE(rpv01_K > 0.0,
"BlackIndexCdsOptionEngine: strike based risky annuity must be positive.");
348 const Date& exerciseDate =
arguments_.exercise->dates().front();
349 Probability spToExercise = dph->survivalProbability(exerciseDate);
351 results_.additionalResults[
"strikeBasedSurvivalToExercise"] = spToExercise;
354 Real rpv01_K_fwd = rpv01_K / spToExercise / discToExercise;
355 results_.additionalResults[
"forwardRiskyAnnuityStrike"] = rpv01_K_fwd;
const Instrument::results * results_
QuantLib::Handle< QuantLib::YieldTermStructure > discountTradeCollateral_
QuantLib::Real fep() const
Calculate the discounted value of the front end protection.
const QuantLib::Handle< QuantExt::CreditVolCurve > volatility() const
QuantLib::Real indexRecovery_
Assumed index recovery used in the flat strike spread curve calculation if provided.
QuantLib::Handle< QuantLib::YieldTermStructure > discountSwapCurrency_
QuantLib::Handle< QuantExt::CreditVolCurve > volatility_
Real forwardRiskyAnnuityStrike(const Real strike) const
void doCalc() const override
Engine specific calculation.
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Real periodToTime(const Period &p)
numerical integration index credit default swap option engine.
Swap::arguments * arguments_